@nsshunt/ststestrunner 1.1.6 → 1.1.7
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/dist/ststestrunner.cjs +264 -0
- package/dist/ststestrunner.cjs.map +1 -1
- package/dist/ststestrunner.mjs +264 -0
- package/dist/ststestrunner.mjs.map +1 -1
- package/package.json +2 -1
- package/types/commonTypes.d.ts +2 -0
- package/types/commonTypes.d.ts.map +1 -1
- package/types/redisTestRunStore.d.ts +71 -0
- package/types/redisTestRunStore.d.ts.map +1 -0
- package/types/testCaseFhirBase.d.ts +1 -0
- package/types/testCaseFhirBase.d.ts.map +1 -1
- package/types/workerTestCases.d.ts +3 -0
- package/types/workerTestCases.d.ts.map +1 -1
package/dist/ststestrunner.cjs
CHANGED
|
@@ -42,6 +42,7 @@ let node_https = require("node:https");
|
|
|
42
42
|
node_https = __toESM(node_https, 1);
|
|
43
43
|
let node_crypto = require("node:crypto");
|
|
44
44
|
let node_worker_threads = require("node:worker_threads");
|
|
45
|
+
let redis = require("redis");
|
|
45
46
|
//#region src/testCase01.ts
|
|
46
47
|
var TestCase01 = class {
|
|
47
48
|
#runner;
|
|
@@ -505,6 +506,38 @@ var TestCaseFhirBase = class {
|
|
|
505
506
|
this.#accesssToken = retVal;
|
|
506
507
|
return retVal;
|
|
507
508
|
};
|
|
509
|
+
GetPersonRecord_ZZ = async (prefix, index, version) => {
|
|
510
|
+
const { workerIndex, runnerIndex, runId } = this.#options;
|
|
511
|
+
const useId = `${prefix}-${index}`;
|
|
512
|
+
let firstName;
|
|
513
|
+
let lastName;
|
|
514
|
+
if (this.#options.useRedisForNames === true) {
|
|
515
|
+
const loaded = await (await this.runnerExecutionWorker.GetStore()).getRecord(runId, `${workerIndex}_${runnerIndex}_${this.runner.iteration}`);
|
|
516
|
+
firstName = loaded.givenName;
|
|
517
|
+
lastName = loaded.familyName;
|
|
518
|
+
} else {
|
|
519
|
+
const name = this.runnerExecutionWorker.GetFakerName(this.runner.iteration);
|
|
520
|
+
firstName = name.firstName;
|
|
521
|
+
lastName = name.lastName;
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
id: useId,
|
|
525
|
+
resourceType: "Person",
|
|
526
|
+
name: [{
|
|
527
|
+
family: lastName,
|
|
528
|
+
given: [firstName],
|
|
529
|
+
use: `usual`
|
|
530
|
+
}],
|
|
531
|
+
text: {
|
|
532
|
+
div: `New Record ${this.runner.iteration}`,
|
|
533
|
+
status: "generated"
|
|
534
|
+
},
|
|
535
|
+
meta: {
|
|
536
|
+
versionId: version,
|
|
537
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
};
|
|
508
541
|
GetPersonRecord = async (prefix, index, version) => {
|
|
509
542
|
const useId = `${prefix}-${index}`;
|
|
510
543
|
let name;
|
|
@@ -35428,14 +35461,245 @@ var TestShutDown = class {
|
|
|
35428
35461
|
};
|
|
35429
35462
|
};
|
|
35430
35463
|
//#endregion
|
|
35464
|
+
//#region src/redisTestRunStore.ts
|
|
35465
|
+
var RedisTestRunStore = class {
|
|
35466
|
+
client;
|
|
35467
|
+
keyPrefix;
|
|
35468
|
+
defaultTtlSeconds;
|
|
35469
|
+
batchSize;
|
|
35470
|
+
maxDeleteBatchSize;
|
|
35471
|
+
connected = false;
|
|
35472
|
+
constructor(options) {
|
|
35473
|
+
this.options = options;
|
|
35474
|
+
this.keyPrefix = options.keyPrefix ?? "testrun";
|
|
35475
|
+
this.defaultTtlSeconds = options.defaultTtlSeconds ?? 3600;
|
|
35476
|
+
this.batchSize = options.batchSize ?? 1e3;
|
|
35477
|
+
this.maxDeleteBatchSize = options.maxDeleteBatchSize ?? 1e3;
|
|
35478
|
+
this.client = (0, redis.createClient)({ url: options.redisUrl });
|
|
35479
|
+
this.client.on("error", (err) => {
|
|
35480
|
+
console.error("[RedisTestRunStore] Redis error:", err);
|
|
35481
|
+
});
|
|
35482
|
+
}
|
|
35483
|
+
async connect() {
|
|
35484
|
+
if (this.connected) return;
|
|
35485
|
+
await this.client.connect();
|
|
35486
|
+
this.connected = true;
|
|
35487
|
+
}
|
|
35488
|
+
async disconnect() {
|
|
35489
|
+
if (!this.connected) return;
|
|
35490
|
+
await this.client.quit();
|
|
35491
|
+
this.connected = false;
|
|
35492
|
+
}
|
|
35493
|
+
async ping() {
|
|
35494
|
+
await this.ensureConnected();
|
|
35495
|
+
return this.client.ping();
|
|
35496
|
+
}
|
|
35497
|
+
createRunId(prefix = "run") {
|
|
35498
|
+
return `${prefix}_${Date.now()}_${(0, node_crypto.randomUUID)()}`;
|
|
35499
|
+
}
|
|
35500
|
+
async seedRun(runId, records, options) {
|
|
35501
|
+
await this.ensureConnected();
|
|
35502
|
+
this.validateRunId(runId);
|
|
35503
|
+
if (!Array.isArray(records)) throw new Error("records must be an array");
|
|
35504
|
+
const ttlSeconds = options?.ttlSeconds ?? this.defaultTtlSeconds;
|
|
35505
|
+
const batchSize = options?.batchSize ?? this.batchSize;
|
|
35506
|
+
const storeMetadata = options?.storeMetadata ?? true;
|
|
35507
|
+
if (records.length === 0) {
|
|
35508
|
+
if (storeMetadata) await this.writeRunMetadata(runId, {
|
|
35509
|
+
runId,
|
|
35510
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35511
|
+
recordCount: 0,
|
|
35512
|
+
ttlSeconds
|
|
35513
|
+
}, ttlSeconds);
|
|
35514
|
+
return;
|
|
35515
|
+
}
|
|
35516
|
+
for (const record of records) this.validateRecord(record);
|
|
35517
|
+
const trackingKey = this.getTrackingKey(runId);
|
|
35518
|
+
const recordChunks = this.chunk(records, batchSize);
|
|
35519
|
+
for (const chunk of recordChunks) {
|
|
35520
|
+
const multi = this.client.multi();
|
|
35521
|
+
for (const record of chunk) {
|
|
35522
|
+
const recordKey = this.getRecordKey(runId, record.id);
|
|
35523
|
+
const flatHash = this.toRedisHash(record);
|
|
35524
|
+
multi.hSet(recordKey, flatHash);
|
|
35525
|
+
multi.sAdd(trackingKey, recordKey);
|
|
35526
|
+
multi.expire(recordKey, ttlSeconds);
|
|
35527
|
+
}
|
|
35528
|
+
multi.expire(trackingKey, ttlSeconds);
|
|
35529
|
+
await multi.exec();
|
|
35530
|
+
}
|
|
35531
|
+
if (storeMetadata) await this.writeRunMetadata(runId, {
|
|
35532
|
+
runId,
|
|
35533
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35534
|
+
recordCount: records.length,
|
|
35535
|
+
ttlSeconds
|
|
35536
|
+
}, ttlSeconds);
|
|
35537
|
+
}
|
|
35538
|
+
async getRecord(runId, recordId) {
|
|
35539
|
+
await this.ensureConnected();
|
|
35540
|
+
this.validateRunId(runId);
|
|
35541
|
+
this.validateRecordId(recordId);
|
|
35542
|
+
const recordKey = this.getRecordKey(runId, recordId);
|
|
35543
|
+
const data = await this.client.hGetAll(recordKey);
|
|
35544
|
+
return Object.keys(data).length === 0 ? null : data;
|
|
35545
|
+
}
|
|
35546
|
+
async hasRecord(runId, recordId) {
|
|
35547
|
+
await this.ensureConnected();
|
|
35548
|
+
this.validateRunId(runId);
|
|
35549
|
+
this.validateRecordId(recordId);
|
|
35550
|
+
const recordKey = this.getRecordKey(runId, recordId);
|
|
35551
|
+
return await this.client.exists(recordKey) === 1;
|
|
35552
|
+
}
|
|
35553
|
+
async deleteRecord(runId, recordId) {
|
|
35554
|
+
await this.ensureConnected();
|
|
35555
|
+
this.validateRunId(runId);
|
|
35556
|
+
this.validateRecordId(recordId);
|
|
35557
|
+
const recordKey = this.getRecordKey(runId, recordId);
|
|
35558
|
+
const trackingKey = this.getTrackingKey(runId);
|
|
35559
|
+
const multi = this.client.multi();
|
|
35560
|
+
multi.del(recordKey);
|
|
35561
|
+
multi.sRem(trackingKey, recordKey);
|
|
35562
|
+
const delResult = (await multi.exec())?.[0];
|
|
35563
|
+
return Number(delResult) > 0;
|
|
35564
|
+
}
|
|
35565
|
+
async countRunRecords(runId) {
|
|
35566
|
+
await this.ensureConnected();
|
|
35567
|
+
this.validateRunId(runId);
|
|
35568
|
+
const trackingKey = this.getTrackingKey(runId);
|
|
35569
|
+
return this.client.sCard(trackingKey);
|
|
35570
|
+
}
|
|
35571
|
+
async getRunMetadata(runId) {
|
|
35572
|
+
await this.ensureConnected();
|
|
35573
|
+
this.validateRunId(runId);
|
|
35574
|
+
const metadataKey = this.getMetadataKey(runId);
|
|
35575
|
+
const data = await this.client.hGetAll(metadataKey);
|
|
35576
|
+
return Object.keys(data).length === 0 ? null : data;
|
|
35577
|
+
}
|
|
35578
|
+
async cleanupRun(runId) {
|
|
35579
|
+
await this.ensureConnected();
|
|
35580
|
+
this.validateRunId(runId);
|
|
35581
|
+
const trackingKey = this.getTrackingKey(runId);
|
|
35582
|
+
const metadataKey = this.getMetadataKey(runId);
|
|
35583
|
+
const recordKeys = await this.client.sMembers(trackingKey);
|
|
35584
|
+
let deletedRecordKeys = 0;
|
|
35585
|
+
for (const chunk of this.chunk(recordKeys, this.maxDeleteBatchSize)) {
|
|
35586
|
+
if (chunk.length === 0) continue;
|
|
35587
|
+
deletedRecordKeys += await this.client.del(chunk);
|
|
35588
|
+
}
|
|
35589
|
+
const deletedTrackingKey = await this.client.del(trackingKey) > 0;
|
|
35590
|
+
const deletedMetadataKey = await this.client.del(metadataKey) > 0;
|
|
35591
|
+
return {
|
|
35592
|
+
runId,
|
|
35593
|
+
deletedRecordKeys,
|
|
35594
|
+
deletedTrackingKey,
|
|
35595
|
+
deletedMetadataKey
|
|
35596
|
+
};
|
|
35597
|
+
}
|
|
35598
|
+
async extendRunTtl(runId, ttlSeconds) {
|
|
35599
|
+
await this.ensureConnected();
|
|
35600
|
+
this.validateRunId(runId);
|
|
35601
|
+
const trackingKey = this.getTrackingKey(runId);
|
|
35602
|
+
const metadataKey = this.getMetadataKey(runId);
|
|
35603
|
+
const recordKeys = await this.client.sMembers(trackingKey);
|
|
35604
|
+
for (const chunk of this.chunk(recordKeys, this.batchSize)) {
|
|
35605
|
+
const multi = this.client.multi();
|
|
35606
|
+
for (const key of chunk) multi.expire(key, ttlSeconds);
|
|
35607
|
+
await multi.exec();
|
|
35608
|
+
}
|
|
35609
|
+
await this.client.expire(trackingKey, ttlSeconds);
|
|
35610
|
+
await this.client.expire(metadataKey, ttlSeconds);
|
|
35611
|
+
}
|
|
35612
|
+
async listRunRecordKeys(runId) {
|
|
35613
|
+
await this.ensureConnected();
|
|
35614
|
+
this.validateRunId(runId);
|
|
35615
|
+
return this.client.sMembers(this.getTrackingKey(runId));
|
|
35616
|
+
}
|
|
35617
|
+
async getRunTtl(runId) {
|
|
35618
|
+
await this.ensureConnected();
|
|
35619
|
+
this.validateRunId(runId);
|
|
35620
|
+
const trackingKey = this.getTrackingKey(runId);
|
|
35621
|
+
const metadataKey = this.getMetadataKey(runId);
|
|
35622
|
+
const [trackingKeyTtl, metadataKeyTtl] = await Promise.all([this.client.ttl(trackingKey), this.client.ttl(metadataKey)]);
|
|
35623
|
+
return {
|
|
35624
|
+
trackingKeyTtl,
|
|
35625
|
+
metadataKeyTtl
|
|
35626
|
+
};
|
|
35627
|
+
}
|
|
35628
|
+
async writeRunMetadata(runId, metadata, ttlSeconds) {
|
|
35629
|
+
const metadataKey = this.getMetadataKey(runId);
|
|
35630
|
+
const multi = this.client.multi();
|
|
35631
|
+
multi.hSet(metadataKey, {
|
|
35632
|
+
runId: metadata.runId,
|
|
35633
|
+
createdAt: metadata.createdAt,
|
|
35634
|
+
recordCount: String(metadata.recordCount),
|
|
35635
|
+
ttlSeconds: String(metadata.ttlSeconds)
|
|
35636
|
+
});
|
|
35637
|
+
multi.expire(metadataKey, ttlSeconds);
|
|
35638
|
+
await multi.exec();
|
|
35639
|
+
}
|
|
35640
|
+
getRecordKey(runId, recordId) {
|
|
35641
|
+
return `${this.keyPrefix}:${runId}:record:${recordId}`;
|
|
35642
|
+
}
|
|
35643
|
+
getTrackingKey(runId) {
|
|
35644
|
+
return `${this.keyPrefix}:${runId}:keys`;
|
|
35645
|
+
}
|
|
35646
|
+
getMetadataKey(runId) {
|
|
35647
|
+
return `${this.keyPrefix}:${runId}:meta`;
|
|
35648
|
+
}
|
|
35649
|
+
toRedisHash(record) {
|
|
35650
|
+
const out = {};
|
|
35651
|
+
for (const [key, value] of Object.entries(record)) {
|
|
35652
|
+
if (value === void 0) continue;
|
|
35653
|
+
out[key] = String(value);
|
|
35654
|
+
}
|
|
35655
|
+
return out;
|
|
35656
|
+
}
|
|
35657
|
+
chunk(items, size) {
|
|
35658
|
+
if (size <= 0) throw new Error("Chunk size must be > 0");
|
|
35659
|
+
const result = [];
|
|
35660
|
+
for (let i = 0; i < items.length; i += size) result.push(items.slice(i, i + size));
|
|
35661
|
+
return result;
|
|
35662
|
+
}
|
|
35663
|
+
validateRunId(runId) {
|
|
35664
|
+
if (!runId || typeof runId !== "string") throw new Error("runId must be a non-empty string");
|
|
35665
|
+
}
|
|
35666
|
+
validateRecordId(recordId) {
|
|
35667
|
+
if (!recordId || typeof recordId !== "string") throw new Error("recordId must be a non-empty string");
|
|
35668
|
+
}
|
|
35669
|
+
validateRecord(record) {
|
|
35670
|
+
if (!record || typeof record !== "object") throw new Error("record must be an object");
|
|
35671
|
+
if (!record.id || typeof record.id !== "string") throw new Error("record.id must be a non-empty string");
|
|
35672
|
+
if (typeof record.givenName !== "string") throw new Error(`record ${record.id}: givenName must be a string`);
|
|
35673
|
+
if (typeof record.familyName !== "string") throw new Error(`record ${record.id}: familyName must be a string`);
|
|
35674
|
+
if (typeof record.worker !== "number") throw new Error(`record ${record.id}: worker must be a number`);
|
|
35675
|
+
if (typeof record.runner !== "number") throw new Error(`record ${record.id}: runner must be a number`);
|
|
35676
|
+
if (typeof record.iteration !== "number") throw new Error(`record ${record.id}: iteration must be a number`);
|
|
35677
|
+
}
|
|
35678
|
+
async ensureConnected() {
|
|
35679
|
+
if (!this.connected) await this.connect();
|
|
35680
|
+
}
|
|
35681
|
+
};
|
|
35682
|
+
//#endregion
|
|
35431
35683
|
//#region src/workerTestCases.ts
|
|
35432
35684
|
var WorkerTestCases = class extends _nsshunt_stsrunnerframework.AbstractRunnerExecutionWorker {
|
|
35433
35685
|
#port;
|
|
35434
35686
|
#randomNameList = {};
|
|
35687
|
+
_store;
|
|
35435
35688
|
constructor() {
|
|
35436
35689
|
super();
|
|
35437
35690
|
f.seed(123);
|
|
35438
35691
|
}
|
|
35692
|
+
GetStore = async () => {
|
|
35693
|
+
if (!this._store) this._store = new RedisTestRunStore({
|
|
35694
|
+
redisUrl: this.Options.userData.redisStoreConnection,
|
|
35695
|
+
keyPrefix: "testrun",
|
|
35696
|
+
defaultTtlSeconds: 3600,
|
|
35697
|
+
batchSize: 1e3,
|
|
35698
|
+
maxDeleteBatchSize: 1e3
|
|
35699
|
+
});
|
|
35700
|
+
await this._store.ensureConnected();
|
|
35701
|
+
return this._store;
|
|
35702
|
+
};
|
|
35439
35703
|
GetFakerName = (index) => {
|
|
35440
35704
|
if (this.#randomNameList[index.toString()]) return this.#randomNameList[index.toString()];
|
|
35441
35705
|
const fakerNameTuple = {
|