Skip to content

Add orchestration DTO snapshots (#73) - #96

Merged
leynos merged 24 commits into
mainfrom
issue-73-syrupy-snapshot-tests-for-orchestration-outputs
May 28, 2026
Merged

Add orchestration DTO snapshots (#73)#96
leynos merged 24 commits into
mainfrom
issue-73-syrupy-snapshot-tests-for-orchestration-outputs

Conversation

@lodyai

@lodyai lodyai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Summary

This branch adds committed syrupy snapshots for orchestration DTO serialisation
so plan-version, action-kind, and nested show-notes output regressions are
visible in CI.

Closes #73.

Review walkthrough

Validation

  • pytest tests/test_generation_orchestration_snapshots.py --snapshot-update: passed via uv run; generated three snapshots and updated the canonical execution-plan snapshot.
  • make check-fmt: passed.
  • make test: passed, 480 passed and 3 skipped.
  • make lint: passed.
  • make typecheck: passed.

Summary by Sourcery

Tests:

  • Add syrupy snapshot tests for show-notes DTOs and orchestration results that include show-notes data.

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 01a8b00f-b241-46d0-bfd5-bc2b809ae484

📥 Commits

Reviewing files that changed from the base of the PR and between 60011f7 and b8c74bd.

📒 Files selected for processing (1)
  • tests/test_orchestration_usage.py

Overview

This PR completes issue #73 by introducing committed Syrupy snapshot tests for orchestration DTO serialisation, surfacing regressions in CI for the structured planning and tool-calling execution work described in ADR-005 and ExecPlan 2-4-1.

Key Changes

Snapshot Coverage for Orchestration DTOs

Added canonical snapshot tests in tests/test_generation_orchestration_snapshots.py and committed snapshots in tests/__snapshots__/test_generation_orchestration_snapshots.ambr:

  • ExecutionPlan serialisation: Snapshots now include PlannedAction.required_inputs and normalise non-deterministic fields (e.g., timestamps).
  • GenerationOrchestrationResult: Full DTO graphs covering plan versions, action-kind representations, and nested show-notes data via spec-driven builders (_OrchestrationResultSpec).
  • ShowNotesEntry and ShowNotesResult: Dedicated snapshot tests validating serialised forms of show-notes outputs with correct enum and nested field representations.

Validation and Invariant Tests

Added strict DTO validation tests in new files test_generation_orchestration_validation_properties.py and updated test_generation_orchestration_snapshots.py:

  • ShowNotesEntry constraints: Whitespace-only tei_locator values are normalised to None; non-ISO8601 timestamp strings trigger ValueError with field-specific error messages; invalid timestamp types raise TypeError.
  • ExecutionPlan freezing: Steps must be PlannedAction instances; invalid types raise TypeError.
  • PlannedAction validation: Whitespace-only rationale and required_inputs fields are rejected with field-specific ValueError messages.

Usage Rollup Refinement

Refactored episodic/orchestration/_usage.py to derive total_tokens from summed input_tokens + output_tokens across all components rather than summing component total_tokens directly. The implementation:

  • Logs warnings when individual component totals disagree with derived input/output sums.
  • Logs warnings when aggregate provider-reported totals disagree with sums across all components.
  • Provides unit tests in new module test_orchestration_usage.py validating correct derivation and warning emission.

Orchestration Result Builders

Introduced tests/_generation_orchestration_snapshot_support.py with deterministic DTO factories:

  • _OrchestrationResultSpec: Frozen dataclass centralising parameters for orchestration result construction (rationale, required inputs, action summary, usage overrides, show-notes result).
  • _make_orchestration_result(spec): Builds full PlannedAction → ExecutionPlan → ActionExecutionResult → GenerationOrchestrationResult graphs with optional spec overrides.
  • _make_show_notes_entry() and _make_show_notes_result(): Helper factories for show-notes DTO construction.
  • Structured planner payload builders: _valid_plan_payload(), _valid_plan_step(), and mutation helpers (_plan_payload_with_step_field, _plan_payload_without_step_field) for driving strict parsing error snapshot cases.

Property-Based Testing

Added Hypothesis property tests across multiple files:

  • test_orchestration_langgraph_properties.py: New test_build_generation_result_total_usage_property validates that summed input/output tokens match derived totals under synthetic planner and action usage combinations.
  • test_generation_orchestration_snapshots.py: New test_generation_orchestration_fixture_total_usage_property runs parametrised usage tuple combinations against the fixture builder, asserting correct total derivation.
  • test_generation_orchestration_validation_properties.py: Four property tests validating show-notes timestamp format/type constraints and planned-action whitespace/type constraints using Hypothesis strategies.
  • _orchestration_property_support.py: Added usage_counts_strategy and unconstrained_usage_counts_strategy for generating (input_tokens, output_tokens, total_tokens) tuples across tests.

Integration Testing

Enhanced tests/test_orchestration_orchestrator.py with two new async integration tests:

  • test_orchestrator_aggregates_show_notes_tool_output(): Validates that StructuredPlanningOrchestrator correctly aggregates real show-notes tool output, including plan version, rolled-up usage, and tei_locator entries; asserts show-notes LLM is invoked with model gpt-4o-mini and a prompt containing template_structure.
  • test_orchestrator_propagates_show_notes_format_errors(): Confirms orchestration failure when the show-notes LLM returns malformed JSON, raising ShowNotesFormatError with underlying validation details preserved via __cause__.

Test Harness Updates

  • test_guest_bios_properties.py: Simplified Hypothesis text strategy character set from explicit blacklist to fixed allowed-alphabet string.
  • test_orchestration_usage.py (new): Three unit tests validate _sum_usage derives totals correctly, logs component and aggregate mismatches, and skips logging when provider totals are consistent.

Design Document References

  • ADR-005 (Structured planning and tool execution): Defines the orchestration contract separating planning, execution routing through ToolExecutorPort, and model-tier configuration.
  • ExecPlan 2-4-1 (Structured-output planning and tool-calling execution): Documents the vertical slice implementation, which is now complete with deterministic test coverage via this PR.

Validation

All validation commands passed sequentially (as noted in the ExecPlan):

  • make check-fmt
  • make typecheck
  • make lint
  • make test (480 passed, 3 skipped) ✓

Walkthrough

Add deterministic ShowNotesEntry/ShowNotesResult helpers and serialisation snapshots; update ExecutionPlan serialisation so generate_show_notes requires script_tei_xml; extend orchestration fixtures to embed show-notes results and usage; add orchestrator integration and Hypothesis property tests for usage rollups and format-error propagation.

Changes

Show notes snapshot test coverage

Layer / File(s) Summary
Snapshot support helpers and fixtures
tests/_generation_orchestration_snapshot_support.py
Add deterministic builders: _UnusedLLMPort, _PlannedActionKwargs, _OrchestrationResultSpec, _make_show_notes_entry, _make_show_notes_result, _make_orchestration_result, plan payload helpers, and _capture_plan_format_error for planner error snapshots.
Test module overhaul and fixture wiring
tests/test_generation_orchestration_snapshots.py
Refactor tests to import shared snapshot-support helpers, add DTO invariants (ShowNotesEntry/Result), ExecutionPlan freeze/type assertions, PlannedAction whitespace validation, spec-driven orchestration-result snapshots, and usage-accounting tests including a Hypothesis property.
Snapshots and ExecutionPlan serialisation
tests/__snapshots__/test_generation_orchestration_snapshots.ambr, tests/test_generation_orchestration_snapshots.py
Update ExecutionPlan serialisation to use dataclasses.asdict and include PlannedAction.required_inputs ("script_tei_xml"); add Syrupy snapshots for ShowNotesEntry and ShowNotesResult; commit updated/added snapshot entries.
Hypothesis strategy and total-usage property
tests/_orchestration_property_support.py, tests/test_orchestration_langgraph_properties.py
Add usage_counts_strategy and unconstrained_usage_counts_strategy producing larger-token tuples and property test test_build_generation_result_total_usage_property that asserts total_usage equals component-wise planner+action LLMUsage sums using dataclasses.replace.
StructuredPlanningOrchestrator show-notes integration
tests/test_orchestration_orchestrator.py
Add async tests wiring StructuredPlanningOrchestrator with ShowNotesToolExecutor to assert aggregation of show-notes tool output, rolled-up usage, presence of tei_locator, correct LLM model (gpt-4o-mini) and prompt content (template_structure), and that malformed structured output raises ShowNotesFormatError with preserved parsing cause.
Guest-bios text strategy tweak
tests/test_guest_bios_properties.py
Replace _TEXT Hypothesis characters config with an explicit allowed-character alphabet for TEI payload generation.
Usage aggregation change
episodic/orchestration/_usage.py
Adjust _sum_usage to derive total_tokens from aggregated input_tokens + output_tokens instead of summing input records' total_tokens; log warnings when provider totals mismatch derived totals.
Usage aggregation tests
tests/test_orchestration_usage.py
Add unit tests verifying derived-total behaviour and logged mismatch events when provider totals differ, and no logs when consistent.

Sequence Diagram(s)

sequenceDiagram
  participant Test
  participant Orchestrator as StructuredPlanningOrchestrator
  participant ToolExec as ShowNotesToolExecutor
  participant ShowNotesLLM as ShowNotesLLM
  participant Result as GenerationOrchestrationResult

  Test->>Orchestrator: orchestrate(request)
  Orchestrator->>ToolExec: request show-notes generation (planned action)
  ToolExec->>ShowNotesLLM: invoke model with prompt containing template_structure (model=gpt-4o-mini)
  ShowNotesLLM-->>ToolExec: return structured show-notes JSON
  ToolExec-->>Orchestrator: return ShowNotesResult (entries, usage, tei_locator)
  Orchestrator-->>Result: aggregate planner and action usage, embed show_notes_result
  Result-->>Test: return final orchestration result
Loading

Possibly related PRs

  • leynos/episodic#97: Shares updates to the property-test Hypothesis infrastructure and usage-count strategies used by these tests.

Poem

Pin the show-notes to the page tonight,
Stamp the tokens, count them tight,
Freeze the plan and hold the sight,
Surface errors, keep the cause in light,
Run the snapshots, let the tests take flight.

🚥 Pre-merge checks | ✅ 19 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Developer Documentation ⚠️ Warning PR closes issue #73 but fails to update roadmap.md to document the snapshot-test work alongside #72, and execplan does not reflect this completion. Add issue #73 to roadmap.md 2.4.1 entry alongside #72. Update execplan or clarify when issue #73 snapshot work was completed relative to Stage F.
✅ Passed checks (19 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add orchestration DTO snapshots (#73)' directly matches the main changeset: syrupy snapshot tests for orchestration DTOs with committed snapshots, and references the linked issue number as required.
Description check ✅ Passed The description clearly relates to the changeset, explaining the branch adds committed syrupy snapshots for DTO serialisation, covers the test files and snapshot assertions, and documents validation steps and closure of issue #73.
Linked Issues check ✅ Passed The PR fulfils all coding requirements from issue #73: snapshot tests for ExecutionPlan, GenerationOrchestrationResult, and ShowNotesEntry serialisation [#73], with committed snapshots and non-deterministic field handling [#73].
Out of Scope Changes check ✅ Passed All changes are scoped to snapshot test implementation and supporting test utilities. The single non-test change to episodic/orchestration/_usage.py (refactoring _sum_usage) directly supports test validation and is scoped within snapshot-test coverage requirements.
Docstring Coverage ✅ Passed Docstring coverage is 95.24% which is sufficient. The required threshold is 80.00%.
Testing (Overall) ✅ Passed Snapshots verify DTO enums. Hypothesis tests confirm validators reject whitespace and invalid types. Usage tests validate correct derivation. Tests exercise real post_init constraints.
User-Facing Documentation ✅ Passed PR adds snapshot tests for orchestration DTOs with internal refactoring of _sum_usage. Public API unchanged; no user-facing documentation required.
Module-Level Documentation ✅ Passed All 9 modules modified or added in the PR carry module-level docstrings with clear purpose, utility, and component-relationship documentation as required by the check.
Testing (Unit And Behavioural) ✅ Passed Snapshot tests validate DTO serialisation. Unit tests verify validation with happy and error paths. Property tests cover arbitrary values. Behavioural tests exercise real orchestrator components.
Testing (Property / Proof) ✅ Passed PR introduces invariants with comprehensive Hypothesis property tests: token-count derivation (100 examples), timestamp ISO8601 validation (75 examples), and field validation (50 examples).
Testing (Compile-Time / Ui) ✅ Passed Frozen DTO snapshots using dataclasses.asdict with redacted nondeterministic fields, focused tests (5 snapshots + 7 semantic validations), and committed to CI (.ambr file).
Unit Architecture ✅ Passed Clear separation of queries/commands, explicit side-effects via _log_event, injectable dependencies, composable test components, proper boundary verification without hidden state.
Domain Architecture ✅ Passed Production code (_usage.py) is pure domain logic with _log_event abstraction only. Test code properly segregated in tests/. No domain/infrastructure contamination.
Observability ✅ Passed Change to _sum_usage logs usage-total mismatches with stable numeric fields (token counts) at meaningful decision points with appropriate warning level and comprehensive test coverage.
Security And Privacy ✅ Passed All changes are test-only with synthetic data. No secrets, credentials, PII, unsafe deserialization, injection risks, or sensitive data exposure found in snapshots, fixtures, logs, or error messages.
Performance And Resource Use ✅ Passed Production usage aggregation is O(n) with O(1) space; all test strategies have explicit bounds; regex compiled once; no blocking operations or repeated I/O on hot paths.
Concurrency And State ✅ Passed No shared mutable state, async task spawning, locks, or concurrency concerns introduced. Logging uses thread-safe stdlib; monkeypatching properly scoped.
Architectural Complexity And Maintainability ✅ Passed Abstractions remove duplication, reduce complexity via parameter object, have proven cross-file reuse, and no circular dependencies.
Rust Compiler Lint Integrity ✅ Passed This is a Python project with no Rust code. The Rust compiler lint check does not apply here, as the PR contains only Python test and orchestration module changes.

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

📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #73

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-73-syrupy-snapshot-tests-for-orchestration-outputs

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

@sourcery-ai

sourcery-ai Bot commented May 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds syrupy-based snapshot tests for orchestration DTO serialisation, including new coverage for show-notes DTOs and updated execution plan snapshots to track required_inputs and nested orchestration results.

File-Level Changes

Change Details Files
Extend execution plan snapshot to include required_inputs for GENERATE_SHOW_NOTES actions.
  • Add required_inputs tuple containing script_tei_xml to PlannedAction fixtures used in execution plan serialization tests
  • Regenerate execution plan snapshot to reflect the new required_inputs field and keep canonical snapshot up to date
tests/test_generation_orchestration_snapshots.py
tests/__snapshots__/test_generation_orchestration_snapshots.ambr
Add snapshot coverage for ShowNotesEntry and ShowNotesResult DTO serialisation.
  • Introduce deterministic ShowNotesEntry fixture and assert its dataclass-as-dict representation against syrupy snapshot
  • Introduce deterministic ShowNotesResult fixture including multiple entries, usage, model metadata, and finish_reason, and assert its serialised form against snapshot
  • Commit corresponding snapshots to capture enum/value representations and nested show-notes structures
tests/test_generation_orchestration_snapshots.py
tests/__snapshots__/test_generation_orchestration_snapshots.ambr
Extend GenerationOrchestrationResult snapshot coverage to include show-notes action results and usage aggregation.
  • Add test that builds an ExecutionPlan with a GENERATE_SHOW_NOTES PlannedAction and required_inputs
  • Add test that constructs a GenerationOrchestrationResult with an ActionExecutionResult holding a ShowNotesResult, reusing LLMUsage fields for planner and total usage
  • Serialise orchestration result via dataclasses.asdict and assert against a committed snapshot capturing nested action/show-notes payloads
tests/test_generation_orchestration_snapshots.py
tests/__snapshots__/test_generation_orchestration_snapshots.ambr

Assessment against linked issues

Issue Objective Addressed Explanation
#73 Implement syrupy snapshot tests for ExecutionPlan serialisation, asserting a canonical JSON/dict representation (with non-deterministic fields handled) and committing the snapshot.
#73 Implement syrupy snapshot tests for GenerationOrchestrationResult aggregation, asserting a fully assembled result snapshot including plan version and action kinds.
#73 Implement syrupy snapshot tests for ShowNotesEntry-related structures, asserting the serialised form of ShowNotesEntry (and related ShowNotesResult) outputs matches committed snapshots.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
leynos marked this pull request as ready for review May 13, 2026 23:23
@coderabbitai coderabbitai Bot added the Issue label May 13, 2026
sourcery-ai[bot]

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented May 13, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

tests/test_generation_orchestration_snapshots.py

Comment on lines +159 to +194

def test_generation_orchestration_result_with_show_notes_snapshot(
    snapshot: SnapshotAssertion,
) -> None:
    planned = PlannedAction(
        action_id="a1",
        action_kind=ActionKind.GENERATE_SHOW_NOTES,
        rationale="Generate listener-facing notes from canonical TEI.",
        model_tier=ModelTier.EXECUTION,
        required_inputs=("script_tei_xml",),
    )
    plan = ExecutionPlan(
        plan_version="1",
        selected_planning_model="gpt-4.1",
        selected_execution_model="gpt-4o-mini",
        steps=(planned,),
    )
    show_notes = make_show_notes_result(
        entries=(make_show_notes_entry(),),
    )
    action_done = ActionExecutionResult(
        action_id="a1",
        action_kind=ActionKind.GENERATE_SHOW_NOTES,
        model_tier=ModelTier.EXECUTION,
        model="gpt-4o-mini",
        summary="Generated one show-notes entry.",
        usage=show_notes.usage,
        show_notes_result=show_notes,
    )
    result = GenerationOrchestrationResult(
        plan=plan,
        action_results=(action_done,),
        planner_usage=LLMUsage(input_tokens=12, output_tokens=8, total_tokens=20),
        total_usage=LLMUsage(input_tokens=52, output_tokens=33, total_tokens=85),
    )
    serialised = dataclasses.asdict(result)
    assert serialised == snapshot

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: test_generation_orchestration_result_snapshot,test_generation_orchestration_result_with_show_notes_snapshot

@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@lodyai
lodyai Bot force-pushed the issue-73-syrupy-snapshot-tests-for-orchestration-outputs branch from 24e56ca to 9860436 Compare May 19, 2026 20:41
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_generation_orchestration_snapshots.py`:
- Around line 214-219: When total_usage is None, compute it from planner_usage
and action_usage instead of using a fixed LLMUsage; sum the input_tokens,
output_tokens, and total_tokens from planner_usage and action_usage (treat any
missing planner_usage or action_usage as zero for each token field) and assign
total_usage = LLMUsage(input_tokens=sum_input, output_tokens=sum_output,
total_tokens=sum_total); if both planner_usage and action_usage are absent keep
the existing hardcoded fallback LLMUsage(11,22,33). Ensure you update the block
that currently sets total_usage to reference planner_usage and action_usage by
name so callers who override only one usage still produce correct totals.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: abe035cf-f64e-419e-8724-42a57d21e50a

📥 Commits

Reviewing files that changed from the base of the PR and between 6498501 and 9860436.

📒 Files selected for processing (3)
  • tests/__snapshots__/test_generation_orchestration_snapshots.ambr
  • tests/test_generation_orchestration_snapshots.py
  • tests/test_orchestration_orchestrator.py

Comment thread tests/test_generation_orchestration_snapshots.py Outdated
@leynos

leynos commented May 20, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

Excess Number of Function Arguments

tests/test_generation_orchestration_snapshots.py: _make_orchestration_result

What lead to degradation?

_make_orchestration_result has 7 arguments, max arguments = 4

Why does this problem occur?

Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.
The threshold for the Python language is 4 function arguments.

How to fix it?

Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring INTRODUCE PARAMETER OBJECT to encapsulate arguments that refer to the same logical concept.

@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_generation_orchestration_snapshots.py`:
- Around line 339-349: Modify the
test_generation_orchestration_fixture_totals_partial_usage_overrides test to
parametrize input cases so it covers both action-only and planner-only partial
overrides: use pytest.mark.parametrize on the test to pass either
action_usage=_make_orchestration_result(action_usage=LLMUsage(...)) or
planner_usage=_make_orchestration_result(planner_usage=LLMUsage(...))
(referencing the test name
test_generation_orchestration_fixture_totals_partial_usage_overrides, helper
_make_orchestration_result, and LLMUsage) and assert result.total_usage equals
the supplied LLMUsage in each param case; this ensures both partial branches
(action vs planner) are exercised without changing other test logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c38111a1-bafd-41a6-a19b-b3cd4e382b15

📥 Commits

Reviewing files that changed from the base of the PR and between 9860436 and a0bf15c.

📒 Files selected for processing (1)
  • tests/test_generation_orchestration_snapshots.py

Comment thread tests/test_generation_orchestration_snapshots.py Outdated
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
tests/test_generation_orchestration_snapshots.py (2)

193-202: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace the seven-argument fixture helper with a parameter object.

Unblock the CodeScene gate at Line 193 by collapsing the helper signature into a single spec object and removing the too-many-arguments suppression.

Refactor diff
+@dataclasses.dataclass(frozen=True, slots=True)
+class _OrchestrationResultSpec:
+    rationale: str = "test"
+    required_inputs: tuple[str, ...] = ()
+    action_summary: str = "test"
+    action_usage: LLMUsage | None = None
+    show_notes_result: ShowNotesResult | None = None
+    planner_usage: LLMUsage | None = None
+    total_usage: LLMUsage | None = None
+
-# pylint: disable-next=too-many-arguments
 def _make_orchestration_result(
-    *,
-    rationale: str = "test",
-    required_inputs: tuple[str, ...] = (),
-    action_summary: str = "test",
-    action_usage: LLMUsage | None = None,
-    show_notes_result: ShowNotesResult | None = None,
-    planner_usage: LLMUsage | None = None,
-    total_usage: LLMUsage | None = None,
+    spec: _OrchestrationResultSpec | None = None,
 ) -> GenerationOrchestrationResult:
+    spec = _OrchestrationResultSpec() if spec is None else spec
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_generation_orchestration_snapshots.py` around lines 193 - 202, The
helper function _make_orchestration_result currently accepts seven separate
parameters (rationale, required_inputs, action_summary, action_usage,
show_notes_result, planner_usage, total_usage); replace this signature with a
single parameter object (e.g., a dataclass or dict named OrchestrationResultSpec
or spec) and update the body to read properties from that spec, then adjust all
callers in tests to pass a single spec instance and remove the
`too-many-arguments` suppression; keep the original defaults by providing
default values on the spec fields and preserve the return type
GenerationOrchestrationResult in the function (still named
_make_orchestration_result) so other references remain valid.

323-330: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Parametrize the partial-override test to cover planner-only input.

Exercise the missing planner-only branch at Line 323 and keep the test aligned with its own “whichever usage values callers supply” contract.

Test diff
-def test_generation_orchestration_fixture_totals_partial_usage_overrides() -> None:
+@pytest.mark.parametrize(
+    ("kwargs", "expected"),
+    [
+        (
+            {"action_usage": LLMUsage(input_tokens=5, output_tokens=7, total_tokens=12)},
+            LLMUsage(input_tokens=6, output_tokens=9, total_tokens=15),
+        ),
+        (
+            {"planner_usage": LLMUsage(input_tokens=2, output_tokens=3, total_tokens=5)},
+            LLMUsage(input_tokens=12, output_tokens=23, total_tokens=35),
+        ),
+    ],
+)
+def test_generation_orchestration_fixture_totals_partial_usage_overrides(
+    kwargs: dict[str, LLMUsage],
+    expected: LLMUsage,
+) -> None:
     """Verify total usage is derived from whichever usage values callers supply."""
-    result = _make_orchestration_result(
-        action_usage=LLMUsage(input_tokens=5, output_tokens=7, total_tokens=12),
-    )
-
-    assert result.total_usage == LLMUsage(6, 9, 15)
+    result = _make_orchestration_result(**kwargs)
+    assert result.total_usage == expected

As per coding guidelines "**/test_*.py: Use pytest idioms: prefer fixtures over setup/teardown methods, parametrize broadly, avoid unnecessary mocks".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_generation_orchestration_snapshots.py` around lines 323 - 330, The
test test_generation_orchestration_fixture_totals_partial_usage_overrides only
exercises an action-only override; parametrize it to also exercise a
planner-only override by adding a case that calls
_make_orchestration_result(planner_usage=LLMUsage(...)) and assert
result.total_usage equals the expected sum (matching the test's “whichever usage
values callers supply” behavior); keep other existing cases (action-only, both)
in the param list and use pytest.mark.parametrize to feed (planner_usage,
action_usage, expected_total) into the test so the planner-only branch in
_make_orchestration_result is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@tests/test_generation_orchestration_snapshots.py`:
- Around line 193-202: The helper function _make_orchestration_result currently
accepts seven separate parameters (rationale, required_inputs, action_summary,
action_usage, show_notes_result, planner_usage, total_usage); replace this
signature with a single parameter object (e.g., a dataclass or dict named
OrchestrationResultSpec or spec) and update the body to read properties from
that spec, then adjust all callers in tests to pass a single spec instance and
remove the `too-many-arguments` suppression; keep the original defaults by
providing default values on the spec fields and preserve the return type
GenerationOrchestrationResult in the function (still named
_make_orchestration_result) so other references remain valid.
- Around line 323-330: The test
test_generation_orchestration_fixture_totals_partial_usage_overrides only
exercises an action-only override; parametrize it to also exercise a
planner-only override by adding a case that calls
_make_orchestration_result(planner_usage=LLMUsage(...)) and assert
result.total_usage equals the expected sum (matching the test's “whichever usage
values callers supply” behavior); keep other existing cases (action-only, both)
in the param list and use pytest.mark.parametrize to feed (planner_usage,
action_usage, expected_total) into the test so the planner-only branch in
_make_orchestration_result is covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 071b738e-6c1b-4c43-a7fc-b1c9c5f8c872

📥 Commits

Reviewing files that changed from the base of the PR and between a0bf15c and 2c69ec8.

📒 Files selected for processing (1)
  • tests/test_generation_orchestration_snapshots.py

@leynos

leynos commented May 21, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following now been resolved?

  • The show-notes DTO instances are constructed multiple times with identical or near-identical values; consider extracting small helper/fixture builders (e.g., make_show_notes_entry, make_show_notes_result) to reduce duplication and keep future updates to these structures in one place.
  • Since these tests rely on dataclasses.asdict for snapshot shape, it may be worth adding a brief inline comment in one of the tests explaining that asdict is intentionally used as the canonical serialisation path for nested DTOs, so future refactors don’t inadvertently change it to a custom encoder and silently alter the snapshot format.

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for make_show_notes_entry / make_show_notes_result helpers and asdict comment
echo "=== Helper functions ==="
rg -n 'def make_show_notes_entry|def make_show_notes_result' tests/test_generation_orchestration_snapshots.py

echo ""
echo "=== asdict usages with surrounding context ==="
rg -n -B2 -A2 'asdict' tests/test_generation_orchestration_snapshots.py

echo ""
echo "=== Full file ==="
cat tests/test_generation_orchestration_snapshots.py

Length of output: 14971


@leynos Yes, both items are resolved in the current version of the file.

  1. Helper/fixture buildersmake_show_notes_entry (line 55) and make_show_notes_result (line 71) are present as module-level helpers and are used consistently across all snapshot tests that require show-notes DTO instances.

  2. dataclasses.asdict inline comment — Line 125 in test_execution_plan_serialisation_snapshot reads: # \asdict` is the canonical nested DTO serialisation path under snapshot.That comment anchors the intent for future maintainers before the firstasdict` call in the snapshot suite.

Both suggestions are fully addressed. ✅

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented May 25, 2026

Copy link
Copy Markdown
Owner

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor
✅ 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.

@leynos

leynos commented May 25, 2026

Copy link
Copy Markdown
Owner

@coderabbitai review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_generation_orchestration_snapshots.py`:
- Line 8: The file contains a file-level lint suppression "# pylint:
disable=too-many-lines" and is over the allowed size; remove the file-level
suppression and split the large test module into smaller modules/fixtures so
each file is under 400 logical lines: extract helper functions/classes (the
surrounding test helpers referenced near line 8) into one or more new modules
(e.g., tests/helpers/*.py or fixtures modules) and convert shared setup into
pytest fixtures, update imports in
tests/test_generation_orchestration_snapshots.py to import those
helpers/fixtures, ensure no file-level "# pylint: disable=..." remains and
instead apply targeted, minimal inline suppressions only where absolutely
necessary, and run tests/linter to confirm everything still passes (this change
also applies to the full range covered by the original file 1-528).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bb69ff23-66ce-416a-87a8-c3c534aaf148

📥 Commits

Reviewing files that changed from the base of the PR and between 731bf5d and 79df60d.

📒 Files selected for processing (6)
  • tests/__snapshots__/test_generation_orchestration_snapshots.ambr
  • tests/_orchestration_property_support.py
  • tests/test_generation_orchestration_snapshots.py
  • tests/test_guest_bios_properties.py
  • tests/test_orchestration_langgraph_properties.py
  • tests/test_orchestration_orchestrator.py

Comment thread tests/test_generation_orchestration_snapshots.py Outdated
Move snapshot DTO builders and planner-format helpers into a private test
support module so the snapshot test module stays below the lint line limit.
Remove the file-level `too-many-lines` suppression and keep the existing
snapshot assertions unchanged.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented May 26, 2026

Copy link
Copy Markdown
Owner

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor
✅ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/_generation_orchestration_snapshot_support.py`:
- Around line 21-202: Rename the module-level test helpers to be private by
prefixing their names with an underscore (e.g., UnusedLLMPort -> _UnusedLLMPort,
PlannedActionKwargs -> _PlannedActionKwargs, OrchestrationResultSpec ->
_OrchestrationResultSpec, make_show_notes_entry -> _make_show_notes_entry,
make_show_notes_result -> _make_show_notes_result, make_orchestration_result ->
_make_orchestration_result, valid_plan_payload -> _valid_plan_payload,
valid_plan_step -> _valid_plan_step, plan_payload_with_step_field ->
_plan_payload_with_step_field, plan_payload_without_step_field ->
_plan_payload_without_step_field, capture_plan_format_error ->
_capture_plan_format_error) and update the import site in
tests/test_generation_orchestration_snapshots.py to import those underscored
names; keep docstrings and signatures unchanged. Ensure all internal references
within this file use the new underscored names so references like
StructuredGenerationPlanner._parse_plan and the test import continue to resolve.
- Around line 24-31: The generate static method currently raises RuntimeError
for the impossible await path; change it to raise AssertionError instead to
reflect a broken test-harness invariant. Update the raise in generate
(signature: async def generate(request: LLMRequest) -> LLMResponse) to raise
AssertionError(msg) using the same msg that references
StructuredGenerationPlanner.build_prompt so the error retains context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5f5eb640-3503-4381-9f5c-f74d6248db69

📥 Commits

Reviewing files that changed from the base of the PR and between 79df60d and e86c261.

📒 Files selected for processing (2)
  • tests/_generation_orchestration_snapshot_support.py
  • tests/test_generation_orchestration_snapshots.py

Comment thread tests/_generation_orchestration_snapshot_support.py Outdated
Comment thread tests/_generation_orchestration_snapshot_support.py Outdated
Prefix the extracted snapshot support helpers with underscores so the module
signals that they are local test implementation details. Raise
`AssertionError` from the unreachable LLM shim path to report a broken test
harness invariant with the existing contextual message.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Expand the support module docstring to describe the fixture families, planner
payload builders, and their relationship to the orchestration snapshot tests.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Cover snapshot fixture validation boundaries with Hypothesis-generated
invalid timestamps, whitespace-only values, and non-string DTO inputs.
Keep the snapshot module under the line limit by placing these checks in
a focused validation property test module.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_generation_orchestration_validation_properties.py (1)

67-125: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Parametrize duplicated PlannedAction rejection tests.

Collapse the duplicated test bodies into parametrized cases so the boundary intent stays explicit and maintenance cost drops.

Proposed refactor
+import pytest
@@
-@given(value=_WHITESPACE_STRINGS)
-@settings(max_examples=25)
-def test_planned_action_rejects_arbitrary_whitespace_rationale(
-    value: str,
-) -> None:
-    """Verify rationale validation rejects arbitrary whitespace strings."""
-    with pytest.raises(ValueError, match="rationale must be a non-empty string"):
-        PlannedAction(
-            action_id="a1",
-            action_kind=ActionKind.GENERATE_SHOW_NOTES,
-            rationale=value,
-            model_tier=ModelTier.EXECUTION,
-            required_inputs=("script_tei_xml",),
-        )
-
-
-@given(value=_WHITESPACE_STRINGS)
-@settings(max_examples=25)
-def test_planned_action_rejects_arbitrary_whitespace_required_inputs(
-    value: str,
-) -> None:
-    """Verify required input validation rejects arbitrary whitespace strings."""
-    with pytest.raises(ValueError, match="required_inputs must be a non-empty string"):
-        PlannedAction(
-            action_id="a1",
-            action_kind=ActionKind.GENERATE_SHOW_NOTES,
-            rationale="test",
-            model_tier=ModelTier.EXECUTION,
-            required_inputs=(value,),
-        )
+@pytest.mark.parametrize(
+    ("field_name", "error_match"),
+    (
+        ("rationale", "rationale must be a non-empty string"),
+        ("required_inputs", "required_inputs must be a non-empty string"),
+    ),
+)
+@given(value=_WHITESPACE_STRINGS)
+@settings(max_examples=25)
+def test_planned_action_rejects_arbitrary_whitespace_fields(
+    field_name: str,
+    error_match: str,
+    value: str,
+) -> None:
+    """Verify PlannedAction rejects arbitrary whitespace-only field values."""
+    kwargs = {
+        "action_id": "a1",
+        "action_kind": ActionKind.GENERATE_SHOW_NOTES,
+        "rationale": "test",
+        "model_tier": ModelTier.EXECUTION,
+        "required_inputs": ("script_tei_xml",),
+    }
+    if field_name == "rationale":
+        kwargs["rationale"] = value
+    else:
+        kwargs["required_inputs"] = (value,)
+    with pytest.raises(ValueError, match=error_match):
+        PlannedAction(**kwargs)
@@
-@given(value=_INVALID_DTO_FIELD_TYPES)
-@settings(max_examples=25)
-def test_planned_action_rejects_invalid_rationale_types(value: object) -> None:
-    """Verify rationale validation rejects arbitrary non-string values."""
-    with pytest.raises(ValueError, match="rationale must be a non-empty string"):
-        PlannedAction(
-            action_id="a1",
-            action_kind=ActionKind.GENERATE_SHOW_NOTES,
-            rationale=typ.cast("str", value),
-            model_tier=ModelTier.EXECUTION,
-            required_inputs=("script_tei_xml",),
-        )
-
-
-@given(value=_INVALID_DTO_FIELD_TYPES)
-@settings(max_examples=25)
-def test_planned_action_rejects_invalid_required_input_types(value: object) -> None:
-    """Verify required input validation rejects arbitrary non-string items."""
-    with pytest.raises(ValueError, match="required_inputs must be a non-empty string"):
-        PlannedAction(
-            action_id="a1",
-            action_kind=ActionKind.GENERATE_SHOW_NOTES,
-            rationale="test",
-            model_tier=ModelTier.EXECUTION,
-            required_inputs=typ.cast("tuple[str, ...]", (value,)),
-        )
+@pytest.mark.parametrize(
+    ("field_name", "error_match"),
+    (
+        ("rationale", "rationale must be a non-empty string"),
+        ("required_inputs", "required_inputs must be a non-empty string"),
+    ),
+)
+@given(value=_INVALID_DTO_FIELD_TYPES)
+@settings(max_examples=25)
+def test_planned_action_rejects_invalid_field_types(
+    field_name: str,
+    error_match: str,
+    value: object,
+) -> None:
+    """Verify PlannedAction rejects arbitrary invalid field types."""
+    kwargs = {
+        "action_id": "a1",
+        "action_kind": ActionKind.GENERATE_SHOW_NOTES,
+        "rationale": "test",
+        "model_tier": ModelTier.EXECUTION,
+        "required_inputs": ("script_tei_xml",),
+    }
+    if field_name == "rationale":
+        kwargs["rationale"] = typ.cast("str", value)
+    else:
+        kwargs["required_inputs"] = typ.cast("tuple[str, ...]", (value,))
+    with pytest.raises(ValueError, match=error_match):
+        PlannedAction(**kwargs)

As per coding guidelines, "**/*.py: Replace duplicate tests with @pytest.mark.parametrize" and "**/test_*.py: ... parametrize broadly".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_generation_orchestration_validation_properties.py` around lines 67
- 125, Collapse duplicated property-specific tests into parametrized variants:
replace the two whitespace tests
(test_planned_action_rejects_arbitrary_whitespace_rationale and
test_planned_action_rejects_arbitrary_whitespace_required_inputs) with a single
`@pytest.mark.parametrize` test (e.g.,
test_planned_action_rejects_arbitrary_whitespace_fields) that accepts field_name
and error_match, builds a kwargs dict for PlannedAction and injects value into
either "rationale" or "required_inputs"; do the same for the invalid-type pair
(test_planned_action_rejects_invalid_rationale_types and
test_planned_action_rejects_invalid_required_input_types) into one parametrized
test (e.g., test_planned_action_rejects_invalid_field_types) that casts value
appropriately and asserts the same ValueError matches, keeping references to
PlannedAction, "rationale", and "required_inputs" to locate the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tests/test_generation_orchestration_validation_properties.py`:
- Around line 67-125: Collapse duplicated property-specific tests into
parametrized variants: replace the two whitespace tests
(test_planned_action_rejects_arbitrary_whitespace_rationale and
test_planned_action_rejects_arbitrary_whitespace_required_inputs) with a single
`@pytest.mark.parametrize` test (e.g.,
test_planned_action_rejects_arbitrary_whitespace_fields) that accepts field_name
and error_match, builds a kwargs dict for PlannedAction and injects value into
either "rationale" or "required_inputs"; do the same for the invalid-type pair
(test_planned_action_rejects_invalid_rationale_types and
test_planned_action_rejects_invalid_required_input_types) into one parametrized
test (e.g., test_planned_action_rejects_invalid_field_types) that casts value
appropriately and asserts the same ValueError matches, keeping references to
PlannedAction, "rationale", and "required_inputs" to locate the code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 51c3403e-7227-4ff0-8ddd-4d66f8b9ebba

📥 Commits

Reviewing files that changed from the base of the PR and between fec4bba and 7bccee3.

📒 Files selected for processing (1)
  • tests/test_generation_orchestration_validation_properties.py

Collapse duplicated `PlannedAction` rejection checks into parametrized
Hypothesis tests. Keep the boundary cases explicit while reducing the
repeated DTO construction in the validation property module.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented May 26, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following now been resolved?

tests/test_generation_orchestration_validation_properties.py (1)

67-125: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Parametrize duplicated PlannedAction rejection tests.
Collapse the duplicated test bodies into parametrized cases so the boundary intent stays explicit and maintenance cost drops.

Proposed refactor
+import pytest
@@
-@given(value=_WHITESPACE_STRINGS)
-@settings(max_examples=25)
-def test_planned_action_rejects_arbitrary_whitespace_rationale(
-    value: str,
-) -> None:
-    """Verify rationale validation rejects arbitrary whitespace strings."""
-    with pytest.raises(ValueError, match="rationale must be a non-empty string"):
-        PlannedAction(
-            action_id="a1",
-            action_kind=ActionKind.GENERATE_SHOW_NOTES,
-            rationale=value,
-            model_tier=ModelTier.EXECUTION,
-            required_inputs=("script_tei_xml",),
-        )
-
-
-@given(value=_WHITESPACE_STRINGS)
-@settings(max_examples=25)
-def test_planned_action_rejects_arbitrary_whitespace_required_inputs(
-    value: str,
-) -> None:
-    """Verify required input validation rejects arbitrary whitespace strings."""
-    with pytest.raises(ValueError, match="required_inputs must be a non-empty string"):
-        PlannedAction(
-            action_id="a1",
-            action_kind=ActionKind.GENERATE_SHOW_NOTES,
-            rationale="test",
-            model_tier=ModelTier.EXECUTION,
-            required_inputs=(value,),
-        )
+@pytest.mark.parametrize(
+    ("field_name", "error_match"),
+    (
+        ("rationale", "rationale must be a non-empty string"),
+        ("required_inputs", "required_inputs must be a non-empty string"),
+    ),
+)
+@given(value=_WHITESPACE_STRINGS)
+@settings(max_examples=25)
+def test_planned_action_rejects_arbitrary_whitespace_fields(
+    field_name: str,
+    error_match: str,
+    value: str,
+) -> None:
+    """Verify PlannedAction rejects arbitrary whitespace-only field values."""
+    kwargs = {
+        "action_id": "a1",
+        "action_kind": ActionKind.GENERATE_SHOW_NOTES,
+        "rationale": "test",
+        "model_tier": ModelTier.EXECUTION,
+        "required_inputs": ("script_tei_xml",),
+    }
+    if field_name == "rationale":
+        kwargs["rationale"] = value
+    else:
+        kwargs["required_inputs"] = (value,)
+    with pytest.raises(ValueError, match=error_match):
+        PlannedAction(**kwargs)
@@
-@given(value=_INVALID_DTO_FIELD_TYPES)
-@settings(max_examples=25)
-def test_planned_action_rejects_invalid_rationale_types(value: object) -> None:
-    """Verify rationale validation rejects arbitrary non-string values."""
-    with pytest.raises(ValueError, match="rationale must be a non-empty string"):
-        PlannedAction(
-            action_id="a1",
-            action_kind=ActionKind.GENERATE_SHOW_NOTES,
-            rationale=typ.cast("str", value),
-            model_tier=ModelTier.EXECUTION,
-            required_inputs=("script_tei_xml",),
-        )
-
-
-@given(value=_INVALID_DTO_FIELD_TYPES)
-@settings(max_examples=25)
-def test_planned_action_rejects_invalid_required_input_types(value: object) -> None:
-    """Verify required input validation rejects arbitrary non-string items."""
-    with pytest.raises(ValueError, match="required_inputs must be a non-empty string"):
-        PlannedAction(
-            action_id="a1",
-            action_kind=ActionKind.GENERATE_SHOW_NOTES,
-            rationale="test",
-            model_tier=ModelTier.EXECUTION,
-            required_inputs=typ.cast("tuple[str, ...]", (value,)),
-        )
+@pytest.mark.parametrize(
+    ("field_name", "error_match"),
+    (
+        ("rationale", "rationale must be a non-empty string"),
+        ("required_inputs", "required_inputs must be a non-empty string"),
+    ),
+)
+@given(value=_INVALID_DTO_FIELD_TYPES)
+@settings(max_examples=25)
+def test_planned_action_rejects_invalid_field_types(
+    field_name: str,
+    error_match: str,
+    value: object,
+) -> None:
+    """Verify PlannedAction rejects arbitrary invalid field types."""
+    kwargs = {
+        "action_id": "a1",
+        "action_kind": ActionKind.GENERATE_SHOW_NOTES,
+        "rationale": "test",
+        "model_tier": ModelTier.EXECUTION,
+        "required_inputs": ("script_tei_xml",),
+    }
+    if field_name == "rationale":
+        kwargs["rationale"] = typ.cast("str", value)
+    else:
+        kwargs["required_inputs"] = typ.cast("tuple[str, ...]", (value,))
+    with pytest.raises(ValueError, match=error_match):
+        PlannedAction(**kwargs)

As per coding guidelines, "**/*.py: Replace duplicate tests with @pytest.mark.parametrize" and "**/test_*.py: ... parametrize broadly".

🤖 Detailed instructions

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @tests/test_generation_orchestration_validation_properties.py around lines 67

  • 125, Collapse duplicated property-specific tests into parametrized variants:
    replace the two whitespace tests
    (test_planned_action_rejects_arbitrary_whitespace_rationale and
    test_planned_action_rejects_arbitrary_whitespace_required_inputs) with a single
    @pytest.mark.parametrize test (e.g.,
    test_planned_action_rejects_arbitrary_whitespace_fields) that accepts field_name
    and error_match, builds a kwargs dict for PlannedAction and injects value into
    either "rationale" or "required_inputs"; do the same for the invalid-type pair
    (test_planned_action_rejects_invalid_rationale_types and
    test_planned_action_rejects_invalid_required_input_types) into one parametrized
    test (e.g., test_planned_action_rejects_invalid_field_types) that casts value
    appropriately and asserts the same ValueError matches, keeping references to
    PlannedAction, "rationale", and "required_inputs" to locate the code.

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd 'test_generation_orchestration_validation_properties.py' --exec cat {}

Length of output: 4470



@leynos Yes, both parametrised variants are fully in place in the current file.

  • test_planned_action_rejects_arbitrary_whitespace_fields (lines 79–99) uses @pytest.mark.parametrize over ("field_name", "error_match") for both rationale and required_inputs, combined with @given(value=_WHITESPACE_STRINGS).
  • test_planned_action_rejects_invalid_field_types (lines 107–130) applies the same parametrisation pattern with @given(value=_INVALID_DTO_FIELD_TYPES).

The four original single-field test functions are gone. The implementation also goes one step further than the proposed refactor by annotating the kwargs dict with _PlannedActionKwargs (a TypedDict), which tightens the type safety on the mutation step.

Both items are resolved. ✅

Replace pre-constrained usage strategies with independent input, output,
and total tuples so property tests can detect incorrect total rollups.
Import the shared `_PlannedActionKwargs` TypedDict in validation property
tests, and derive aggregated `total_tokens` from summed input and output
counts in `_sum_usage` and snapshot fixtures.

Co-authored-by: Cursor <cursoragent@cursor.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

Document why aggregated total_tokens is derived from summed input and
output counts, and emit structured warnings when provider-reported totals
differ at the component or aggregate level.

Co-authored-by: Cursor <cursoragent@cursor.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_orchestration_usage.py`:
- Around line 50-55: The test uses a bare assert comparing aggregate_fields to
the expected dict; change it to include a descriptive assertion message by
updating the assertion for aggregate_fields (the dict comparison) to use
Python's assert ... , "message" form — e.g. assert aggregate_fields == { ... },
"Aggregate fields mismatch: expected X but got Y" — so that failures show a
clear message; reference the existing variable name aggregate_fields and update
that specific assert statement accordingly.
- Line 21: Update the bare assertion that compares total_usage to LLMUsage in
tests/test_orchestration_usage.py to include a descriptive failure message;
specifically modify the assert using the left-hand expression total_usage and
expected LLMUsage(input_tokens=13, output_tokens=12, total_tokens=25) so it
becomes assert <comparison>, "<descriptive message indicating actual vs expected
total_usage or which part failed>" to improve test diagnostics.
- Line 48: The bare assertion in tests/test_orchestration_usage.py uses assert
all(event[0] == "warning" for event in logged_events) without a message; update
that assertion to include a descriptive failure message (e.g., "Expected all
logged events to be warnings, got: {logged_events}") so failures are
informative—modify the assertion in the test function that references
logged_events accordingly.
- Around line 79-80: Replace the two bare asserts in
tests/test_orchestration_usage.py with assertions that include descriptive
messages: for the first assert comparing total_usage to
LLMUsage(input_tokens=13, output_tokens=12, total_tokens=25) add a message like
"unexpected LLM token usage: {total_usage}" and for the second assert checking
logged_events is empty add a message like "expected no logged events but found
{logged_events}"; update the assertions that reference total_usage,
LLMUsage(...) and logged_events accordingly so failures show clear context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6c940578-44b0-4b4f-bb81-e9e738ec2c07

📥 Commits

Reviewing files that changed from the base of the PR and between f472a1b and 60011f7.

📒 Files selected for processing (2)
  • episodic/orchestration/_usage.py
  • tests/test_orchestration_usage.py

Comment thread tests/test_orchestration_usage.py Outdated
Comment thread tests/test_orchestration_usage.py Outdated
Comment thread tests/test_orchestration_usage.py Outdated
Comment thread tests/test_orchestration_usage.py Outdated
Add descriptive failure messages to usage aggregation unit tests so
mismatched totals, warning levels, and aggregate fields are easier to
diagnose.

Co-authored-by: Cursor <cursoragent@cursor.com>
@leynos
leynos merged commit 9adae0a into main May 28, 2026
4 checks passed
@leynos
leynos deleted the issue-73-syrupy-snapshot-tests-for-orchestration-outputs branch May 28, 2026 17:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add syrupy snapshot tests for orchestration structured outputs

1 participant