From bf15f420edfd3392fb7d645996a9917575a0c391 Mon Sep 17 00:00:00 2001 From: David Meister Date: Mon, 15 Jun 2026 05:33:15 +0000 Subject: [PATCH 1/3] test: mutation-harden gas/index unit coverage Add discriminating tests to src/gas/index.test.ts that kill mutants surviving the existing GasManager suite: - Constructor default branches: assert class-field defaults (gasIncreasePointsPerStep=3, gasIncreaseStepTime=3_600_000, txTimeThreshold=30_000) and the maxGasPriceMultiplier = base + 50 fallback when optional config fields are omitted, plus a companion test pinning the provided-value assignment branches. - onTransactionMine boundary: a mine time exactly equal to the threshold must take the increase branch (>=, not >). - Deadline arithmetic: the deadline is Date.now() + gasIncreaseStepTime (not minus), bracketed against before/after timestamps. - Future-deadline guard: an under-threshold mine with a future deadline must not reset the multiplier. - watchGasPrice idempotency: a second call while already watching must early-return and keep the same interval handle (no leaked interval). Tests-only; src/ unchanged. Full vitest unit suite green (847 tests). Co-Authored-By: Claude Opus 4.8 --- src/gas/index.test.ts | 94 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/gas/index.test.ts b/src/gas/index.test.ts index 72612e8a..9077555d 100644 --- a/src/gas/index.test.ts +++ b/src/gas/index.test.ts @@ -118,5 +118,99 @@ describe("Test GasManager", () => { gasManager.unwatchGasPrice(); }); + + it("should not start a second watcher when already watching", () => { + // start watching once and capture the active interval handle + gasManager.watchGasPrice(); + expect(gasManager.isWatchingGasPrice).toBe(true); + const firstWatcher = (gasManager as any).gasPriceWatcher; + + // calling again while already watching must early-return and keep the + // same interval handle (guards against leaking a duplicate interval) + gasManager.watchGasPrice(); + expect((gasManager as any).gasPriceWatcher).toBe(firstWatcher); + + gasManager.unwatchGasPrice(); + expect(gasManager.isWatchingGasPrice).toBe(false); + }); + }); + + describe("Test constructor default values", () => { + it("should apply class defaults when optional config fields are omitted", () => { + // construct with only the required fields so every optional default branch runs + const manager = new GasManager({ + chainConfig: { id: 1, isSpecialL2: false } as any, + client: { name: "mockClient" } as any, + baseGasPriceMultiplier: 100, + } as any); + + // class field defaults + expect(manager.gasIncreasePointsPerStep).toBe(3); + expect(manager.gasIncreaseStepTime).toBe(60 * 60 * 1000); // 3_600_000 ms + expect(manager.txTimeThreshold).toBe(30_000); + + // maxGasPriceMultiplier defaults to base + 50 when not provided + expect(manager.maxGasPriceMultiplier).toBe(150); + + // multiplier starts at the base value + expect(manager.gasPriceMultiplier).toBe(100); + }); + + it("should use provided optional config values over the defaults", () => { + // provide values distinct from the class defaults to pin the assignment branches + const manager = new GasManager({ + chainConfig: { id: 1, isSpecialL2: false } as any, + client: { name: "mockClient" } as any, + baseGasPriceMultiplier: 100, + maxGasPriceMultiplier: 200, + gasIncreasePointsPerStep: 7, + gasIncreaseStepTime: 12_345, + txTimeThreshold: 9_999, + } as any); + + expect(manager.gasIncreasePointsPerStep).toBe(7); + expect(manager.gasIncreaseStepTime).toBe(12_345); + expect(manager.txTimeThreshold).toBe(9_999); + // provided value used, not base + 50 (which would be 150) + expect(manager.maxGasPriceMultiplier).toBe(200); + }); + }); + + describe("Test onTransactionMine boundary and arithmetic", () => { + it("should increase the multiplier when mine time equals the threshold exactly", () => { + // boundary: length === txTimeThreshold must take the increase branch (>=, not >) + gasManager.onTransactionMine({ + didMine: true, + length: 30_000, // exactly the threshold + }); + expect(gasManager.deadline).toBeDefined(); + expect(gasManager.gasPriceMultiplier).toBe(110); // 107 + 3 + }); + + it("should set the deadline to now plus the step time when increasing", () => { + const before = Date.now(); + gasManager.onTransactionMine({ + didMine: true, + length: 40_000, // over threshold + }); + const after = Date.now(); + // deadline is Date.now() + gasIncreaseStepTime (not minus); bracket it tightly + expect(gasManager.deadline).toBeGreaterThanOrEqual(before + config.gasIncreaseStepTime); + expect(gasManager.deadline).toBeLessThanOrEqual(after + config.gasIncreaseStepTime); + }); + + it("should not reset the multiplier when the deadline is still in the future", () => { + // deadline in the future and an elevated multiplier; an under-threshold mine + // must NOT reset because now < deadline + gasManager.deadline = Date.now() + 100_000; + gasManager.gasPriceMultiplier = 120; + const futureDeadline = gasManager.deadline; + gasManager.onTransactionMine({ + didMine: true, + length: 20_000, // under threshold + }); + expect(gasManager.gasPriceMultiplier).toBe(120); // unchanged + expect(gasManager.deadline).toBe(futureDeadline); // unchanged + }); }); }); From 4ce25025dac9128af4665a8eac0ac51497b98c0b Mon Sep 17 00:00:00 2001 From: David Meister Date: Mon, 15 Jun 2026 14:39:09 +0000 Subject: [PATCH 2/3] test: rewrite mutation-narrating comments to current-behavior Restate the added gas/index test comments as present-tense facts about what the code does, dropping mutant/process/coverage framing. Co-Authored-By: Claude Opus 4.8 --- src/gas/index.test.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/gas/index.test.ts b/src/gas/index.test.ts index 9077555d..67dc6893 100644 --- a/src/gas/index.test.ts +++ b/src/gas/index.test.ts @@ -120,13 +120,12 @@ describe("Test GasManager", () => { }); it("should not start a second watcher when already watching", () => { - // start watching once and capture the active interval handle + // the active interval handle while watching gasManager.watchGasPrice(); expect(gasManager.isWatchingGasPrice).toBe(true); const firstWatcher = (gasManager as any).gasPriceWatcher; - // calling again while already watching must early-return and keep the - // same interval handle (guards against leaking a duplicate interval) + // calling again while already watching keeps the same interval handle gasManager.watchGasPrice(); expect((gasManager as any).gasPriceWatcher).toBe(firstWatcher); @@ -137,7 +136,7 @@ describe("Test GasManager", () => { describe("Test constructor default values", () => { it("should apply class defaults when optional config fields are omitted", () => { - // construct with only the required fields so every optional default branch runs + // only the required fields are provided; optional fields take their defaults const manager = new GasManager({ chainConfig: { id: 1, isSpecialL2: false } as any, client: { name: "mockClient" } as any, @@ -157,7 +156,7 @@ describe("Test GasManager", () => { }); it("should use provided optional config values over the defaults", () => { - // provide values distinct from the class defaults to pin the assignment branches + // optional fields are provided with values distinct from the class defaults const manager = new GasManager({ chainConfig: { id: 1, isSpecialL2: false } as any, client: { name: "mockClient" } as any, @@ -171,14 +170,14 @@ describe("Test GasManager", () => { expect(manager.gasIncreasePointsPerStep).toBe(7); expect(manager.gasIncreaseStepTime).toBe(12_345); expect(manager.txTimeThreshold).toBe(9_999); - // provided value used, not base + 50 (which would be 150) + // the provided maxGasPriceMultiplier is used directly expect(manager.maxGasPriceMultiplier).toBe(200); }); }); describe("Test onTransactionMine boundary and arithmetic", () => { it("should increase the multiplier when mine time equals the threshold exactly", () => { - // boundary: length === txTimeThreshold must take the increase branch (>=, not >) + // a mine time equal to txTimeThreshold takes the increase branch gasManager.onTransactionMine({ didMine: true, length: 30_000, // exactly the threshold @@ -194,14 +193,14 @@ describe("Test GasManager", () => { length: 40_000, // over threshold }); const after = Date.now(); - // deadline is Date.now() + gasIncreaseStepTime (not minus); bracket it tightly + // deadline is Date.now() + gasIncreaseStepTime, bracketed tightly expect(gasManager.deadline).toBeGreaterThanOrEqual(before + config.gasIncreaseStepTime); expect(gasManager.deadline).toBeLessThanOrEqual(after + config.gasIncreaseStepTime); }); it("should not reset the multiplier when the deadline is still in the future", () => { - // deadline in the future and an elevated multiplier; an under-threshold mine - // must NOT reset because now < deadline + // with the deadline in the future, an under-threshold mine leaves the + // elevated multiplier in place because now < deadline gasManager.deadline = Date.now() + 100_000; gasManager.gasPriceMultiplier = 120; const futureDeadline = gasManager.deadline; From c9acc0a62fd7a51ecd513d964269f4851d367fa0 Mon Sep 17 00:00:00 2001 From: David Meister Date: Fri, 19 Jun 2026 17:33:10 +0000 Subject: [PATCH 3/3] merge(main): resolve conflicts [merge-update]