@nsshunt/ststestrunner 1.1.6 → 1.1.8
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 +3 -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.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import http from "node:http";
|
|
|
7
7
|
import https from "node:https";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { parentPort } from "node:worker_threads";
|
|
10
|
+
import { createClient } from "redis";
|
|
10
11
|
//#region \0rolldown/runtime.js
|
|
11
12
|
var __defProp = Object.defineProperty;
|
|
12
13
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -482,6 +483,38 @@ var TestCaseFhirBase = class {
|
|
|
482
483
|
this.#accesssToken = retVal;
|
|
483
484
|
return retVal;
|
|
484
485
|
};
|
|
486
|
+
GetPersonRecord_ZZ = async (prefix, index, version) => {
|
|
487
|
+
const { workerIndex, runnerIndex, runId } = this.#options;
|
|
488
|
+
const useId = `${prefix}-${index}`;
|
|
489
|
+
let firstName;
|
|
490
|
+
let lastName;
|
|
491
|
+
if (this.#options.useRedisForNames === true) {
|
|
492
|
+
const loaded = await (await this.runnerExecutionWorker.GetStore()).getRecord(runId, `${workerIndex}_${runnerIndex}_${this.runner.iteration}`);
|
|
493
|
+
firstName = loaded.givenName;
|
|
494
|
+
lastName = loaded.familyName;
|
|
495
|
+
} else {
|
|
496
|
+
const name = this.runnerExecutionWorker.GetFakerName(this.runner.iteration);
|
|
497
|
+
firstName = name.firstName;
|
|
498
|
+
lastName = name.lastName;
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
id: useId,
|
|
502
|
+
resourceType: "Person",
|
|
503
|
+
name: [{
|
|
504
|
+
family: lastName,
|
|
505
|
+
given: [firstName],
|
|
506
|
+
use: `usual`
|
|
507
|
+
}],
|
|
508
|
+
text: {
|
|
509
|
+
div: `New Record ${this.runner.iteration}`,
|
|
510
|
+
status: "generated"
|
|
511
|
+
},
|
|
512
|
+
meta: {
|
|
513
|
+
versionId: version,
|
|
514
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
};
|
|
485
518
|
GetPersonRecord = async (prefix, index, version) => {
|
|
486
519
|
const useId = `${prefix}-${index}`;
|
|
487
520
|
let name;
|
|
@@ -35405,14 +35438,245 @@ var TestShutDown = class {
|
|
|
35405
35438
|
};
|
|
35406
35439
|
};
|
|
35407
35440
|
//#endregion
|
|
35441
|
+
//#region src/redisTestRunStore.ts
|
|
35442
|
+
var RedisTestRunStore = class {
|
|
35443
|
+
client;
|
|
35444
|
+
keyPrefix;
|
|
35445
|
+
defaultTtlSeconds;
|
|
35446
|
+
batchSize;
|
|
35447
|
+
maxDeleteBatchSize;
|
|
35448
|
+
connected = false;
|
|
35449
|
+
constructor(options) {
|
|
35450
|
+
this.options = options;
|
|
35451
|
+
this.keyPrefix = options.keyPrefix ?? "testrun";
|
|
35452
|
+
this.defaultTtlSeconds = options.defaultTtlSeconds ?? 3600;
|
|
35453
|
+
this.batchSize = options.batchSize ?? 1e3;
|
|
35454
|
+
this.maxDeleteBatchSize = options.maxDeleteBatchSize ?? 1e3;
|
|
35455
|
+
this.client = createClient({ url: options.redisUrl });
|
|
35456
|
+
this.client.on("error", (err) => {
|
|
35457
|
+
console.error("[RedisTestRunStore] Redis error:", err);
|
|
35458
|
+
});
|
|
35459
|
+
}
|
|
35460
|
+
async connect() {
|
|
35461
|
+
if (this.connected) return;
|
|
35462
|
+
await this.client.connect();
|
|
35463
|
+
this.connected = true;
|
|
35464
|
+
}
|
|
35465
|
+
async disconnect() {
|
|
35466
|
+
if (!this.connected) return;
|
|
35467
|
+
await this.client.quit();
|
|
35468
|
+
this.connected = false;
|
|
35469
|
+
}
|
|
35470
|
+
async ping() {
|
|
35471
|
+
await this.ensureConnected();
|
|
35472
|
+
return this.client.ping();
|
|
35473
|
+
}
|
|
35474
|
+
createRunId(prefix = "run") {
|
|
35475
|
+
return `${prefix}_${Date.now()}_${randomUUID()}`;
|
|
35476
|
+
}
|
|
35477
|
+
async seedRun(runId, records, options) {
|
|
35478
|
+
await this.ensureConnected();
|
|
35479
|
+
this.validateRunId(runId);
|
|
35480
|
+
if (!Array.isArray(records)) throw new Error("records must be an array");
|
|
35481
|
+
const ttlSeconds = options?.ttlSeconds ?? this.defaultTtlSeconds;
|
|
35482
|
+
const batchSize = options?.batchSize ?? this.batchSize;
|
|
35483
|
+
const storeMetadata = options?.storeMetadata ?? true;
|
|
35484
|
+
if (records.length === 0) {
|
|
35485
|
+
if (storeMetadata) await this.writeRunMetadata(runId, {
|
|
35486
|
+
runId,
|
|
35487
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35488
|
+
recordCount: 0,
|
|
35489
|
+
ttlSeconds
|
|
35490
|
+
}, ttlSeconds);
|
|
35491
|
+
return;
|
|
35492
|
+
}
|
|
35493
|
+
for (const record of records) this.validateRecord(record);
|
|
35494
|
+
const trackingKey = this.getTrackingKey(runId);
|
|
35495
|
+
const recordChunks = this.chunk(records, batchSize);
|
|
35496
|
+
for (const chunk of recordChunks) {
|
|
35497
|
+
const multi = this.client.multi();
|
|
35498
|
+
for (const record of chunk) {
|
|
35499
|
+
const recordKey = this.getRecordKey(runId, record.id);
|
|
35500
|
+
const flatHash = this.toRedisHash(record);
|
|
35501
|
+
multi.hSet(recordKey, flatHash);
|
|
35502
|
+
multi.sAdd(trackingKey, recordKey);
|
|
35503
|
+
multi.expire(recordKey, ttlSeconds);
|
|
35504
|
+
}
|
|
35505
|
+
multi.expire(trackingKey, ttlSeconds);
|
|
35506
|
+
await multi.exec();
|
|
35507
|
+
}
|
|
35508
|
+
if (storeMetadata) await this.writeRunMetadata(runId, {
|
|
35509
|
+
runId,
|
|
35510
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35511
|
+
recordCount: records.length,
|
|
35512
|
+
ttlSeconds
|
|
35513
|
+
}, ttlSeconds);
|
|
35514
|
+
}
|
|
35515
|
+
async getRecord(runId, recordId) {
|
|
35516
|
+
await this.ensureConnected();
|
|
35517
|
+
this.validateRunId(runId);
|
|
35518
|
+
this.validateRecordId(recordId);
|
|
35519
|
+
const recordKey = this.getRecordKey(runId, recordId);
|
|
35520
|
+
const data = await this.client.hGetAll(recordKey);
|
|
35521
|
+
return Object.keys(data).length === 0 ? null : data;
|
|
35522
|
+
}
|
|
35523
|
+
async hasRecord(runId, recordId) {
|
|
35524
|
+
await this.ensureConnected();
|
|
35525
|
+
this.validateRunId(runId);
|
|
35526
|
+
this.validateRecordId(recordId);
|
|
35527
|
+
const recordKey = this.getRecordKey(runId, recordId);
|
|
35528
|
+
return await this.client.exists(recordKey) === 1;
|
|
35529
|
+
}
|
|
35530
|
+
async deleteRecord(runId, recordId) {
|
|
35531
|
+
await this.ensureConnected();
|
|
35532
|
+
this.validateRunId(runId);
|
|
35533
|
+
this.validateRecordId(recordId);
|
|
35534
|
+
const recordKey = this.getRecordKey(runId, recordId);
|
|
35535
|
+
const trackingKey = this.getTrackingKey(runId);
|
|
35536
|
+
const multi = this.client.multi();
|
|
35537
|
+
multi.del(recordKey);
|
|
35538
|
+
multi.sRem(trackingKey, recordKey);
|
|
35539
|
+
const delResult = (await multi.exec())?.[0];
|
|
35540
|
+
return Number(delResult) > 0;
|
|
35541
|
+
}
|
|
35542
|
+
async countRunRecords(runId) {
|
|
35543
|
+
await this.ensureConnected();
|
|
35544
|
+
this.validateRunId(runId);
|
|
35545
|
+
const trackingKey = this.getTrackingKey(runId);
|
|
35546
|
+
return this.client.sCard(trackingKey);
|
|
35547
|
+
}
|
|
35548
|
+
async getRunMetadata(runId) {
|
|
35549
|
+
await this.ensureConnected();
|
|
35550
|
+
this.validateRunId(runId);
|
|
35551
|
+
const metadataKey = this.getMetadataKey(runId);
|
|
35552
|
+
const data = await this.client.hGetAll(metadataKey);
|
|
35553
|
+
return Object.keys(data).length === 0 ? null : data;
|
|
35554
|
+
}
|
|
35555
|
+
async cleanupRun(runId) {
|
|
35556
|
+
await this.ensureConnected();
|
|
35557
|
+
this.validateRunId(runId);
|
|
35558
|
+
const trackingKey = this.getTrackingKey(runId);
|
|
35559
|
+
const metadataKey = this.getMetadataKey(runId);
|
|
35560
|
+
const recordKeys = await this.client.sMembers(trackingKey);
|
|
35561
|
+
let deletedRecordKeys = 0;
|
|
35562
|
+
for (const chunk of this.chunk(recordKeys, this.maxDeleteBatchSize)) {
|
|
35563
|
+
if (chunk.length === 0) continue;
|
|
35564
|
+
deletedRecordKeys += await this.client.del(chunk);
|
|
35565
|
+
}
|
|
35566
|
+
const deletedTrackingKey = await this.client.del(trackingKey) > 0;
|
|
35567
|
+
const deletedMetadataKey = await this.client.del(metadataKey) > 0;
|
|
35568
|
+
return {
|
|
35569
|
+
runId,
|
|
35570
|
+
deletedRecordKeys,
|
|
35571
|
+
deletedTrackingKey,
|
|
35572
|
+
deletedMetadataKey
|
|
35573
|
+
};
|
|
35574
|
+
}
|
|
35575
|
+
async extendRunTtl(runId, ttlSeconds) {
|
|
35576
|
+
await this.ensureConnected();
|
|
35577
|
+
this.validateRunId(runId);
|
|
35578
|
+
const trackingKey = this.getTrackingKey(runId);
|
|
35579
|
+
const metadataKey = this.getMetadataKey(runId);
|
|
35580
|
+
const recordKeys = await this.client.sMembers(trackingKey);
|
|
35581
|
+
for (const chunk of this.chunk(recordKeys, this.batchSize)) {
|
|
35582
|
+
const multi = this.client.multi();
|
|
35583
|
+
for (const key of chunk) multi.expire(key, ttlSeconds);
|
|
35584
|
+
await multi.exec();
|
|
35585
|
+
}
|
|
35586
|
+
await this.client.expire(trackingKey, ttlSeconds);
|
|
35587
|
+
await this.client.expire(metadataKey, ttlSeconds);
|
|
35588
|
+
}
|
|
35589
|
+
async listRunRecordKeys(runId) {
|
|
35590
|
+
await this.ensureConnected();
|
|
35591
|
+
this.validateRunId(runId);
|
|
35592
|
+
return this.client.sMembers(this.getTrackingKey(runId));
|
|
35593
|
+
}
|
|
35594
|
+
async getRunTtl(runId) {
|
|
35595
|
+
await this.ensureConnected();
|
|
35596
|
+
this.validateRunId(runId);
|
|
35597
|
+
const trackingKey = this.getTrackingKey(runId);
|
|
35598
|
+
const metadataKey = this.getMetadataKey(runId);
|
|
35599
|
+
const [trackingKeyTtl, metadataKeyTtl] = await Promise.all([this.client.ttl(trackingKey), this.client.ttl(metadataKey)]);
|
|
35600
|
+
return {
|
|
35601
|
+
trackingKeyTtl,
|
|
35602
|
+
metadataKeyTtl
|
|
35603
|
+
};
|
|
35604
|
+
}
|
|
35605
|
+
async writeRunMetadata(runId, metadata, ttlSeconds) {
|
|
35606
|
+
const metadataKey = this.getMetadataKey(runId);
|
|
35607
|
+
const multi = this.client.multi();
|
|
35608
|
+
multi.hSet(metadataKey, {
|
|
35609
|
+
runId: metadata.runId,
|
|
35610
|
+
createdAt: metadata.createdAt,
|
|
35611
|
+
recordCount: String(metadata.recordCount),
|
|
35612
|
+
ttlSeconds: String(metadata.ttlSeconds)
|
|
35613
|
+
});
|
|
35614
|
+
multi.expire(metadataKey, ttlSeconds);
|
|
35615
|
+
await multi.exec();
|
|
35616
|
+
}
|
|
35617
|
+
getRecordKey(runId, recordId) {
|
|
35618
|
+
return `${this.keyPrefix}:${runId}:record:${recordId}`;
|
|
35619
|
+
}
|
|
35620
|
+
getTrackingKey(runId) {
|
|
35621
|
+
return `${this.keyPrefix}:${runId}:keys`;
|
|
35622
|
+
}
|
|
35623
|
+
getMetadataKey(runId) {
|
|
35624
|
+
return `${this.keyPrefix}:${runId}:meta`;
|
|
35625
|
+
}
|
|
35626
|
+
toRedisHash(record) {
|
|
35627
|
+
const out = {};
|
|
35628
|
+
for (const [key, value] of Object.entries(record)) {
|
|
35629
|
+
if (value === void 0) continue;
|
|
35630
|
+
out[key] = String(value);
|
|
35631
|
+
}
|
|
35632
|
+
return out;
|
|
35633
|
+
}
|
|
35634
|
+
chunk(items, size) {
|
|
35635
|
+
if (size <= 0) throw new Error("Chunk size must be > 0");
|
|
35636
|
+
const result = [];
|
|
35637
|
+
for (let i = 0; i < items.length; i += size) result.push(items.slice(i, i + size));
|
|
35638
|
+
return result;
|
|
35639
|
+
}
|
|
35640
|
+
validateRunId(runId) {
|
|
35641
|
+
if (!runId || typeof runId !== "string") throw new Error("runId must be a non-empty string");
|
|
35642
|
+
}
|
|
35643
|
+
validateRecordId(recordId) {
|
|
35644
|
+
if (!recordId || typeof recordId !== "string") throw new Error("recordId must be a non-empty string");
|
|
35645
|
+
}
|
|
35646
|
+
validateRecord(record) {
|
|
35647
|
+
if (!record || typeof record !== "object") throw new Error("record must be an object");
|
|
35648
|
+
if (!record.id || typeof record.id !== "string") throw new Error("record.id must be a non-empty string");
|
|
35649
|
+
if (typeof record.givenName !== "string") throw new Error(`record ${record.id}: givenName must be a string`);
|
|
35650
|
+
if (typeof record.familyName !== "string") throw new Error(`record ${record.id}: familyName must be a string`);
|
|
35651
|
+
if (typeof record.worker !== "number") throw new Error(`record ${record.id}: worker must be a number`);
|
|
35652
|
+
if (typeof record.runner !== "number") throw new Error(`record ${record.id}: runner must be a number`);
|
|
35653
|
+
if (typeof record.iteration !== "number") throw new Error(`record ${record.id}: iteration must be a number`);
|
|
35654
|
+
}
|
|
35655
|
+
async ensureConnected() {
|
|
35656
|
+
if (!this.connected) await this.connect();
|
|
35657
|
+
}
|
|
35658
|
+
};
|
|
35659
|
+
//#endregion
|
|
35408
35660
|
//#region src/workerTestCases.ts
|
|
35409
35661
|
var WorkerTestCases = class extends AbstractRunnerExecutionWorker {
|
|
35410
35662
|
#port;
|
|
35411
35663
|
#randomNameList = {};
|
|
35664
|
+
_store;
|
|
35412
35665
|
constructor() {
|
|
35413
35666
|
super();
|
|
35414
35667
|
f.seed(123);
|
|
35415
35668
|
}
|
|
35669
|
+
GetStore = async () => {
|
|
35670
|
+
if (!this._store) this._store = new RedisTestRunStore({
|
|
35671
|
+
redisUrl: this.Options.userData.redisStoreConnection,
|
|
35672
|
+
keyPrefix: "testrun",
|
|
35673
|
+
defaultTtlSeconds: 3600,
|
|
35674
|
+
batchSize: 1e3,
|
|
35675
|
+
maxDeleteBatchSize: 1e3
|
|
35676
|
+
});
|
|
35677
|
+
await this._store.ensureConnected();
|
|
35678
|
+
return this._store;
|
|
35679
|
+
};
|
|
35416
35680
|
GetFakerName = (index) => {
|
|
35417
35681
|
if (this.#randomNameList[index.toString()]) return this.#randomNameList[index.toString()];
|
|
35418
35682
|
const fakerNameTuple = {
|