velocious 1.0.652 → 1.0.654
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 +14 -1
- package/build/database/query/model-class-query.js +3 -1
- package/build/database/query/where-hash.js +12 -1
- package/build/database/query/where-in.js +48 -0
- package/build/database/query/where-model-class-hash.js +34 -1
- package/build/environment-handlers/node/cli/commands/test.js +2 -1
- package/build/src/database/query/model-class-query.js +5 -2
- package/build/src/database/query/where-hash.d.ts.map +1 -1
- package/build/src/database/query/where-hash.js +14 -2
- package/build/src/database/query/where-in.d.ts +22 -0
- package/build/src/database/query/where-in.d.ts.map +1 -0
- package/build/src/database/query/where-in.js +40 -0
- package/build/src/database/query/where-model-class-hash.d.ts +11 -0
- package/build/src/database/query/where-model-class-hash.d.ts.map +1 -1
- package/build/src/database/query/where-model-class-hash.js +33 -2
- package/build/src/environment-handlers/node/cli/commands/test.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/test.js +4 -2
- package/build/src/testing/test-runner.d.ts +23 -0
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +29 -2
- package/build/src/testing/velocious-runner-reporter.d.ts +7 -0
- package/build/src/testing/velocious-runner-reporter.d.ts.map +1 -1
- package/build/src/testing/velocious-runner-reporter.js +54 -12
- package/build/testing/test-runner.js +33 -1
- package/build/testing/velocious-runner-reporter.js +52 -11
- package/package.json +4 -4
- package/scripts/browser-test-session.js +21 -23
- package/src/database/query/model-class-query.js +3 -1
- package/src/database/query/where-hash.js +12 -1
- package/src/database/query/where-in.js +48 -0
- package/src/database/query/where-model-class-hash.js +34 -1
- package/src/environment-handlers/node/cli/commands/test.js +2 -1
- package/src/testing/test-runner.js +33 -1
- package/src/testing/velocious-runner-reporter.js +52 -11
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/** @typedef {string | number | boolean | null} InValue */
|
|
4
|
+
|
|
5
|
+
export default class WhereIn {
|
|
6
|
+
/**
|
|
7
|
+
* Validates an explicit membership descriptor at a column boundary.
|
|
8
|
+
* @param {unknown} condition - Untrusted column condition, narrowed before use.
|
|
9
|
+
* @returns {InValue[]} - Validated members in a new array.
|
|
10
|
+
*/
|
|
11
|
+
static values(condition) {
|
|
12
|
+
if (condition === null || typeof condition !== "object" ||
|
|
13
|
+
!Object.hasOwn(condition, "in") || !("in" in condition) ||
|
|
14
|
+
Reflect.ownKeys(condition).length !== 1 || !Array.isArray(condition.in)) {
|
|
15
|
+
throw new Error("Invalid IN condition: expected an object with only an own 'in' array")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** @type {InValue[]} */
|
|
19
|
+
const values = []
|
|
20
|
+
|
|
21
|
+
for (const value of condition.in) {
|
|
22
|
+
if (value !== null && typeof value !== "string" && typeof value !== "boolean" &&
|
|
23
|
+
!(typeof value === "number" && Number.isFinite(value))) {
|
|
24
|
+
throw new Error("Invalid IN condition: members must be strings, finite numbers, booleans or null")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
values.push(value)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return values
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Renders membership without letting its null branch escape sibling filters.
|
|
35
|
+
* @param {{columnSql: string, inColumnSql?: string, values: InValue[], options: import("../query-parser/options.js").default}} args - Quoted column operands, normalized members and driver quoting.
|
|
36
|
+
* @returns {string} - Complete membership predicate.
|
|
37
|
+
*/
|
|
38
|
+
static toSql({columnSql, inColumnSql = columnSql, values, options}) {
|
|
39
|
+
const nonNullValues = values.filter((value) => value !== null)
|
|
40
|
+
const includesNull = values.includes(null)
|
|
41
|
+
|
|
42
|
+
if (nonNullValues.length === 0) return includesNull ? `${columnSql} IS NULL` : "1=0"
|
|
43
|
+
|
|
44
|
+
const membershipSql = `${inColumnSql} IN (${nonNullValues.map((value) => options.quote(value)).join(", ")})`
|
|
45
|
+
|
|
46
|
+
return includesNull ? `(${membershipSql} OR ${columnSql} IS NULL)` : membershipSql
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as inflection from "inflection"
|
|
4
4
|
import {isPlainObject} from "is-plain-object"
|
|
5
5
|
import WhereBase from "./where-base.js"
|
|
6
|
+
import WhereIn from "./where-in.js"
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* No match.
|
|
@@ -378,6 +379,34 @@ export default class VelociousDatabaseQueryWhereModelClassHash extends WhereBase
|
|
|
378
379
|
return normalized
|
|
379
380
|
}
|
|
380
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Normalizes explicit membership through the model's column metadata.
|
|
384
|
+
* @param {{modelClass: typeof import("../record/index.js").default, columnName: string, tableName?: string, condition: unknown}} args - Resolved column and untrusted membership descriptor.
|
|
385
|
+
* @returns {string} - Complete column membership predicate.
|
|
386
|
+
*/
|
|
387
|
+
_whereSQLFromInCondition({modelClass, columnName, tableName, condition}) {
|
|
388
|
+
const options = this.getOptions()
|
|
389
|
+
const values = WhereIn.values(condition)
|
|
390
|
+
const normalizedValues = this._normalizeSqliteBooleanValue({columnName, modelClass, value: values})
|
|
391
|
+
/** @type {import("./where-in.js").InValue[] | typeof NO_MATCH} */
|
|
392
|
+
const typedValues = this._normalizeValueForColumnType({columnName, modelClass, value: normalizedValues})
|
|
393
|
+
|
|
394
|
+
if (typedValues === NO_MATCH) return "1=0"
|
|
395
|
+
|
|
396
|
+
const columnSql = tableName
|
|
397
|
+
? `${options.quoteTableName(tableName)}.${options.quoteColumnName(columnName)}`
|
|
398
|
+
: options.quoteColumnName(columnName)
|
|
399
|
+
const columnType = modelClass.getColumnTypeByName(columnName)
|
|
400
|
+
const castText = this.getQuery().driver.getType() === "mssql" && columnType?.toLowerCase() === "text"
|
|
401
|
+
|
|
402
|
+
return WhereIn.toSql({
|
|
403
|
+
columnSql,
|
|
404
|
+
inColumnSql: castText ? `CAST(${columnSql} AS NVARCHAR(MAX))` : columnSql,
|
|
405
|
+
options,
|
|
406
|
+
values: typedValues
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
|
|
381
410
|
/**
|
|
382
411
|
* Runs where sqlfrom hash.
|
|
383
412
|
* @param {WhereHash} hash - Hash.
|
|
@@ -400,7 +429,11 @@ export default class VelociousDatabaseQueryWhereModelClassHash extends WhereBase
|
|
|
400
429
|
: null
|
|
401
430
|
const resolvedColumnName = this._resolveColumnName(modelClass, whereKey)
|
|
402
431
|
|
|
403
|
-
if (relationship &&
|
|
432
|
+
if (resolvedColumnName && !relationship && isPlainObject(whereValue)) {
|
|
433
|
+
if (index > 0) sql += " AND "
|
|
434
|
+
|
|
435
|
+
sql += this._whereSQLFromInCondition({columnName: resolvedColumnName, condition: whereValue, modelClass, tableName})
|
|
436
|
+
} else if (relationship && tuples) {
|
|
404
437
|
if (index > 0) sql += " AND "
|
|
405
438
|
|
|
406
439
|
const rawTargetModelClass = relationship.getTargetModelClass()
|
|
@@ -203,7 +203,7 @@ export default class VelociousCliCommandsTest extends BaseCommand {
|
|
|
203
203
|
const hasExampleFilters = examplePatterns.length > 0
|
|
204
204
|
const hasTagFilters = includeTags.length > 0 || effectiveExcludeTagCount > 0
|
|
205
205
|
|
|
206
|
-
if ((hasTagFilters || hasLineFilters || hasExampleFilters) &&
|
|
206
|
+
if ((hasTagFilters || hasLineFilters || hasExampleFilters) && testRunner.hasNoMatches()) {
|
|
207
207
|
console.error(picocolors.red("\nNo tests matched the provided filters"))
|
|
208
208
|
await finalizeProfile("no-tests")
|
|
209
209
|
process.exit(1)
|
|
@@ -247,6 +247,7 @@ export default class VelociousCliCommandsTest extends BaseCommand {
|
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
if (testRunner.getNotRunTests() > 0) console.error(`${testRunner.getNotRunTests()} tests not run because a shared resource failed`)
|
|
250
251
|
console.error(picocolors.red(`\nTest run failed with ${testRunner.getFailedTests()} failed tests and ${testRunner.getSuccessfulTests()} successfull`))
|
|
251
252
|
await finalizeProfile("failed")
|
|
252
253
|
process.exit(1)
|
|
@@ -142,6 +142,8 @@ import VelociousTestArguments from "./velocious-test-arguments.js"
|
|
|
142
142
|
* @property {import("../database/pool/base.js").TestSharedConnectionRegistration | undefined} sharedRegistration - Physical-key shared registration once published.
|
|
143
143
|
*/
|
|
144
144
|
|
|
145
|
+
const testingPackageDirectory = path.dirname(fileURLToPath(import.meta.resolve("@velocious/testing/package.json")))
|
|
146
|
+
|
|
145
147
|
/**
|
|
146
148
|
* Runs to file slug.
|
|
147
149
|
* @param {string} value - Value to sanitize.
|
|
@@ -197,6 +199,8 @@ export default class TestRunner {
|
|
|
197
199
|
this._successfulTests = 0
|
|
198
200
|
this._testsCount = 0
|
|
199
201
|
this._failedTestDetails = []
|
|
202
|
+
/** @type {import("@velocious/testing/runner").NonRunTestResult[]} */
|
|
203
|
+
this._notRunTestDetails = []
|
|
200
204
|
/** @type {{fullDescription: string, filePath: string, line: number} | null} */
|
|
201
205
|
this._lastTestContext = null
|
|
202
206
|
/** @type {Array<{fullDescription: string, filePath: string, line: number, durationMs: number}>} */
|
|
@@ -1007,6 +1011,25 @@ export default class TestRunner {
|
|
|
1007
1011
|
return this._failedTests
|
|
1008
1012
|
}
|
|
1009
1013
|
|
|
1014
|
+
/**
|
|
1015
|
+
* Counts selected tests blocked by a terminal resource.
|
|
1016
|
+
* @returns {number} - Selected tests not executed because a shared resource failed.
|
|
1017
|
+
*/
|
|
1018
|
+
getNotRunTests() { return this._notRunTestDetails.length }
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Returns runtime non-run attribution.
|
|
1022
|
+
* @returns {import("@velocious/testing/runner").NonRunTestResult[]} - Runtime non-run details with the originating failure.
|
|
1023
|
+
*/
|
|
1024
|
+
getNotRunTestDetails() { return this._notRunTestDetails }
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Records a selected test that did not execute.
|
|
1028
|
+
* @param {import("@velocious/testing/runner").NonRunTestResult} result - Terminal-resource non-run record.
|
|
1029
|
+
* @returns {void}
|
|
1030
|
+
*/
|
|
1031
|
+
recordNotRunTest(result) { this._notRunTestDetails.push(result) }
|
|
1032
|
+
|
|
1010
1033
|
/**
|
|
1011
1034
|
* Runs get failed test details.
|
|
1012
1035
|
* @returns {FailedTestDetail[]} - Failed test details.
|
|
@@ -1087,6 +1110,14 @@ export default class TestRunner {
|
|
|
1087
1110
|
return this._packageResult?.tests.length ?? this._testDurations.length
|
|
1088
1111
|
}
|
|
1089
1112
|
|
|
1113
|
+
/**
|
|
1114
|
+
* Distinguishes an empty selection from a failure before selected cases execute.
|
|
1115
|
+
* @returns {boolean} - Whether selection matched no declarations.
|
|
1116
|
+
*/
|
|
1117
|
+
hasNoMatches() {
|
|
1118
|
+
return this._packageResult?.noMatches === true
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1090
1121
|
/**
|
|
1091
1122
|
* Returns the tests recorded during the run, slowest first.
|
|
1092
1123
|
* @param {number} [limit] - Maximum number of tests to return (0 returns all).
|
|
@@ -1105,6 +1136,7 @@ export default class TestRunner {
|
|
|
1105
1136
|
async prepare() {
|
|
1106
1137
|
this.anyTestsFocussed = false
|
|
1107
1138
|
this._failedTests = 0
|
|
1139
|
+
this._notRunTestDetails = []
|
|
1108
1140
|
this._successfulTests = 0
|
|
1109
1141
|
this._testsCount = 0
|
|
1110
1142
|
this._abortRemainingTests = false
|
|
@@ -1177,7 +1209,7 @@ export default class TestRunner {
|
|
|
1177
1209
|
|
|
1178
1210
|
if (portablePath.endsWith("/src/testing/test-runner.js")) continue
|
|
1179
1211
|
if (portablePath.endsWith("/src/testing/test.js")) continue
|
|
1180
|
-
if (
|
|
1212
|
+
if (resolvedFilePath.startsWith(`${testingPackageDirectory}${path.sep}`)) continue
|
|
1181
1213
|
|
|
1182
1214
|
return {filePath: resolvedFilePath, line: Number(match[2])}
|
|
1183
1215
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import { addTrackedStackToError } from "../utils/with-tracked-stack.js"
|
|
4
|
-
import BacktraceCleaner from "../utils/backtrace-cleaner-node.js"
|
|
5
4
|
import picocolors from "picocolors"
|
|
6
5
|
import restArgsError from "../utils/rest-args-error.js"
|
|
7
6
|
import { testEvents } from "./test.js"
|
|
@@ -21,6 +20,8 @@ function errorFromPackageRecord(errorRecord) {
|
|
|
21
20
|
|
|
22
21
|
error.name = errorRecord.name
|
|
23
22
|
if (errorRecord.stack) error.stack = errorRecord.stack
|
|
23
|
+
if (errorRecord.cause) error.cause = errorFromPackageRecord(errorRecord.cause)
|
|
24
|
+
if (errorRecord.terminalResource) Object.assign(error, {terminalResource: errorRecord.terminalResource})
|
|
24
25
|
|
|
25
26
|
return error
|
|
26
27
|
}
|
|
@@ -68,7 +69,27 @@ export default class VelociousRunnerReporter {
|
|
|
68
69
|
return
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
if (event.type === "run
|
|
72
|
+
if (event.type === "test:not-run") {
|
|
73
|
+
const test = this.testRunner.findTestDeclaration(event.test.fullName)
|
|
74
|
+
if (!test) throw new Error(`Package not-run result did not match a declaration: ${event.test.fullName}`)
|
|
75
|
+
this.testRunner.recordNotRunTest(event.test)
|
|
76
|
+
this.testRunner.completeTestDeclaration(test)
|
|
77
|
+
console.error(`Not run: ${event.test.fullName} (terminal resource failure in ${event.test.reason.fullName})`)
|
|
78
|
+
await this.emitEvent("testNotRun", {
|
|
79
|
+
configuration: this.testRunner.getConfiguration(),
|
|
80
|
+
test: event.test,
|
|
81
|
+
testRunner: this.testRunner
|
|
82
|
+
})
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (event.type === "run:finish") {
|
|
87
|
+
this.testRunner.recordPackageResult(event.result)
|
|
88
|
+
for (const failure of event.result.errors) {
|
|
89
|
+
console.error(`Suite ${failure.phase} failed: ${failure.suite}`)
|
|
90
|
+
this.printErrorCauses(errorFromPackageRecord(failure.error))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
72
93
|
}
|
|
73
94
|
|
|
74
95
|
/**
|
|
@@ -95,7 +116,7 @@ export default class VelociousRunnerReporter {
|
|
|
95
116
|
const failed = outcome?.failed ?? Boolean(attempt.error)
|
|
96
117
|
const error = outcome?.error
|
|
97
118
|
const retriesUsed = Math.min(attempt.attemptNumber, retryCount)
|
|
98
|
-
const willRetry = failed && !outcome?.abortRemainingTests && attempt.attemptNumber <= retryCount
|
|
119
|
+
const willRetry = failed && !event.terminalFailure && !outcome?.abortRemainingTests && attempt.attemptNumber <= retryCount
|
|
99
120
|
const {descriptions, testDescription} = this.testRunner.testMetadata(test)
|
|
100
121
|
const compatibility = this.testRunner.testData(test)
|
|
101
122
|
|
|
@@ -245,15 +266,8 @@ export default class VelociousRunnerReporter {
|
|
|
245
266
|
|
|
246
267
|
if (error instanceof Error) {
|
|
247
268
|
console.error(picocolors.red(`${leftPadding} Test failed: ${error.message}`))
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const backtraceCleaner = new BacktraceCleaner(error)
|
|
251
|
-
const cleanedStack = backtraceCleaner.getCleanedStack()
|
|
252
|
-
const stackLines = cleanedStack?.split("\n")
|
|
269
|
+
this.printErrorCauses(error)
|
|
253
270
|
|
|
254
|
-
if (stackLines) {
|
|
255
|
-
for (const stackLine of stackLines) console.error(picocolors.red(`${leftPadding} ${stackLine}`))
|
|
256
|
-
}
|
|
257
271
|
} else {
|
|
258
272
|
console.error(picocolors.red(`${leftPadding} Test failed with a ${typeof error}: ${String(error)}`))
|
|
259
273
|
}
|
|
@@ -274,6 +288,33 @@ export default class VelociousRunnerReporter {
|
|
|
274
288
|
testRunner.printRerunCommand({descriptions, testDescription, testData, leftPadding})
|
|
275
289
|
}
|
|
276
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Prints complete primary and secondary stacks once, including cyclic cause graphs.
|
|
293
|
+
* @param {unknown} error - Thrown value at the reporting boundary.
|
|
294
|
+
* @param {Set<Error>} [reported] - Error identities already printed.
|
|
295
|
+
* @returns {void}
|
|
296
|
+
*/
|
|
297
|
+
printErrorCauses(error, reported = new Set()) {
|
|
298
|
+
if (!(error instanceof Error)) {
|
|
299
|
+
console.error(String(error))
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
if (reported.has(error)) return
|
|
303
|
+
reported.add(error)
|
|
304
|
+
addTrackedStackToError(error)
|
|
305
|
+
console.error(error.stack || `${error.name}: ${error.message}`)
|
|
306
|
+
if (error.cause !== undefined) {
|
|
307
|
+
console.error("Caused by:")
|
|
308
|
+
this.printErrorCauses(error.cause, reported)
|
|
309
|
+
}
|
|
310
|
+
if (error instanceof AggregateError) {
|
|
311
|
+
for (const secondary of error.errors) {
|
|
312
|
+
console.error("Related failure:")
|
|
313
|
+
this.printErrorCauses(secondary, reported)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
277
318
|
/**
|
|
278
319
|
* Emits one legacy event and awaits listeners in registration order.
|
|
279
320
|
* @param {string} eventName - Event name.
|