@nestia/e2e 7.3.3 → 8.0.0-dev.20250829

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.
@@ -84,21 +84,21 @@ var json_equal_to_1 = require("./internal/json_equal_to");
84
84
  * HTTP error validation, pagination testing, search functionality validation,
85
85
  * and sorting validation.
86
86
  *
87
- * All functions follow a currying pattern to enable reusable test
88
- * configurations and provide detailed error messages for debugging failed
89
- * assertions.
87
+ * Most functions use direct parameter passing for simplicity, while some
88
+ * maintain currying patterns for advanced composition. All provide detailed
89
+ * error messages for debugging failed assertions.
90
90
  *
91
91
  * @author Jeongho Nam - https://github.com/samchon
92
92
  * @example
93
93
  * ```typescript
94
94
  * // Basic condition testing
95
- * TestValidator.predicate("user should be authenticated")(user.isAuthenticated);
95
+ * TestValidator.predicate("user should be authenticated", user.isAuthenticated);
96
96
  *
97
97
  * // Equality validation
98
- * TestValidator.equals("API response should match expected")(expected)(actual);
98
+ * TestValidator.equals("API response should match expected", x, y);
99
99
  *
100
100
  * // Error validation
101
- * TestValidator.error("should throw on invalid input")(() => validateInput(""));
101
+ * TestValidator.error("should throw on invalid input", () => assertInput(""));
102
102
  * ```;
103
103
  */
104
104
  var TestValidator;
@@ -114,53 +114,54 @@ var TestValidator;
114
114
  * @example
115
115
  * ```typescript
116
116
  * // Synchronous boolean
117
- * TestValidator.predicate("user should exist")(user !== null);
117
+ * TestValidator.predicate("user should exist", user !== null);
118
118
  *
119
119
  * // Synchronous function
120
- * TestValidator.predicate("array should be empty")(() => arr.length === 0);
120
+ * TestValidator.predicate("array should be empty", () => arr.length === 0);
121
121
  *
122
122
  * // Asynchronous function
123
- * await TestValidator.predicate("database should be connected")(
123
+ * await TestValidator.predicate("database should be connected",
124
124
  * async () => await db.ping()
125
125
  * );
126
126
  * ```;
127
127
  *
128
128
  * @param title - Descriptive title used in error messages when validation
129
129
  * fails
130
- * @returns A currying function that accepts the condition to validate
130
+ * @param condition - The condition to validate (boolean, function, or async
131
+ * function)
132
+ * @returns Void or Promise<void> based on the input type
131
133
  * @throws Error with descriptive message when condition is not satisfied
132
134
  */
133
- TestValidator.predicate = function (title) {
134
- return function (condition) {
135
- var message = function () {
136
- return "Bug on ".concat(title, ": expected condition is not satisfied.");
137
- };
138
- // SCALAR
139
- if (typeof condition === "boolean") {
140
- if (condition !== true)
141
- throw new Error(message());
142
- return undefined;
143
- }
144
- // CLOSURE
145
- var output = condition();
146
- if (typeof output === "boolean") {
147
- if (output !== true)
148
- throw new Error(message());
149
- return undefined;
150
- }
151
- // ASYNCHRONOUS
152
- return new Promise(function (resolve, reject) {
153
- output
154
- .then(function (flag) {
155
- if (flag === true)
156
- resolve();
157
- else
158
- reject(message());
159
- })
160
- .catch(reject);
161
- });
135
+ function predicate(title, condition) {
136
+ var message = function () {
137
+ return "Bug on ".concat(title, ": expected condition is not satisfied.");
162
138
  };
163
- };
139
+ // SCALAR
140
+ if (typeof condition === "boolean") {
141
+ if (condition !== true)
142
+ throw new Error(message());
143
+ return undefined;
144
+ }
145
+ // CLOSURE
146
+ var output = condition();
147
+ if (typeof output === "boolean") {
148
+ if (output !== true)
149
+ throw new Error(message());
150
+ return undefined;
151
+ }
152
+ // ASYNCHRONOUS
153
+ return new Promise(function (resolve, reject) {
154
+ output
155
+ .then(function (flag) {
156
+ if (flag === true)
157
+ resolve();
158
+ else
159
+ reject(message());
160
+ })
161
+ .catch(reject);
162
+ });
163
+ }
164
+ TestValidator.predicate = predicate;
164
165
  /**
165
166
  * Validates deep equality between two values using JSON comparison.
166
167
  *
@@ -168,61 +169,44 @@ var TestValidator;
168
169
  * exception filter to ignore specific keys during comparison. Useful for
169
170
  * validating API responses, data transformations, and object state changes.
170
171
  *
171
- * **Type Safety Notes:**
172
- *
173
- * - The generic type T is inferred from the `actual` parameter (first in the
174
- * currying chain)
175
- * - The `expected` parameter must be assignable to `T | null | undefined`
176
- * - For objects, `expected` must have the same or subset of properties as
177
- * `actual`
178
- * - For union types like `string | null`, ensure proper type compatibility:
179
- *
180
- * ```typescript
181
- * const x: string | null;
182
- * TestValidator.equals("works")(x)(null); // ✅ Works: null is assignable to string | null
183
- * TestValidator.equals("error")(null)(x); // ❌ Error: x might be string, but expected is null
184
- * ```
185
- *
186
172
  * @example
187
173
  * ```typescript
188
174
  * // Basic equality
189
- * TestValidator.equals("response should match expected")(expectedUser)(actualUser);
175
+ * TestValidator.equals("response should match expected", expectedUser, actualUser);
190
176
  *
191
177
  * // Ignore timestamps in comparison
192
- * TestValidator.equals("user data should match", (key) => key === "updatedAt")(
193
- * expectedUser
194
- * )(actualUser);
178
+ * TestValidator.equals("user data should match", expectedUser, actualUser,
179
+ * (key) => key === "updatedAt"
180
+ * );
195
181
  *
196
182
  * // Validate API response structure
197
- * const validateResponse = TestValidator.equals("API response structure");
198
- * validateResponse({ id: 1, name: "John" })({ id: 1, name: "John" });
183
+ * TestValidator.equals("API response structure",
184
+ * { id: 1, name: "John" },
185
+ * { id: 1, name: "John" }
186
+ * );
199
187
  *
200
188
  * // Type-safe nullable comparisons
201
189
  * const nullableData: { name: string } | null = getData();
202
- * TestValidator.equals("nullable check")(nullableData)(null); // ✅ Safe
190
+ * TestValidator.equals("nullable check", nullableData, null);
203
191
  * ```;
204
192
  *
205
193
  * @param title - Descriptive title used in error messages when values differ
194
+ * @param x - The first value to compare
195
+ * @param y - The second value to compare (can be null or undefined)
206
196
  * @param exception - Optional filter function to exclude specific keys from
207
197
  * comparison
208
- * @returns A currying function chain: first accepts expected value, then
209
- * actual value
210
198
  * @throws Error with detailed diff information when values are not equal
211
199
  */
212
- TestValidator.equals = function (title, exception) {
213
- if (exception === void 0) { exception = function () { return false; }; }
214
- return function (actual) {
215
- return function (expected) {
216
- var diff = (0, json_equal_to_1.json_equal_to)(exception)(actual)(expected);
217
- if (diff.length)
218
- throw new Error([
219
- "Bug on ".concat(title, ": found different values - [").concat(diff.join(", "), "]:"),
220
- "\n",
221
- JSON.stringify({ actual: actual, expected: expected }, null, 2),
222
- ].join("\n"));
223
- };
224
- };
225
- };
200
+ function equals(title, x, y, exception) {
201
+ var diff = (0, json_equal_to_1.json_equal_to)(exception !== null && exception !== void 0 ? exception : (function () { return false; }))(x)(y);
202
+ if (diff.length)
203
+ throw new Error([
204
+ "Bug on ".concat(title, ": found different values - [").concat(diff.join(", "), "]:"),
205
+ "\n",
206
+ JSON.stringify({ x: x, y: y }, null, 2),
207
+ ].join("\n"));
208
+ }
209
+ TestValidator.equals = equals;
226
210
  /**
227
211
  * Validates deep inequality between two values using JSON comparison.
228
212
  *
@@ -231,62 +215,42 @@ var TestValidator;
231
215
  * comparison. Useful for validating that data has changed, objects are
232
216
  * different, or mutations have occurred.
233
217
  *
234
- * **Type Safety Notes:**
235
- *
236
- * - The generic type T is inferred from the `actual` parameter (first in the
237
- * currying chain)
238
- * - The `expected` parameter must be assignable to `T | null | undefined`
239
- * - For objects, `expected` must have the same or subset of properties as
240
- * `actual`
241
- * - For union types like `string | null`, ensure proper type compatibility:
242
- *
243
- * ```typescript
244
- * const x: string | null;
245
- * TestValidator.notEquals("works")(x)(null); // ✅ Works: null is assignable to string | null
246
- * TestValidator.notEquals("error")(null)(x); // ❌ Error: x might be string, but expected is null
247
- * ```
248
- *
249
218
  * @example
250
219
  * ```typescript
251
220
  * // Basic inequality
252
- * TestValidator.notEquals("user should be different after update")(originalUser)(updatedUser);
221
+ * TestValidator.notEquals("user should be different after update", originalUser, updatedUser);
253
222
  *
254
223
  * // Ignore timestamps in comparison
255
- * TestValidator.notEquals("user data should differ", (key) => key === "updatedAt")(
256
- * originalUser
257
- * )(modifiedUser);
224
+ * TestValidator.notEquals("user data should differ", originalUser, modifiedUser,
225
+ * (key) => key === "updatedAt"
226
+ * );
258
227
  *
259
228
  * // Validate state changes
260
- * const validateStateChange = TestValidator.notEquals("state should have changed");
261
- * validateStateChange(initialState)(currentState);
229
+ * TestValidator.notEquals("state should have changed", initialState, currentState);
262
230
  *
263
231
  * // Type-safe nullable comparisons
264
232
  * const mutableData: { count: number } | null = getMutableData();
265
- * TestValidator.notEquals("should have changed")(mutableData)(null); // ✅ Safe
233
+ * TestValidator.notEquals("should have changed", mutableData, null);
266
234
  * ```;
267
235
  *
268
236
  * @param title - Descriptive title used in error messages when values are
269
237
  * equal
238
+ * @param x - The first value to compare
239
+ * @param y - The second value to compare (can be null or undefined)
270
240
  * @param exception - Optional filter function to exclude specific keys from
271
241
  * comparison
272
- * @returns A currying function chain: first accepts expected value, then
273
- * actual value
274
242
  * @throws Error when values are equal (indicating validation failure)
275
243
  */
276
- TestValidator.notEquals = function (title, exception) {
277
- if (exception === void 0) { exception = function () { return false; }; }
278
- return function (actual) {
279
- return function (expected) {
280
- var diff = (0, json_equal_to_1.json_equal_to)(exception)(actual)(expected);
281
- if (diff.length === 0)
282
- throw new Error([
283
- "Bug on ".concat(title, ": values should be different but are equal:"),
284
- "\n",
285
- JSON.stringify({ actual: actual, expected: expected }, null, 2),
286
- ].join("\n"));
287
- };
288
- };
289
- };
244
+ function notEquals(title, x, y, exception) {
245
+ var diff = (0, json_equal_to_1.json_equal_to)(exception !== null && exception !== void 0 ? exception : (function () { return false; }))(x)(y);
246
+ if (diff.length === 0)
247
+ throw new Error([
248
+ "Bug on ".concat(title, ": values should be different but are equal:"),
249
+ "\n",
250
+ JSON.stringify({ x: x, y: y }, null, 2),
251
+ ].join("\n"));
252
+ }
253
+ TestValidator.notEquals = notEquals;
290
254
  /**
291
255
  * Validates that a function throws an error or rejects when executed.
292
256
  *
@@ -297,43 +261,43 @@ var TestValidator;
297
261
  * @example
298
262
  * ```typescript
299
263
  * // Synchronous error validation
300
- * TestValidator.error("should reject invalid email")(
264
+ * TestValidator.error("should reject invalid email",
301
265
  * () => validateEmail("invalid-email")
302
266
  * );
303
267
  *
304
268
  * // Asynchronous error validation
305
- * await TestValidator.error("should reject unauthorized access")(
269
+ * await TestValidator.error("should reject unauthorized access",
306
270
  * async () => await api.functional.getSecretData()
307
271
  * );
308
272
  *
309
273
  * // Validate input validation
310
- * TestValidator.error("should throw on empty string")(
274
+ * TestValidator.error("should throw on empty string",
311
275
  * () => processRequiredInput("")
312
276
  * );
313
277
  * ```;
314
278
  *
315
279
  * @param title - Descriptive title used in error messages when no error
316
280
  * occurs
317
- * @returns A currying function that accepts the task function to validate
281
+ * @param task - The function that should throw an error or reject
282
+ * @returns Void or Promise<void> based on the input type
318
283
  * @throws Error when the task function does not throw an error or reject
319
284
  */
320
- TestValidator.error = function (title) {
321
- return function (task) {
322
- var message = function () { return "Bug on ".concat(title, ": exception must be thrown."); };
323
- try {
324
- var output_1 = task();
325
- if (is_promise(output_1))
326
- return new Promise(function (resolve, reject) {
327
- return output_1.catch(function () { return resolve(); }).then(function () { return reject(message()); });
328
- });
329
- else
330
- throw new Error(message());
331
- }
332
- catch (_a) {
333
- return undefined;
334
- }
335
- };
336
- };
285
+ function error(title, task) {
286
+ var message = function () { return "Bug on ".concat(title, ": exception must be thrown."); };
287
+ try {
288
+ var output_1 = task();
289
+ if (is_promise(output_1))
290
+ return new Promise(function (resolve, reject) {
291
+ return output_1.catch(function () { return resolve(); }).then(function () { return reject(message()); });
292
+ });
293
+ else
294
+ throw new Error(message());
295
+ }
296
+ catch (_a) {
297
+ return undefined;
298
+ }
299
+ }
300
+ TestValidator.error = error;
337
301
  /**
338
302
  * Validates that a function throws an HTTP error with specific status codes.
339
303
  *
@@ -344,90 +308,70 @@ var TestValidator;
344
308
  * @example
345
309
  * ```typescript
346
310
  * // Validate 401 Unauthorized
347
- * await TestValidator.httpError("should return 401 for invalid token")(401)(
311
+ * await TestValidator.httpError("should return 401 for invalid token", 401,
348
312
  * async () => await api.functional.getProtectedResource("invalid-token")
349
313
  * );
350
314
  *
351
315
  * // Validate multiple possible error codes
352
- * await TestValidator.httpError("should return client error")(400, 404, 422)(
316
+ * await TestValidator.httpError("should return client error", [400, 404, 422],
353
317
  * async () => await api.functional.updateNonexistentResource(data)
354
318
  * );
355
319
  *
356
320
  * // Validate server errors
357
- * TestValidator.httpError("should handle server errors")(500, 502, 503)(
321
+ * TestValidator.httpError("should handle server errors", [500, 502, 503],
358
322
  * () => callFaultyEndpoint()
359
323
  * );
360
324
  * ```;
361
325
  *
362
326
  * @param title - Descriptive title used in error messages
363
- * @returns A currying function that accepts status codes, then the task
364
- * function
327
+ * @param status - Expected status code(s), can be a single number or array
328
+ * @param task - The function that should throw an HttpError
329
+ * @returns Void or Promise<void> based on the input type
365
330
  * @throws Error when function doesn't throw HttpError or status code doesn't
366
331
  * match
367
332
  */
368
- TestValidator.httpError = function (title) {
369
- return function () {
370
- var statuses = [];
371
- for (var _i = 0; _i < arguments.length; _i++) {
372
- statuses[_i] = arguments[_i];
373
- }
374
- return function (task) {
375
- var message = function (actual) {
376
- return typeof actual === "number"
377
- ? "Bug on ".concat(title, ": status code must be ").concat(statuses.join(" or "), ", but ").concat(actual, ".")
378
- : "Bug on ".concat(title, ": status code must be ").concat(statuses.join(" or "), ", but succeeded.");
379
- };
380
- var predicate = function (exp) {
381
- return typeof exp === "object" &&
382
- exp.constructor.name === "HttpError" &&
383
- statuses.some(function (val) { return val === exp.status; })
384
- ? null
385
- : new Error(message(typeof exp === "object" && exp.constructor.name === "HttpError"
386
- ? exp.status
387
- : undefined));
388
- };
389
- try {
390
- var output_2 = task();
391
- if (is_promise(output_2))
392
- return new Promise(function (resolve, reject) {
393
- return output_2
394
- .catch(function (exp) {
395
- var res = predicate(exp);
396
- if (res)
397
- reject(res);
398
- else
399
- resolve();
400
- })
401
- .then(function () { return reject(new Error(message())); });
402
- });
403
- else
404
- throw new Error(message());
405
- }
406
- catch (exp) {
407
- var res = predicate(exp);
408
- if (res)
409
- throw res;
410
- return undefined;
411
- }
412
- };
333
+ function httpError(title, status, task) {
334
+ if (typeof status === "number")
335
+ status = [status];
336
+ var message = function (actual) {
337
+ return typeof actual === "number"
338
+ ? "Bug on ".concat(title, ": status code must be ").concat(status.join(" or "), ", but ").concat(actual, ".")
339
+ : "Bug on ".concat(title, ": status code must be ").concat(status.join(" or "), ", but succeeded.");
340
+ };
341
+ var predicate = function (exp) {
342
+ return typeof exp === "object" &&
343
+ exp.constructor.name === "HttpError" &&
344
+ status.some(function (val) { return val === exp.status; })
345
+ ? null
346
+ : new Error(message(typeof exp === "object" && exp.constructor.name === "HttpError"
347
+ ? exp.status
348
+ : undefined));
413
349
  };
414
- };
415
- function proceed(task) {
416
350
  try {
417
- var output_3 = task();
418
- if (is_promise(output_3))
419
- return new Promise(function (resolve) {
420
- return output_3
421
- .catch(function (exp) { return resolve(exp); })
422
- .then(function () { return resolve(null); });
351
+ var output_2 = task();
352
+ if (is_promise(output_2))
353
+ return new Promise(function (resolve, reject) {
354
+ return output_2
355
+ .catch(function (exp) {
356
+ var res = predicate(exp);
357
+ if (res)
358
+ reject(res);
359
+ else
360
+ resolve();
361
+ })
362
+ .then(function () { return reject(new Error(message())); });
423
363
  });
364
+ else
365
+ throw new Error(message());
424
366
  }
425
367
  catch (exp) {
426
- return exp;
368
+ var res = predicate(exp);
369
+ if (res)
370
+ throw res;
371
+ return undefined;
427
372
  }
428
- return null;
429
373
  }
430
- TestValidator.proceed = proceed;
374
+ TestValidator.httpError = httpError;
431
375
  /**
432
376
  * Validates pagination index API results against expected entity order.
433
377
  *
@@ -442,8 +386,7 @@ var TestValidator;
442
386
  * const expectedArticles = await db.articles.findAll({ order: 'created_at DESC' });
443
387
  * const actualArticles = await api.functional.getArticles({ page: 1, limit: 10 });
444
388
  *
445
- * TestValidator.index("article pagination order")(expectedArticles)(
446
- * actualArticles,
389
+ * TestValidator.index("article pagination order", expectedArticles, actualArticles,
447
390
  * true // enable trace logging
448
391
  * );
449
392
  *
@@ -451,37 +394,33 @@ var TestValidator;
451
394
  * const manuallyFilteredUsers = allUsers.filter(u => u.name.includes("John"));
452
395
  * const apiSearchResults = await api.functional.searchUsers({ query: "John" });
453
396
  *
454
- * TestValidator.index("user search results")(manuallyFilteredUsers)(
455
- * apiSearchResults
456
- * );
397
+ * TestValidator.index("user search results", manuallyFilteredUsers, apiSearchResults);
457
398
  * ```;
458
399
  *
459
400
  * @param title - Descriptive title used in error messages when order differs
460
- * @returns A currying function chain: expected entities, then actual entities
401
+ * @param expected - The expected entities in correct order
402
+ * @param gotten - The actual entities returned by the API
403
+ * @param trace - Optional flag to enable debug logging (default: false)
461
404
  * @throws Error when entity order differs between expected and actual results
462
405
  */
463
- TestValidator.index = function (title) {
464
- return function (expected) {
465
- return function (gotten, trace) {
466
- if (trace === void 0) { trace = false; }
467
- var length = Math.min(expected.length, gotten.length);
468
- expected = expected.slice(0, length);
469
- gotten = gotten.slice(0, length);
470
- var xIds = get_ids(expected).slice(0, length);
471
- var yIds = get_ids(gotten)
472
- .filter(function (id) { return id >= xIds[0]; })
473
- .slice(0, length);
474
- var equals = xIds.every(function (x, i) { return x === yIds[i]; });
475
- if (equals === true)
476
- return;
477
- else if (trace === true)
478
- console.log({
479
- expected: xIds,
480
- gotten: yIds,
481
- });
482
- throw new Error("Bug on ".concat(title, ": result of the index is different with manual aggregation."));
483
- };
484
- };
406
+ TestValidator.index = function (title, expected, gotten, trace) {
407
+ if (trace === void 0) { trace = false; }
408
+ var length = Math.min(expected.length, gotten.length);
409
+ expected = expected.slice(0, length);
410
+ gotten = gotten.slice(0, length);
411
+ var xIds = get_ids(expected).slice(0, length);
412
+ var yIds = get_ids(gotten)
413
+ .filter(function (id) { return id >= xIds[0]; })
414
+ .slice(0, length);
415
+ var equals = xIds.every(function (x, i) { return x === yIds[i]; });
416
+ if (equals === true)
417
+ return;
418
+ else if (trace === true)
419
+ console.log({
420
+ expected: xIds,
421
+ gotten: yIds,
422
+ });
423
+ throw new Error("Bug on ".concat(title, ": result of the index is different with manual aggregation."));
485
424
  };
486
425
  /**
487
426
  * Validates search functionality by testing API results against manual
@@ -494,97 +433,104 @@ var TestValidator;
494
433
  *
495
434
  * @example
496
435
  * ```typescript
497
- * // Test article search functionality
436
+ * // Test article search functionality with exact matching
498
437
  * const allArticles = await db.articles.findAll();
499
- * const searchValidator = TestValidator.search("article search API")(
500
- * (req) => api.searchArticles(req)
501
- * )(allArticles, 5); // test with 5 random samples
438
+ * const searchValidator = TestValidator.search(
439
+ * "article search API",
440
+ * (req) => api.searchArticles(req),
441
+ * allArticles,
442
+ * 5 // test with 5 random samples
443
+ * );
444
+ *
445
+ * // Test exact match search
446
+ * await searchValidator({
447
+ * fields: ["title"],
448
+ * values: (article) => [article.title], // full title for exact match
449
+ * filter: (article, [title]) => article.title === title, // exact match
450
+ * request: ([title]) => ({ search: { title } })
451
+ * });
502
452
  *
453
+ * // Test partial match search with includes
503
454
  * await searchValidator({
504
- * fields: ["title", "content"],
505
- * values: (article) => [article.title.split(" ")[0]], // first word
506
- * filter: (article, [keyword]) =>
507
- * article.title.includes(keyword) || article.content.includes(keyword),
455
+ * fields: ["content"],
456
+ * values: (article) => [article.content.substring(0, 20)], // partial content
457
+ * filter: (article, [keyword]) => article.content.includes(keyword),
508
458
  * request: ([keyword]) => ({ q: keyword })
509
459
  * });
510
460
  *
511
- * // Test user search with multiple criteria
512
- * await TestValidator.search("user search with filters")(
513
- * (req) => api.getUsers(req)
514
- * )(allUsers, 3)({
515
- * fields: ["status", "role"],
516
- * values: (user) => [user.status, user.role],
517
- * filter: (user, [status, role]) =>
518
- * user.status === status && user.role === role,
519
- * request: ([status, role]) => ({ status, role })
461
+ * // Test multi-field search with exact matching
462
+ * await searchValidator({
463
+ * fields: ["writer", "title"],
464
+ * values: (article) => [article.writer, article.title],
465
+ * filter: (article, [writer, title]) =>
466
+ * article.writer === writer && article.title === title,
467
+ * request: ([writer, title]) => ({ search: { writer, title } })
520
468
  * });
521
469
  * ```;
522
470
  *
523
471
  * @param title - Descriptive title used in error messages when search fails
524
- * @returns A currying function chain: API getter function, then dataset and
525
- * sample count
472
+ * @param getter - API function that performs the search
473
+ * @param total - Complete dataset to sample from for testing
474
+ * @param sampleCount - Number of random samples to test (default: 1)
475
+ * @returns A function that accepts search configuration properties
526
476
  * @throws Error when API search results don't match manual filtering results
527
477
  */
528
- TestValidator.search = function (title) {
529
- return function (getter) {
530
- return function (total, sampleCount) {
531
- if (sampleCount === void 0) { sampleCount = 1; }
532
- return function (props) { return __awaiter(_this, void 0, void 0, function () {
533
- var samples, _loop_1, samples_1, samples_1_1, s, e_1_1;
534
- var e_1, _a;
535
- return __generator(this, function (_b) {
536
- switch (_b.label) {
537
- case 0:
538
- samples = RandomGenerator_1.RandomGenerator.sample(total)(sampleCount);
539
- _loop_1 = function (s) {
540
- var values, filtered, gotten;
541
- return __generator(this, function (_c) {
542
- switch (_c.label) {
543
- case 0:
544
- values = props.values(s);
545
- filtered = total.filter(function (entity) {
546
- return props.filter(entity, values);
547
- });
548
- return [4 /*yield*/, getter(props.request(values))];
549
- case 1:
550
- gotten = _c.sent();
551
- TestValidator.index("".concat(title, " (").concat(props.fields.join(", "), ")"))(filtered)(gotten);
552
- return [2 /*return*/];
553
- }
554
- });
555
- };
556
- _b.label = 1;
557
- case 1:
558
- _b.trys.push([1, 6, 7, 8]);
559
- samples_1 = __values(samples), samples_1_1 = samples_1.next();
560
- _b.label = 2;
561
- case 2:
562
- if (!!samples_1_1.done) return [3 /*break*/, 5];
563
- s = samples_1_1.value;
564
- return [5 /*yield**/, _loop_1(s)];
565
- case 3:
566
- _b.sent();
567
- _b.label = 4;
568
- case 4:
569
- samples_1_1 = samples_1.next();
570
- return [3 /*break*/, 2];
571
- case 5: return [3 /*break*/, 8];
572
- case 6:
573
- e_1_1 = _b.sent();
574
- e_1 = { error: e_1_1 };
575
- return [3 /*break*/, 8];
576
- case 7:
577
- try {
578
- if (samples_1_1 && !samples_1_1.done && (_a = samples_1.return)) _a.call(samples_1);
478
+ TestValidator.search = function (title, getter, total, sampleCount) {
479
+ if (sampleCount === void 0) { sampleCount = 1; }
480
+ return function (props) { return __awaiter(_this, void 0, void 0, function () {
481
+ var samples, _loop_1, samples_1, samples_1_1, s, e_1_1;
482
+ var e_1, _a;
483
+ return __generator(this, function (_b) {
484
+ switch (_b.label) {
485
+ case 0:
486
+ samples = RandomGenerator_1.RandomGenerator.sample(total, sampleCount);
487
+ _loop_1 = function (s) {
488
+ var values, filtered, gotten;
489
+ return __generator(this, function (_c) {
490
+ switch (_c.label) {
491
+ case 0:
492
+ values = props.values(s);
493
+ filtered = total.filter(function (entity) {
494
+ return props.filter(entity, values);
495
+ });
496
+ return [4 /*yield*/, getter(props.request(values))];
497
+ case 1:
498
+ gotten = _c.sent();
499
+ TestValidator.index("".concat(title, " (").concat(props.fields.join(", "), ")"), filtered, gotten);
500
+ return [2 /*return*/];
579
501
  }
580
- finally { if (e_1) throw e_1.error; }
581
- return [7 /*endfinally*/];
582
- case 8: return [2 /*return*/];
502
+ });
503
+ };
504
+ _b.label = 1;
505
+ case 1:
506
+ _b.trys.push([1, 6, 7, 8]);
507
+ samples_1 = __values(samples), samples_1_1 = samples_1.next();
508
+ _b.label = 2;
509
+ case 2:
510
+ if (!!samples_1_1.done) return [3 /*break*/, 5];
511
+ s = samples_1_1.value;
512
+ return [5 /*yield**/, _loop_1(s)];
513
+ case 3:
514
+ _b.sent();
515
+ _b.label = 4;
516
+ case 4:
517
+ samples_1_1 = samples_1.next();
518
+ return [3 /*break*/, 2];
519
+ case 5: return [3 /*break*/, 8];
520
+ case 6:
521
+ e_1_1 = _b.sent();
522
+ e_1 = { error: e_1_1 };
523
+ return [3 /*break*/, 8];
524
+ case 7:
525
+ try {
526
+ if (samples_1_1 && !samples_1_1.done && (_a = samples_1.return)) _a.call(samples_1);
583
527
  }
584
- });
585
- }); };
586
- };
587
- };
528
+ finally { if (e_1) throw e_1.error; }
529
+ return [7 /*endfinally*/];
530
+ case 8: return [2 /*return*/];
531
+ }
532
+ });
533
+ }); };
588
534
  };
589
535
  /**
590
536
  * Validates sorting functionality of pagination APIs.
@@ -596,87 +542,98 @@ var TestValidator;
596
542
  *
597
543
  * @example
598
544
  * ```typescript
599
- * // Test single field sorting
600
- * const sortValidator = TestValidator.sort("article sorting")(
545
+ * // Test single field sorting with GaffComparator
546
+ * const sortValidator = TestValidator.sort(
547
+ * "article sorting",
601
548
  * (sortable) => api.getArticles({ sort: sortable })
602
549
  * )("created_at")(
603
- * (a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
550
+ * GaffComparator.dates((a) => a.created_at)
604
551
  * );
605
552
  *
606
553
  * await sortValidator("+"); // ascending
607
554
  * await sortValidator("-"); // descending
608
555
  *
609
- * // Test multi-field sorting with filtering
610
- * const userSortValidator = TestValidator.sort("user sorting")(
556
+ * // Test multi-field sorting with GaffComparator
557
+ * const userSortValidator = TestValidator.sort(
558
+ * "user sorting",
611
559
  * (sortable) => api.getUsers({ sort: sortable })
612
- * )("status", "created_at")(
613
- * (a, b) => {
614
- * if (a.status !== b.status) return a.status.localeCompare(b.status);
615
- * return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
616
- * },
560
+ * )("lastName", "firstName")(
561
+ * GaffComparator.strings((user) => [user.lastName, user.firstName]),
617
562
  * (user) => user.isActive // only test active users
618
563
  * );
619
564
  *
620
565
  * await userSortValidator("+", true); // ascending with trace logging
566
+ *
567
+ * // Custom comparator for complex logic
568
+ * const customSortValidator = TestValidator.sort(
569
+ * "custom sorting",
570
+ * (sortable) => api.getProducts({ sort: sortable })
571
+ * )("price", "rating")(
572
+ * (a, b) => {
573
+ * const priceDiff = a.price - b.price;
574
+ * return priceDiff !== 0 ? priceDiff : b.rating - a.rating; // price asc, rating desc
575
+ * }
576
+ * );
621
577
  * ```;
622
578
  *
623
579
  * @param title - Descriptive title used in error messages when sorting fails
624
- * @returns A currying function chain: API getter, field names, comparator,
625
- * then direction
580
+ * @param getter - API function that fetches sorted data
581
+ * @returns A currying function chain: field names, comparator, then direction
626
582
  * @throws Error when API results are not properly sorted according to
627
583
  * specification
628
584
  */
629
- TestValidator.sort = function (title) {
630
- return function (getter) {
631
- return function () {
632
- var fields = [];
633
- for (var _i = 0; _i < arguments.length; _i++) {
634
- fields[_i] = arguments[_i];
635
- }
636
- return function (comp, filter) {
637
- return function (direction_1) {
638
- var args_1 = [];
639
- for (var _i = 1; _i < arguments.length; _i++) {
640
- args_1[_i - 1] = arguments[_i];
641
- }
642
- return __awaiter(_this, __spreadArray([direction_1], __read(args_1), false), void 0, function (direction, trace) {
643
- var data, reversed;
644
- if (trace === void 0) { trace = false; }
645
- return __generator(this, function (_a) {
646
- switch (_a.label) {
647
- case 0: return [4 /*yield*/, getter(fields.map(function (field) { return "".concat(direction).concat(field); }))];
648
- case 1:
649
- data = _a.sent();
650
- if (filter)
651
- data = data.filter(filter);
652
- reversed = direction === "+" ? comp : function (x, y) { return comp(y, x); };
653
- if (is_sorted(data, reversed) === false) {
654
- if (fields.length === 1 &&
655
- data.length &&
656
- data[0][fields[0]] !== undefined &&
657
- trace)
658
- console.log(data.map(function (elem) { return elem[fields[0]]; }));
659
- throw new Error("Bug on ".concat(title, ": wrong sorting on ").concat(direction, "(").concat(fields.join(", "), ")."));
660
- }
661
- return [2 /*return*/];
662
- }
663
- });
585
+ TestValidator.sort = function (title, getter) {
586
+ return function () {
587
+ var fields = [];
588
+ for (var _i = 0; _i < arguments.length; _i++) {
589
+ fields[_i] = arguments[_i];
590
+ }
591
+ return function (comp, filter) {
592
+ return function (direction_1) {
593
+ var args_1 = [];
594
+ for (var _i = 1; _i < arguments.length; _i++) {
595
+ args_1[_i - 1] = arguments[_i];
596
+ }
597
+ return __awaiter(_this, __spreadArray([direction_1], __read(args_1), false), void 0, function (direction, trace) {
598
+ var data, reversed;
599
+ if (trace === void 0) { trace = false; }
600
+ return __generator(this, function (_a) {
601
+ switch (_a.label) {
602
+ case 0: return [4 /*yield*/, getter(fields.map(function (field) { return "".concat(direction).concat(field); }))];
603
+ case 1:
604
+ data = _a.sent();
605
+ if (filter)
606
+ data = data.filter(filter);
607
+ reversed = direction === "+" ? comp : function (x, y) { return comp(y, x); };
608
+ if (is_sorted(data, reversed) === false) {
609
+ if (fields.length === 1 &&
610
+ data.length &&
611
+ data[0][fields[0]] !== undefined &&
612
+ trace)
613
+ console.log(data.map(function (elem) { return elem[fields[0]]; }));
614
+ throw new Error("Bug on ".concat(title, ": wrong sorting on ").concat(direction, "(").concat(fields.join(", "), ")."));
615
+ }
616
+ return [2 /*return*/];
617
+ }
664
618
  });
665
- };
619
+ });
666
620
  };
667
621
  };
668
622
  };
669
623
  };
670
624
  })(TestValidator || (exports.TestValidator = TestValidator = {}));
625
+ /** @internal */
671
626
  function get_ids(entities) {
672
627
  return entities.map(function (entity) { return entity.id; }).sort(function (x, y) { return (x < y ? -1 : 1); });
673
628
  }
629
+ /** @internal */
674
630
  function is_promise(input) {
675
631
  return (typeof input === "object" &&
676
632
  input !== null &&
677
633
  typeof input.then === "function" &&
678
634
  typeof input.catch === "function");
679
635
  }
636
+ /** @internal */
680
637
  function is_sorted(data, comp) {
681
638
  for (var i = 1; i < data.length; ++i)
682
639
  if (comp(data[i - 1], data[i]) > 0)