Skip to content

Implements TestJam, Testing helpers and Testing docs#106

Merged
DrEverr merged 20 commits intomainfrom
maso-jam-test
Feb 19, 2026
Merged

Implements TestJam, Testing helpers and Testing docs#106
DrEverr merged 20 commits intomainfrom
maso-jam-test

Conversation

@DrEverr
Copy link
Copy Markdown
Member

@DrEverr DrEverr commented Feb 4, 2026

Resolves: #99

Implements testing framework for JAM service development.

Updated typeberry/lib

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

This PR introduces comprehensive testing infrastructure for JAM services, including a new TestJam helper class for simpler test setup, assertion utilities, automated test configuration generation, and corresponding documentation. Updates include refactored simulator APIs, expanded work-report types, and integration with the build command.

Changes

Cohort / File(s) Summary
Testing Documentation
docs/src/SUMMARY.md, docs/src/testing.md, bin/cli/README.md
Added comprehensive testing guide and documentation with setup instructions, common patterns, troubleshooting, and build system integration notes.
Testing Helpers & Assertions
packages/jammin-sdk/testing-helpers.ts, packages/jammin-sdk/testing-helpers.test.ts
New module exposing AccumulationAssertionError, StateChangeAssertionError, and helper functions (expectAccumulationSuccess, expectStateChange, expectServiceInfoChange) for test validation with re-exports of core testing APIs.
Test Configuration Generation
packages/jammin-sdk/utils/generate-test-config.ts, packages/jammin-sdk/utils/generate-test-config.test.ts
New utilities to auto-generate type-safe test configuration files with service mappings, chain spec, and pre-configured TestJam instance; integration point for build pipeline.
Core Simulator Refactor
packages/jammin-sdk/simulator.ts, packages/jammin-sdk/simulator.test.ts
Introduced TestJam class with fluent API (withOptions, withWorkReport, accumulate) replacing simulateAccumulation; added DebugLoggingOptions for granular debug control; expanded generateGuarantees return type and service inspection methods (getServiceInfo, getServiceStorage, getServicePreimage, getServicePreimageLookup).
SDK API Updates
packages/jammin-sdk/index.ts, packages/jammin-sdk/package.json
Removed old exports (bytes, state, genesis-state-generator, simulator, work-report, getSDKInfo); added testing-helpers export; bumped @typeberry/lib dependency to 0.5.7.
Work-Report Enhancements
packages/jammin-sdk/work-report.ts, packages/jammin-sdk/work-report.test.ts
Extended WorkResultStatus with digestTooBig and incorrectNumberOfExports variants; refactored to use public factory methods (WorkExecResult.ok, WorkExecResult.error); permissive handling of empty results arrays.
Build Integration
bin/cli/src/commands/build-command.ts
Added post-build step to generate test configuration via generateTestConfigInProjectDir with graceful error handling.
Import & Export Adjustments
packages/jammin-sdk/utils/generate-service-output.ts, packages/jammin-sdk/utils/genesis-state-generator.ts, packages/jammin-sdk/utils/genesis-state-generator.test.ts, packages/jammin-sdk/utils/index.ts
Updated import paths and type references (PreimageHash, service loading utilities); added new utils module exports (generate-test-config, genesis-state-generator).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • tomusdrw
  • skoszuta
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Multiple out-of-scope changes detected: removal of public exports (bytes, state, genesis-state-generator, simulator, work-report) from main SDK index; removal of getSDKInfo() function; dependency updates; API changes unrelated to testing helpers documentation. Review and justify removal of public exports and getSDKInfo(). Separate breaking API changes into distinct PRs or document migration path for consumers.
Docstring Coverage ⚠️ Warning Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main change: implementing TestJam, testing helpers, and testing documentation.
Description check ✅ Passed The PR description references issue #99 and explains it implements a testing framework with updated dependencies, which is related to the changeset.
Linked Issues check ✅ Passed All requirements from issue #99 are met: testing-helpers.ts created with expectAccumulationSuccess and expectStateChange helpers [#99], TestJam class added with comprehensive documentation [#99], docs/testing.md created with setup/patterns/troubleshooting [#99], and exports configured properly [#99].

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch maso-jam-test

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

# Conflicts:
#	packages/jammin-sdk/genesis-state-generator.test.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@docs/src/testing.md`:
- Around line 53-55: The docs reference two different config filenames
("jam.toml" and "jammin.build.yml") which is inconsistent; update all mentions
to use the canonical filename (choose one and apply consistently) — e.g., change
the setup example comment and any troubleshooting text that refers to
"jammin.build.yml" to the canonical name so the example with TestJam.create()
and all other occurrences (including lines around the cited blocks and lines
461-465) consistently reference the same filename.

In `@packages/jammin-sdk/index.ts`:
- Line 11: The public API change removed the top-level re-export "export * as
state from \"@typeberry/lib/state\"" (and replaced work-report exports by
re-exporting them via testing-helpers.js); confirm intent and either restore the
state re-export or document the breaking change: if you intended to keep the SDK
surface, re-add the top-level export (the missing symbol is the namespace export
"state" from "@typeberry/lib/state") so consumers can import sdk.state;
otherwise update the package release notes/CHANGELOG to call out removal of
"state" from the public API and ensure no internal imports rely on that export.
🧹 Nitpick comments (5)
packages/jammin-sdk/simulator.ts (2)

298-301: Consider merging options rather than replacing them.

The withOptions() method replaces all options. If users want to change only one option (e.g., just debug), they lose any previously set options. Consider merging instead:

♻️ Proposed fix to merge options
  withOptions(options: SimulatorOptions): this {
-   this.options = options;
+   this.options = { ...this.options, ...options };
    return this;
  }

343-358: Side effect: chainSpec is mutated on this.options.

When this.options.chainSpec is undefined, Line 353-354 mutates this.options by setting chainSpec = tinyChainSpec. This persists across future accumulation() calls, which may be unexpected since options are supposed to be explicitly set via withOptions().

Consider using a local variable instead:

♻️ Proposed fix to avoid mutating options
  async accumulation(): Promise<AccumulateResult> {
    const result = await simulateAccumulation(this.state, this.workReports, this.options);
    this.workReports = [];
    if (this.state instanceof InMemoryState) {
      this.state.applyUpdate(result);
    } else {
      if (!this.blake2b) {
        this.blake2b = await Blake2b.createHasher();
      }
-     if (!this.options.chainSpec) {
-       this.options.chainSpec = tinyChainSpec;
-     }
-     this.state.backend.applyUpdate(serializeStateUpdate(this.options.chainSpec, this.blake2b, result));
+     const chainSpec = this.options.chainSpec ?? tinyChainSpec;
+     this.state.backend.applyUpdate(serializeStateUpdate(chainSpec, this.blake2b, result));
    }
    return result;
  }
packages/jammin-sdk/testing-helpers.test.ts (2)

17-22: Consider using beforeEach to isolate test state.

The jam instance is created once in beforeAll and reused across all tests. Since accumulation() applies state updates to jam.state, later tests may be affected by state changes from earlier tests, potentially causing test interdependencies.

♻️ Proposed fix to isolate tests
  describe("testing-helpers", () => {
    let jam: TestJam;

-   beforeAll(() => {
+   beforeEach(() => {
      jam = TestJam.empty();
    });

96-103: Test may silently pass if no exception is thrown.

The test at Lines 96-103 uses try/catch to verify the error message, but if expectStateChange doesn't throw, the test passes without any assertions executing.

♻️ Proposed fix to ensure exception is thrown
    test("should include custom error message", () => {
+     let caught = false;
      try {
        expectStateChange(10, 5, (before, after) => after > before, "Custom error");
      } catch (e) {
+       caught = true;
        expect(e).toBeInstanceOf(StateChangeAssertionError);
        expect((e as Error).message).toBe("Custom error");
      }
+     expect(caught).toBe(true);
    });

Or use expect().toThrow() pattern consistently:

    test("should include custom error message", () => {
-     try {
-       expectStateChange(10, 5, (before, after) => after > before, "Custom error");
-     } catch (e) {
-       expect(e).toBeInstanceOf(StateChangeAssertionError);
-       expect((e as Error).message).toBe("Custom error");
-     }
+     expect(() => {
+       expectStateChange(10, 5, (before, after) => after > before, "Custom error");
+     }).toThrow("Custom error");
    });
packages/jammin-sdk/simulator.test.ts (1)

1-12: Isolate TestJam per test to avoid state leakage.

accumulation() mutates state and withOptions() can persist; reusing a single instance via beforeAll can make tests order-dependent. Prefer beforeEach (or instantiating inside each test) for isolation.

♻️ Suggested change
-import { beforeAll, describe, expect, test } from "bun:test";
+import { beforeEach, describe, expect, test } from "bun:test";
 import * as config from "@typeberry/lib/config";
 import { generateGuarantees, TestJam } from "./simulator.js";
 import { CoreId, Gas, ServiceId, Slot } from "./types.js";
 import { createWorkReportAsync } from "./work-report.js";

 describe("simulateAccumulation", () => {
   let jam: TestJam;

-  beforeAll(() => {
+  beforeEach(() => {
     jam = TestJam.empty();
   });

Comment thread docs/src/testing.md
Comment thread packages/jammin-sdk/index.ts
@DrEverr DrEverr requested review from skoszuta and tomusdrw February 4, 2026 18:41
@DrEverr DrEverr changed the title Implementa TestJam, Testing helpers and Testing docs Implements TestJam, Testing helpers and Testing docs Feb 4, 2026
Comment thread docs/src/testing.md Outdated
Comment thread docs/src/testing.md Outdated
Comment thread docs/src/testing.md Outdated
Comment thread docs/src/testing.md
Comment thread docs/src/testing.md Outdated
Comment thread docs/src/testing.md Outdated
Comment thread docs/src/testing.md Outdated
Comment thread docs/src/testing.md
Comment thread docs/src/testing.md
Comment thread packages/jammin-sdk/simulator.ts Outdated
…accumulation() to accumulate(), fine-grained logging control, and documentation updates

Addresses feedback from tomusdrw and skoszuta on PR 106:

Phase 1: Code Generation Enhancement
- Create generate-test-config.ts utility to generate TypeScript files with type-safe service ID mappings
- Enhance build-command to automatically generate config/jammin.test.config.ts with service details and chainspec
- Services now have a type-safe mapping instead of hardcoded values

Phase 2: API Refinement
- Rename accumulation() method to accumulate() for more concise verb-based API
- Update all tests and documentation to use new method name
- Improves code readability and aligns with reviewer preference

Phase 3: Debug Logging Enhancement
- Add DebugLoggingOptions interface for fine-grained control over logging categories
- Support both boolean (enable all) and object (selective) debug modes
- Available options:
  - pvmExecution: PVM execution details including instruction traces
  - ecalliTrace: Ecalli host call traces for service execution
  - hostCalls: Host calls during service execution
  - accumulate: Accumulation process details and state transitions
  - safrole: Randomness and validator selection operations
  - refine: Work report refinement and validation
  - stateTransitions: State transitions and root changes
- Backward compatible with existing debug: true usage

Phase 4: Documentation Updates
- Add prerequisite section about building services before testing
- Include custom assertions in basic test example
- Document fine-grained logging control with examples
- Update available options to reflect new debug capabilities
- Emphasize use of generated test config for type safety

All tests pass, quality checks pass, build succeeds.
…accumulation() to accumulate(), fine-grained logging control, and documentation updates

Addresses feedback from tomusdrw and skoszuta on PR 106:

Phase 1: Code Generation Enhancement
- Create generate-test-config.ts utility to generate TypeScript files with type-safe service ID mappings
- Enhance build-command to automatically generate config/jammin.test.config.ts with service details and chainspec
- Services now have a type-safe mapping instead of hardcoded values

Phase 2: API Refinement
- Rename accumulation() method to accumulate() for more concise verb-based API
- Update all tests and documentation to use new method name
- Improves code readability and aligns with reviewer preference

Phase 3: Debug Logging Enhancement
- Add DebugLoggingOptions interface for fine-grained control over logging categories
- Support both boolean (enable all) and object (selective) debug modes
- Available options:
  - pvmExecution: PVM execution details including instruction traces
  - ecalliTrace: Ecalli host call traces for service execution
  - hostCalls: Host calls during service execution
  - accumulate: Accumulation process details and state transitions
  - safrole: Randomness and validator selection operations
  - refine: Work report refinement and validation
  - stateTransitions: State transitions and root changes
- Backward compatible with existing debug: true usage

Phase 4: Documentation Updates
- Add prerequisite section about building services before testing
- Include custom assertions in basic test example
- Document fine-grained logging control with examples
- Update available options to reflect new debug capabilities
- Emphasize use of generated test config for type safety

All tests pass, quality checks pass, build succeeds.
Create convenient wrapper functions for common BytesBlob conversions:
- stringToBlob(): Convert UTF-8 strings to BytesBlob
- numbersToBlob(): Convert number arrays to BytesBlob
- hexToBlob(): Flexible hex parsing (with or without 0x prefix)
- hexToBlobWithPrefix(): Explicit wrapper for parseBlob()
- hexToBlobNoPrefix(): Explicit wrapper for parseBlobNoPrefix()

These wrappers provide a cleaner API and reduce boilerplate in tests and
service code. Export from jammin-sdk utils module for easy access.
@DrEverr
Copy link
Copy Markdown
Member Author

DrEverr commented Feb 10, 2026

I've implemented suggestions, but it's quite a change so maybe a re-review would be good.

@DrEverr DrEverr requested a review from tomusdrw February 10, 2026 16:27
Comment thread packages/jammin-sdk/simulator.ts Outdated
Comment thread packages/jammin-sdk/simulator.ts Outdated
Comment thread docs/src/testing.md
Comment thread packages/jammin-sdk/utils/blob-converters.ts Outdated
Comment thread packages/jammin-sdk/utils/generate-test-config.ts Outdated
Comment thread packages/jammin-sdk/utils/generate-test-config.ts Outdated
Comment thread packages/jammin-sdk/package.json Outdated
Comment thread packages/jammin-sdk/work-report.ts Outdated
DrEverr and others added 2 commits February 17, 2026 08:28
Co-authored-by: Tomek Drwięga <tomusdrw@users.noreply.github.com>
Co-authored-by: Tomek Drwięga <tomusdrw@users.noreply.github.com>
…or generated config, update typeberry/lib to 0.5.7, remove unnececerry workreport check
@DrEverr
Copy link
Copy Markdown
Member Author

DrEverr commented Feb 19, 2026

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 19, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (6)
bin/cli/src/commands/build-command.ts (1)

117-126: Variable services on line 119 shadows the outer services from line 98.

Both are named services but have different types (ServiceConfig[] vs ServiceBuildOutput[]). While functionally correct due to block scoping, it hinders readability. Consider renaming, e.g., builtServices or loadedServices.

Proposed rename
     try {
       s.start("Generating test configuration...");
-      const services = await loadServices(projectRoot);
-      await generateTestConfigInProjectDir(services, projectRoot);
+      const builtServices = await loadServices(projectRoot);
+      await generateTestConfigInProjectDir(builtServices, projectRoot);
       s.stop("✅ Test configuration generated");
       p.log.message("📝 Generated: config/jammin.test.config.ts");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/cli/src/commands/build-command.ts` around lines 117 - 126, The inner
variable named services (the one assigned from loadServices and passed to
generateTestConfigInProjectDir) shadows an outer services variable of a
different type (ServiceConfig[] vs ServiceBuildOutput[]), hurting readability;
rename the inner variable to a distinct name such as loadedServices or
testServices wherever it's declared and used (the assignment from loadServices
and the call to generateTestConfigInProjectDir) so references are unambiguous
and types no longer collide while leaving the outer services identifier
untouched.
packages/jammin-sdk/testing-helpers.ts (1)

117-129: Predicate returning undefined silently passes the assertion.

When StateChangePredicate returns undefined (e.g., due to a missing return statement or an incomplete conditional), expectStateChange silently succeeds because only === false triggers the error. This can mask bugs in test predicates. Consider treating any non-true return as a failure:

♻️ Suggested alternative
   const result = predicate(before, after);
 
-  // If predicate returns false explicitly, throw error
-  if (result === false) {
+  // If predicate does not return true, throw error
+  if (result !== true) {
     throw new StateChangeAssertionError(errorMessage ?? "State change validation failed", before, after);
   }

If the undefined-passes-silently behavior is intentional, document it explicitly in the JSDoc to avoid surprise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jammin-sdk/testing-helpers.ts` around lines 117 - 129, The current
expectStateChange function treats only an explicit false as failure, letting
undefined (e.g., missing return in StateChangePredicate) pass silently; update
the check in expectStateChange to fail on any non-true result (use result !==
true) and throw StateChangeAssertionError with the provided errorMessage or
default, so undefined/other falsy values trigger the assertion; if this behavior
was intentional instead, add JSDoc to expectStateChange and StateChangePredicate
documenting that only an explicit false fails and undefined passes.
packages/jammin-sdk/simulator.test.ts (1)

7-12: Shared mutable TestJam instance across tests risks order-dependent failures.

TestJam.empty() is created once in beforeAll, but accumulate() mutates the internal state (applies state updates) and withOptions() replaces this.options for subsequent calls. If test execution order changes or new tests are added, earlier tests' side effects (state mutations, lingering options) can silently affect later ones.

Consider using beforeEach instead so each test starts with a fresh instance:

♻️ Suggested fix
-  let jam: TestJam;
-
-  beforeAll(() => {
-    jam = TestJam.empty();
-  });
+  let jam: TestJam;
+
+  beforeEach(() => {
+    jam = TestJam.empty();
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jammin-sdk/simulator.test.ts` around lines 7 - 12, The tests use a
shared mutable TestJam instance created by TestJam.empty() in beforeAll, which
causes order-dependent failures because methods like accumulate() mutate
internal state and withOptions() replaces this.options; change the setup to
create a fresh TestJam for each test (use beforeEach to call TestJam.empty() and
assign to the jam variable) so every test starts with a new instance and no
cross-test state persists.
packages/jammin-sdk/simulator.ts (2)

260-295: enableLogs reconfigures the global logger on every call — consider caching.

This function is called on every simulateAccumulation invocation. The import() call and Logger.configureAll() are repeated even when the same options are used. Consider caching the last-applied config to skip redundant reconfiguration:

♻️ Suggested approach
+let lastLogConfig = "";
+
 async function enableLogs(options: DebugLoggingOptions) {
   try {
     const loggerModule = await import("@typeberry/lib/logger");
     if (loggerModule.Logger && loggerModule.Level) {
       const enabledLoggers: string[] = ["error"];
       // ... (existing logic)
       const logConfig = enabledLoggers.join(",");
+      if (logConfig === lastLogConfig) {
+        return;
+      }
+      lastLogConfig = logConfig;
       loggerModule.Logger.configureAll(logConfig, loggerModule.Level.LOG, process.cwd());
     }
   } catch {
     console.warn("Warning: Could not configure typeberry logger");
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jammin-sdk/simulator.ts` around lines 260 - 295, The enableLogs
function reimports and reconfigures the global logger on every call; cache the
last-applied configuration and the imported logger module to avoid redundant
work by storing a module-scoped variable (e.g., lastLogConfig: string | null and
cachedLoggerModule) and before calling loggerModule.Logger.configureAll(check)
compare the newly built logConfig (from enabledLoggers) to lastLogConfig—if
unchanged, return early; otherwise update lastLogConfig and call
Logger.configureAll. Also reuse the cachedLoggerModule instead of repeated
import() to skip repeated dynamic imports.

230-236: Debug logging enabled by default on every accumulation call — potential noise and overhead.

debug defaults to true (line 232), and enableLogs is invoked on every simulateAccumulation call (line 236), reconfiguring the global logger each time. This means:

  1. Every test that doesn't explicitly pass debug: false gets verbose logging, which can obscure test output and slow execution.
  2. enableLogs performs a global side effect (Logger.configureAll) on each call, which could cause issues in parallel test execution.

Consider defaulting debug to false for quieter test runs, or at minimum, skip the enableLogs call when the resolved options are empty.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jammin-sdk/simulator.ts` around lines 230 - 236, The debug flag
currently defaults to true causing enableLogs to reconfigure global logging on
every simulateAccumulation call; change the default assignment of debug (the
variable declared alongside slot and entropy) to false and then only call
enableLogs(loggingOptions) when loggingOptions is non-empty (e.g., has at least
one truthy property) to avoid unnecessary global Logger.configureAll side
effects; update the logic that builds loggingOptions (the ternary using typeof
debug) and gate the await enableLogs call accordingly so tests run quietly by
default and global logging is not toggled on every invocation.
docs/src/testing.md (1)

425-439: Inconsistent use of ServiceId(0) vs. SERVICES constant.

The "Testing Error Conditions" example uses ServiceId(0) directly (line 429), while the rest of the document consistently recommends using SERVICES.xxx.id from the generated config. This contradicts best practice #2 on line 588. Consider using SERVICES.auth.id here for consistency, or add a comment explaining why a raw ID is appropriate for this specific error-testing scenario.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/testing.md` around lines 425 - 439, The test uses a hardcoded
ServiceId(0) which is inconsistent with the document's guidance to use generated
constants; update the test's results entry to use the generated SERVICES
constant (e.g., replace ServiceId(0) with SERVICES.auth.id) in the
createWorkReportAsync call so it matches the rest of the docs and examples
referenced by createWorkReportAsync, jam.withWorkReport().accumulate, and
expectAccumulationSuccess; if there is a deliberate reason to use a raw numeric
ID for error-testing, add a one-line comment above the results entry explaining
why ServiceId(0) is intentionally used instead of SERVICES.auth.id.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/src/testing.md`:
- Around line 585-594: The docs have inconsistent spelling of the term
(instances of "preconfigured", "pre-configured", and "Pre-configured"); pick one
canonical form (e.g., "preconfigured") and replace all occurrences in the
document so every reference in headings, bullet points, and inline text uses
that exact spelling and casing; search for the strings "preconfigured",
"pre-configured", and "Pre-configured" and standardize them across the file
(update headings like "Use preconfigured `SERVICES`" and any other bullets or
sections using the alternate form).

In `@packages/jammin-sdk/utils/generate-test-config.ts`:
- Around line 74-77: The generated object keys in generateTestConfigCode can be
invalid for names with hyphens/dots/digits and you also shadow the imported name
by using "config"; change the map to rename the destructured variable (e.g.,
([name, svc]) instead of ([name, config])) and emit a quoted key and properly
escaped service name, e.g. use JSON.stringify(name) for the key and
JSON.stringify(svc.name) for the name value, keeping the id as
ServiceId(${svc.id}); this ensures valid TS for any service name and avoids the
config shadowing.
- Around line 32-37: The generateTestConfigFile function currently calls
loadBuildConfig() with no args so it resolves from process.cwd(); modify
generateTestConfigFile to accept a projectRoot parameter (or an already-loaded
config) and call loadBuildConfig(projectRoot) instead, and update
generateTestConfigInProjectDir to pass its projectRoot into
generateTestConfigFile (or pass the pre-loaded config) so config resolution uses
the intended project directory; reference functions: generateTestConfigFile,
generateTestConfigInProjectDir, and loadBuildConfig.

In `@packages/jammin-sdk/work-report.ts`:
- Around line 92-116: The switch over resultStatus.type leaves execResult
uninitialized for unexpected values; add a default case in the switch that
handles unknown resultStatus.type by logging the unexpected value and assigning
execResult to a safe error value (e.g., call WorkExecResult.error(...) using an
appropriate WorkExecResultKind such as a dedicated unknown/fallback kind or a
sensible fallback like badCode), so execResult is always initialized before
being passed to WorkResult.create.

---

Duplicate comments:
In `@docs/src/testing.md`:
- Around line 446-450: The document inconsistently references two config
filenames; choose the canonical name (e.g., "jammin.build.yml") and update every
occurrence in docs/src/testing.md so all references—such as the paragraph that
currently lists `jammin.build.yml`, the earlier code example around line 67, and
any mentions alongside `SERVICES` or `config/jammin.test.config.js`—use the same
filename; ensure spelling and punctuation match exactly across the file.

---

Nitpick comments:
In `@bin/cli/src/commands/build-command.ts`:
- Around line 117-126: The inner variable named services (the one assigned from
loadServices and passed to generateTestConfigInProjectDir) shadows an outer
services variable of a different type (ServiceConfig[] vs ServiceBuildOutput[]),
hurting readability; rename the inner variable to a distinct name such as
loadedServices or testServices wherever it's declared and used (the assignment
from loadServices and the call to generateTestConfigInProjectDir) so references
are unambiguous and types no longer collide while leaving the outer services
identifier untouched.

In `@docs/src/testing.md`:
- Around line 425-439: The test uses a hardcoded ServiceId(0) which is
inconsistent with the document's guidance to use generated constants; update the
test's results entry to use the generated SERVICES constant (e.g., replace
ServiceId(0) with SERVICES.auth.id) in the createWorkReportAsync call so it
matches the rest of the docs and examples referenced by createWorkReportAsync,
jam.withWorkReport().accumulate, and expectAccumulationSuccess; if there is a
deliberate reason to use a raw numeric ID for error-testing, add a one-line
comment above the results entry explaining why ServiceId(0) is intentionally
used instead of SERVICES.auth.id.

In `@packages/jammin-sdk/simulator.test.ts`:
- Around line 7-12: The tests use a shared mutable TestJam instance created by
TestJam.empty() in beforeAll, which causes order-dependent failures because
methods like accumulate() mutate internal state and withOptions() replaces
this.options; change the setup to create a fresh TestJam for each test (use
beforeEach to call TestJam.empty() and assign to the jam variable) so every test
starts with a new instance and no cross-test state persists.

In `@packages/jammin-sdk/simulator.ts`:
- Around line 260-295: The enableLogs function reimports and reconfigures the
global logger on every call; cache the last-applied configuration and the
imported logger module to avoid redundant work by storing a module-scoped
variable (e.g., lastLogConfig: string | null and cachedLoggerModule) and before
calling loggerModule.Logger.configureAll(check) compare the newly built
logConfig (from enabledLoggers) to lastLogConfig—if unchanged, return early;
otherwise update lastLogConfig and call Logger.configureAll. Also reuse the
cachedLoggerModule instead of repeated import() to skip repeated dynamic
imports.
- Around line 230-236: The debug flag currently defaults to true causing
enableLogs to reconfigure global logging on every simulateAccumulation call;
change the default assignment of debug (the variable declared alongside slot and
entropy) to false and then only call enableLogs(loggingOptions) when
loggingOptions is non-empty (e.g., has at least one truthy property) to avoid
unnecessary global Logger.configureAll side effects; update the logic that
builds loggingOptions (the ternary using typeof debug) and gate the await
enableLogs call accordingly so tests run quietly by default and global logging
is not toggled on every invocation.

In `@packages/jammin-sdk/testing-helpers.ts`:
- Around line 117-129: The current expectStateChange function treats only an
explicit false as failure, letting undefined (e.g., missing return in
StateChangePredicate) pass silently; update the check in expectStateChange to
fail on any non-true result (use result !== true) and throw
StateChangeAssertionError with the provided errorMessage or default, so
undefined/other falsy values trigger the assertion; if this behavior was
intentional instead, add JSDoc to expectStateChange and StateChangePredicate
documenting that only an explicit false fails and undefined passes.

Comment thread docs/src/testing.md
Comment thread packages/jammin-sdk/utils/generate-test-config.ts
Comment thread packages/jammin-sdk/utils/generate-test-config.ts
Comment thread packages/jammin-sdk/work-report.ts
@DrEverr DrEverr enabled auto-merge (squash) February 19, 2026 09:02
@DrEverr DrEverr merged commit 4a0d8fd into main Feb 19, 2026
8 checks passed
@DrEverr DrEverr deleted the maso-jam-test branch February 19, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create testing helpers and documentation

3 participants