@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.
@@ -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 = {