Skip to content

Refactor budget config parsing and standardize error handling#494

Merged
leynos merged 4 commits intomainfrom
refactor-config-parsing-zfrm9p
Mar 5, 2026
Merged

Refactor budget config parsing and standardize error handling#494
leynos merged 4 commits intomainfrom
refactor-config-parsing-zfrm9p

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Mar 3, 2026

Summary

  • Centralizes budget config parsing via a new helper and type in tests, and standardizes error messages across fixtures.
  • Removes duplicated inline parsing logic from multiple fixtures and replaces with a shared parser.
  • Simplifies and unifies error handling when sending budget updates in tests.

Changes

  • New module: tests/fixtures/budget_config_parser.rs
    • Adds StandardBudgetConfig struct with fields: timeout_ms, per_message, per_connection, in_flight.
    • Adds parse_standard_budget_config(s: &str) -> Result<StandardBudgetConfig, String> to parse a four-segment budget config string separated by '/'.
    • Enforces a consistent error model:
      • missing
      • unexpected trailing segments
      • : <parse_error> for segment parse failures
  • Exposed parser in fixtures: tests/fixtures/mod.rs now mod budget_config_parser;
  • Fixture updates to use the shared parser:
    • budget_cleanup.rs, budget_transitions.rs, memory_budget_backpressure.rs, memory_budget_hard_cap.rs import parse_standard_budget_config and map parsed fields into their respective config structs instead of duplicating parsing logic.
    • FromStr implementations for BudgetCleanup, Transition, Backpressure, and HardCap now rely on parse_standard_budget_config, setting fields directly from the parsed StandardBudgetConfig.
  • Standardized error handling in tests:
    • In BudgetCleanupWorld::|join| or related sending flow, converts complex nested results into a single Result and stores last_send_error consistently as a string when an error occurs, clearing it on success.
    • Uses Into::into to unify error type conversions where applicable.

Implementation details

  • The new parse_standard_budget_config function parses four segments: timeout_ms (u64), per_message (usize), per_connection (usize), in_flight (usize). It validates no trailing segments remain and converts parse errors into unified String messages.
  • All fixture FromStr implementations now call parse_standard_budget_config(s)? and populate the respective config fields from the returned StandardBudgetConfig, reducing duplication and making behavior consistent across fixtures.
  • BudgetCleanupWorld now handles send_result in a single step: result = send_result.and_then(|io| io.map_err(Into::into)); last_send_error is updated based on result, and result is returned.
  • Tests/fixtures/mod.rs updated to publicly expose budget_config_parser so fixtures can reuse it.

Tests plan

  • Run cargo test to ensure all existing tests compile and run with the new shared parser.
  • Validate that missing and trailing segment errors surface with the expected messages (e.g., "missing timeout_ms", "unexpected trailing segments").
  • Ensure last_send_error tracking behaves identically from the test perspective after the refactor.

Possible impacts

  • This is a test/fixture refactor only; production code paths are unaffected.
  • Error messages for parsing are retained in a standardized form to avoid tests breaking due to ad-hoc strings.
  • If future tests relied on ad-hoc parsing logic, they should migrate to using the shared budget_config_parser.

How to run

  • cargo test
  • Optionally run specific test binaries that rely on budget fixtures to verify behavior in isolation.

📎 Task: https://www.devboxer.com/task/7cb7ad67-23ca-481b-9138-dbf1eeda61c6

…and clarity

Refactored the parsing of budget configuration strings in multiple test fixtures to use a shared helper function `parse_standard_budget_config`. Extracted this parser to a new module `budget_config_parser`, consolidating duplicated parsing logic from budget_cleanup.rs, budget_transitions.rs, memory_budget_backpressure.rs, and memory_budget_hard_cap.rs. Also improved error handling and code clarity in BudgetCleanupWorld by simplifying result assignment of send operations. Updated mod.rs to include the new module.

This change reduces code duplication, improves maintainability, and centralizes standard budget config parsing logic used across budget-related test fixtures.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Mar 3, 2026

Reviewer's Guide

Refactors test budget fixtures to share a common parser for four-part budget configuration strings and unifies send-error handling in BudgetCleanupWorld, reducing duplicated parsing logic and standardizing error messages across tests.

File-Level Changes

Change Details Files
Introduce shared StandardBudgetConfig type and parser for four-part budget configuration strings used in test fixtures.
  • Add StandardBudgetConfig struct encapsulating timeout_ms, per_message, per_connection, and in_flight fields.
  • Implement parse_standard_budget_config to split on '/', validate presence of four non-empty segments, reject trailing segments, and parse each segment with consistent error messages.
  • Provide helper functions next_non_empty and parse_segment to centralize missing-field and parse-failure message formatting.
tests/fixtures/budget_config_parser.rs
Update budget fixtures to use the shared parser instead of duplicating parsing logic in FromStr implementations.
  • Import parse_standard_budget_config into budget_cleanup, budget_transitions, memory_budget_backpressure, and memory_budget_hard_cap fixtures.
  • Replace inline split-and-parse logic in each FromStr implementation with a single call to parse_standard_budget_config and field mapping from StandardBudgetConfig.
  • Remove local parsing helpers and error formatting in fixtures in favor of the centralized parser behavior.
tests/fixtures/budget_cleanup.rs
tests/fixtures/budget_transitions.rs
tests/fixtures/memory_budget_backpressure.rs
tests/fixtures/memory_budget_hard_cap.rs
Simplify and standardize error handling for sending budget updates in BudgetCleanupWorld.
  • Replace nested match over send_result with a composed result using and_then and Into::into for unified error conversion.
  • Update last_send_error tracking to set a single error string on failure and clear it on success, based solely on the final Result.
  • Return the composed result directly from the method to keep control flow linear.
tests/fixtures/budget_cleanup.rs
Expose the shared budget config parser within the fixtures module for reuse.
  • Add the budget_config_parser module under tests/fixtures and make it available to other fixtures via the module tree.
  • Adjust fixtures module declarations so other test fixtures can import parse_standard_budget_config using super::budget_config_parser.
tests/fixtures/mod.rs

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 456fc062-8181-45f9-b8ab-d636167ee1ed

📥 Commits

Reviewing files that changed from the base of the PR and between 26a70bd and c4be944.

📒 Files selected for processing (1)
  • tests/fixtures/budget_config_parser.rs

Summary by CodeRabbit

  • Refactor

    • Centralised parsing logic in test fixtures to reduce duplication and simplify parsing across multiple budget-related tests.
    • Consolidated send-result handling into a single helper to unify success/error pathways.
  • Tests

    • Added tests to verify send-result handling: error recording, clearing on success, and correct propagation.
    • Added comprehensive parsing tests covering valid inputs, missing/trailing segments and per-field parse errors.
  • New

    • Introduced a reusable standard budget parser and data type within the test fixtures.

Walkthrough

Centralise four-segment budget string parsing into a new test-only parser and replace inline parsing in multiple test fixtures with calls to parse_standard_budget_config. Add StandardBudgetConfig and unit tests. Add BudgetCleanupWorld::record_send_result to normalise send outcomes and update last_send_error.

Changes

Cohort / File(s) Summary
New parser module
tests/fixtures/budget_config_parser.rs
Add public StandardBudgetConfig and parse_standard_budget_config(s: &str) -> Result<StandardBudgetConfig, String) with helpers (next_non_empty, parse_segment) and unit tests covering valid input, missing segments, trailing segments, and parse errors.
Module integration
tests/fixtures/mod.rs
Add private module declaration mod budget_config_parser; to expose the new parser to other fixtures.
Refactored fixtures
tests/fixtures/budget_transitions.rs, tests/fixtures/memory_budget_backpressure.rs, tests/fixtures/memory_budget_hard_cap.rs
Replace ad-hoc FromStr/manual parsing logic with calls to parse_standard_budget_config and map returned fields into existing config structs; remove duplicated per-field extraction and trailing-segment validation.
Test helper behaviour
tests/fixtures/budget_cleanup.rs
Add BudgetCleanupWorld::record_send_result to centralise handling of nested send Results, convert them into a unified TestResult, update last_send_error, and return the processed result; update call sites to use this helper and add tests validating clearing, storing and conversion of send errors.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A tiny parser springs to life,
Four values march from text to type,
Fixtures call the single source,
Send-results channel a steadier course,
Tests applaud — the routes are bright.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main refactoring effort: centralizing budget config parsing and standardising error handling across multiple test fixtures.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the new shared parser, fixture updates, error handling standardisation, and test coverage.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-config-parsing-zfrm9p

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

@leynos leynos marked this pull request as ready for review March 4, 2026 11:22
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="tests/fixtures/budget_cleanup.rs" line_range="271-277" />
<code_context>
-                self.last_send_error = Some(error.to_string());
-                Err(error)
-            }
+        let result = send_result.and_then(|io_result| io_result.map_err(Into::into));
+        if let Err(ref error) = result {
+            self.last_send_error = Some(error.to_string());
+        } else {
+            self.last_send_error = None;
         }
+        result
     }

</code_context>
<issue_to_address>
**suggestion (testing):** Add regression tests for `last_send_error` behavior across success and both error paths

Since the three prior branches are now unified, please add focused tests for `BudgetCleanupWorld` (or the relevant abstraction) that verify:
- A successful send after a failure clears `last_send_error`.
- An outer `Err(error)` from the send call sets `last_send_error` to the correct string and returns the expected error type.
- An inner `Ok(Err(io_error))` sets `last_send_error` and returns the converted error.
These tests should assert the exact stored string value, not just `Some/None`, to lock in the observable behavior for future refactors.

Suggested implementation:

```rust
const ROUTE_ID: u32 = 92;
const CORRELATION_ID: Option<u64> = Some(21);
const BUFFER_CAPACITY: usize = 512;

#[cfg(test)]
mod last_send_error_tests {
    use super::*;
    use std::io;

    /// Helper to construct a world instance.
    ///
    /// Adjust this to use the real constructor/new/builder used in the tests.
    fn make_world() -> BudgetCleanupWorld {
        // If `BudgetCleanupWorld` already has a constructor in this module,
        // replace this with that call (e.g. `BudgetCleanupWorld::new(...)`).
        BudgetCleanupWorld::default()
    }

    /// Helper to invoke the method that wraps `send_result.and_then(...)`.
    ///
    /// Adjust the name and parameters to match the real API under test.
    fn perform_send(
        world: &mut BudgetCleanupWorld,
        send_result: Result<Result<(), io::Error>, crate::Error>,
    ) -> Result<(), crate::Error> {
        // This should call the function/method that contains:
        //   let result = send_result.and_then(|io_result| io_result.map_err(Into::into));
        //
        // For example, if the method is:
        //   impl BudgetCleanupWorld {
        //       fn send_cleanup(&mut self, send_result: Result<Result<(), io::Error>, Error>) -> Result<(), Error> { ... }
        //   }
        // then call that here.
        world.send_cleanup(send_result)
    }

    #[test]
    fn last_send_error_is_cleared_on_success_after_failure() {
        let mut world = make_world();

        //
        // 1. Induce a failure to populate `last_send_error`
        //
        let io_error = io::Error::new(io::ErrorKind::Other, "first failure");
        let converted_error: crate::Error = io_error.into();
        let send_result = Ok::<Result<(), io::Error>, crate::Error>(Err(io_error));
        let result = perform_send(&mut world, send_result);

        assert!(result.is_err(), "expected initial send to fail");
        let expected_first = converted_error.to_string();
        assert_eq!(
            world.last_send_error.as_deref(),
            Some(expected_first.as_str()),
            "expected last_send_error to be populated after failure"
        );

        //
        // 2. Follow with a successful send, which should clear `last_send_error`
        //
        let success_send_result =
            Ok::<Result<(), io::Error>, crate::Error>(Ok::<(), io::Error>(()));
        let success_result = perform_send(&mut world, success_send_result);

        assert!(success_result.is_ok(), "expected second send to succeed");
        assert_eq!(
            world.last_send_error, None,
            "expected last_send_error to be cleared after a successful send"
        );
    }

    #[test]
    fn last_send_error_is_set_for_outer_error_from_send_call() {
        let mut world = make_world();

        // Create a deterministic outer error.
        // Replace `crate::Error::SendFailed("...".into())` with the concrete
        // constructor used by your error type.
        let outer_error = crate::Error::SendFailed("outer failure".into());

        let send_result: Result<Result<(), io::Error>, crate::Error> = Err(outer_error.clone());

        let result = perform_send(&mut world, send_result);

        // Verify the returned error matches the outer error.
        let returned_err = result.expect_err("expected outer error from send call");
        assert_eq!(
            format!("{returned_err}"),
            format!("{outer_error}"),
            "expected returned error to equal the outer send error"
        );

        // Verify the exact stored string.
        let expected = outer_error.to_string();
        assert_eq!(
            world.last_send_error.as_deref(),
            Some(expected.as_str()),
            "expected last_send_error to store the outer error string"
        );
    }

    #[test]
    fn last_send_error_is_set_for_inner_io_error_and_error_is_converted() {
        let mut world = make_world();

        // Construct a deterministic IO error.
        let io_error = io::Error::new(io::ErrorKind::Other, "inner io failure");
        let io_error_clone_for_string = io::Error::new(io::ErrorKind::Other, "inner io failure");

        let send_result: Result<Result<(), io::Error>, crate::Error> =
            Ok::<Result<(), io::Error>, crate::Error>(Err(io_error));

        let result = perform_send(&mut world, send_result);

        // The inner `io::Error` should have been converted into `crate::Error`
        // via `Into::into`.
        let converted_error: crate::Error = io_error_clone_for_string.into();
        let returned_err = result.expect_err("expected converted inner IO error");
        assert_eq!(
            format!("{returned_err}"),
            format!("{converted_error}"),
            "expected returned error to be the converted IO error"
        );

        // And `last_send_error` should store the converted error's string.
        let expected = converted_error.to_string();
        assert_eq!(
            world.last_send_error.as_deref(),
            Some(expected.as_str()),
            "expected last_send_error to store the converted IO error string"
        );
    }
}

    type Err = String;

```

These edits assume the following, which you will likely need to align with the actual code:

1. `BudgetCleanupWorld`
   - Exists in this module and is `Default`-constructible.  
     If not, replace `BudgetCleanupWorld::default()` in `make_world()` with the real constructor or builder used in your tests.

2. Method under test
   - I used `world.send_cleanup(send_result)` in `perform_send()` as the wrapper for the new logic:
     ```rust
     let result = send_result.and_then(|io_result| io_result.map_err(Into::into));
     if let Err(ref error) = result { ... } else { ... }
     ```
   - Replace `send_cleanup` with the actual method name and signature that contains that logic, and adjust the parameter types in `perform_send()` to match.

3. Error types
   - I referenced `crate::Error` and a variant `Error::SendFailed(String)` as an example.
   - Replace `crate::Error` and the construction of `outer_error`/`converted_error` with your actual error type and constructors.
   - Make sure the `send_result` type in tests matches the real `send_result` type in the implementation (outer `Err` type and inner `io::Error` vs your concrete types).

4. Field visibility
   - These tests access `world.last_send_error` directly. If `last_send_error` is private or behind an accessor, either:
     - Move these tests into the same module as `BudgetCleanupWorld`’s impl so they can see private fields, or
     - Replace direct field access with the appropriate getter (e.g. `world.last_send_error()`).

Once you align the constructor, method name/signature, and error types with your real implementation, these tests will lock in the `last_send_error` behavior for all three code paths and assert the exact stored error strings.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread tests/fixtures/budget_cleanup.rs
Copy link
Copy Markdown
Contributor

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/fixtures/budget_config_parser.rs`:
- Around line 17-50: Add a #[cfg(test)] mod tests in the same file and implement
unit tests that exercise parse_standard_budget_config for the four cases: valid
input "200/2048/8/8" asserting each field, missing-segment input "10/64/100"
asserting the error equals "missing in_flight", trailing-segment input
"10/64/100/100/extra" asserting the error equals "unexpected trailing segments",
and a parse-failure like "abc/64/100/100" asserting the error starts with
"timeout_ms: ". Use the existing parse_standard_budget_config function name and
rely on its current error strings (next_non_empty produces "missing {name}",
trailing check returns "unexpected trailing segments", and parse_segment
produces "{name}: {error}") so tests pin those messages.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8ebab34b-f903-4b4c-8210-78a538d64ffc

📥 Commits

Reviewing files that changed from the base of the PR and between 6327c67 and db041be.

📒 Files selected for processing (6)
  • tests/fixtures/budget_cleanup.rs
  • tests/fixtures/budget_config_parser.rs
  • tests/fixtures/budget_transitions.rs
  • tests/fixtures/memory_budget_backpressure.rs
  • tests/fixtures/memory_budget_hard_cap.rs
  • tests/fixtures/mod.rs

Comment thread tests/fixtures/budget_config_parser.rs
Added comprehensive tests for the BudgetCleanupWorld's send result handling, verifying error recording and clearing. Also included multiple tests for StandardBudgetConfig parsing, checking valid inputs, missing segments, trailing segments, and parse failures with field names.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Mar 4, 2026

@coderabbitai Have the following now been resolved?

Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="tests/fixtures/budget_cleanup.rs" line_range="271-277" />
<code_context>
-                self.last_send_error = Some(error.to_string());
-                Err(error)
-            }
+        let result = send_result.and_then(|io_result| io_result.map_err(Into::into));
+        if let Err(ref error) = result {
+            self.last_send_error = Some(error.to_string());
+        } else {
+            self.last_send_error = None;
         }
+        result
     }

</code_context>
<issue_to_address>
**suggestion (testing):** Add regression tests for `last_send_error` behavior across success and both error paths

Since the three prior branches are now unified, please add focused tests for `BudgetCleanupWorld` (or the relevant abstraction) that verify:
- A successful send after a failure clears `last_send_error`.
- An outer `Err(error)` from the send call sets `last_send_error` to the correct string and returns the expected error type.
- An inner `Ok(Err(io_error))` sets `last_send_error` and returns the converted error.
These tests should assert the exact stored string value, not just `Some/None`, to lock in the observable behavior for future refactors.

Suggested implementation:

```rust
const ROUTE_ID: u32 = 92;
const CORRELATION_ID: Option<u64> = Some(21);
const BUFFER_CAPACITY: usize = 512;

#[cfg(test)]
mod last_send_error_tests {
    use super::*;
    use std::io;

    /// Helper to construct a world instance.
    ///
    /// Adjust this to use the real constructor/new/builder used in the tests.
    fn make_world() -> BudgetCleanupWorld {
        // If `BudgetCleanupWorld` already has a constructor in this module,
        // replace this with that call (e.g. `BudgetCleanupWorld::new(...)`).
        BudgetCleanupWorld::default()
    }

    /// Helper to invoke the method that wraps `send_result.and_then(...)`.
    ///
    /// Adjust the name and parameters to match the real API under test.
    fn perform_send(
        world: &mut BudgetCleanupWorld,
        send_result: Result<Result<(), io::Error>, crate::Error>,
    ) -> Result<(), crate::Error> {
        // This should call the function/method that contains:
        //   let result = send_result.and_then(|io_result| io_result.map_err(Into::into));
        //
        // For example, if the method is:
        //   impl BudgetCleanupWorld {
        //       fn send_cleanup(&mut self, send_result: Result<Result<(), io::Error>, Error>) -> Result<(), Error> { ... }
        //   }
        // then call that here.
        world.send_cleanup(send_result)
    }

    #[test]
    fn last_send_error_is_cleared_on_success_after_failure() {
        let mut world = make_world();

        //
        // 1. Induce a failure to populate `last_send_error`
        //
        let io_error = io::Error::new(io::ErrorKind::Other, "first failure");
        let converted_error: crate::Error = io_error.into();
        let send_result = Ok::<Result<(), io::Error>, crate::Error>(Err(io_error));
        let result = perform_send(&mut world, send_result);

        assert!(result.is_err(), "expected initial send to fail");
        let expected_first = converted_error.to_string();
        assert_eq!(
            world.last_send_error.as_deref(),
            Some(expected_first.as_str()),
            "expected last_send_error to be populated after failure"
        );

        //
        // 2. Follow with a successful send, which should clear `last_send_error`
        //
        let success_send_result =
            Ok::<Result<(), io::Error>, crate::Error>(Ok::<(), io::Error>(()));
        let success_result = perform_send(&mut world, success_send_result);

        assert!(success_result.is_ok(), "expected second send to succeed");
        assert_eq!(
            world.last_send_error, None,
            "expected last_send_error to be cleared after a successful send"
        );
    }

    #[test]
    fn last_send_error_is_set_for_outer_error_from_send_call() {
        let mut world = make_world();

        // Create a deterministic outer error.
        // Replace `crate::Error::SendFailed("...".into())` with the concrete
        // constructor used by your error type.
        let outer_error = crate::Error::SendFailed("outer failure".into());

        let send_result: Result<Result<(), io::Error>, crate::Error> = Err(outer_error.clone());

        let result = perform_send(&mut world, send_result);

        // Verify the returned error matches the outer error.
        let returned_err = result.expect_err("expected outer error from send call");
        assert_eq!(
            format!("{returned_err}"),
            format!("{outer_error}"),
            "expected returned error to equal the outer send error"
        );

        // Verify the exact stored string.
        let expected = outer_error.to_string();
        assert_eq!(
            world.last_send_error.as_deref(),
            Some(expected.as_str()),
            "expected last_send_error to store the outer error string"
        );
    }

    #[test]
    fn last_send_error_is_set_for_inner_io_error_and_error_is_converted() {
        let mut world = make_world();

        // Construct a deterministic IO error.
        let io_error = io::Error::new(io::ErrorKind::Other, "inner io failure");
        let io_error_clone_for_string = io::Error::new(io::ErrorKind::Other, "inner io failure");

        let send_result: Result<Result<(), io::Error>, crate::Error> =
            Ok::<Result<(), io::Error>, crate::Error>(Err(io_error));

        let result = perform_send(&mut world, send_result);

        // The inner `io::Error` should have been converted into `crate::Error`
        // via `Into::into`.
        let converted_error: crate::Error = io_error_clone_for_string.into();
        let returned_err = result.expect_err("expected converted inner IO error");
        assert_eq!(
            format!("{returned_err}"),
            format!("{converted_error}"),
            "expected returned error to be the converted IO error"
        );

        // And `last_send_error` should store the converted error's string.
        let expected = converted_error.to_string();
        assert_eq!(
            world.last_send_error.as_deref(),
            Some(expected.as_str()),
            "expected last_send_error to store the converted IO error string"
        );
    }
}

    type Err = String;

```

These edits assume the following, which you will likely need to align with the actual code:

1. `BudgetCleanupWorld`
   - Exists in this module and is `Default`-constructible.  
     If not, replace `BudgetCleanupWorld::default()` in `make_world()` with the real constructor or builder used in your tests.

2. Method under test
   - I used `world.send_cleanup(send_result)` in `perform_send()` as the wrapper for the new logic:
     ```rust
     let result = send_result.and_then(|io_result| io_result.map_err(Into::into));
     if let Err(ref error) = result { ... } else { ... }
     ```
   - Replace `send_cleanup` with the actual method name and signature that contains that logic, and adjust the parameter types in `perform_send()` to match.

3. Error types
   - I referenced `crate::Error` and a variant `Error::SendFailed(String)` as an example.
   - Replace `crate::Error` and the construction of `outer_error`/`converted_error` with your actual error type and constructors.
   - Make sure the `send_result` type in tests matches the real `send_result` type in the implementation (outer `Err` type and inner `io::Error` vs your concrete types).

4. Field visibility
   - These tests access `world.last_send_error` directly. If `last_send_error` is private or behind an accessor, either:
     - Move these tests into the same module as `BudgetCleanupWorld`’s impl so they can see private fields, or
     - Replace direct field access with the appropriate getter (e.g. `world.last_send_error()`).

Once you align the constructor, method name/signature, and error types with your real implementation, these tests will lock in the `last_send_error` behavior for all three code paths and assert the exact stored error strings.
</issue_to_address>

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 4, 2026

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the test module(s) from tests/fixtures/budget_cleanup.rs
fd -t f 'budget_cleanup.rs' --exec cat {}

Length of output: 12978


@leynos: Yes, the comment has been fully resolved. The tests module at the bottom of tests/fixtures/budget_cleanup.rs now covers all three paths with exact string assertions:

Test Path covered Exact string asserted
record_send_result_clears_last_error_after_success Inner io::Error → failure, then Ok(Ok(())) → clears error "io error: first failure" then None
record_send_result_stores_outer_error_string Outer Err "io error: outer failure"
record_send_result_converts_inner_io_error_and_stores_it Inner Ok(Err(io_error)) "io error: inner io failure"

This satisfies the original suggestion's requirement to lock in observable behaviour for all three code paths via exact string comparisons.

…config error messages

- Removed multiple detailed error message checks for each missing segment and parse failure
- Consolidated tests to focus on key error scenarios, improving test clarity
- Updated test names and inputs to reflect current expected outputs

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos leynos changed the title Refactor budget config parsing and unify error handling Refactor budget config parsing and standardize error handling Mar 4, 2026
Copy link
Copy Markdown
Contributor

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/fixtures/budget_config_parser.rs`:
- Around line 9-12: The public struct StandardBudgetConfig exposes fields
timeout_ms, per_message, per_connection, and in_flight without documentation;
add Rustdoc comments (///) above each public field in the StandardBudgetConfig
definition describing what the field represents, its units/range (e.g.,
timeout_ms in milliseconds), and any behavioral notes so generated cargo doc API
pages have clear field-level descriptions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f54861c8-a319-433b-b730-b61666395e45

📥 Commits

Reviewing files that changed from the base of the PR and between befd866 and 26a70bd.

📒 Files selected for processing (1)
  • tests/fixtures/budget_config_parser.rs

Comment thread tests/fixtures/budget_config_parser.rs
… struct

Added descriptive doc comments for each field in the StandardBudgetConfig struct within the test fixture to clarify their purpose and constraints, improving code readability and maintainability.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos leynos merged commit 011f250 into main Mar 5, 2026
6 checks passed
@leynos leynos deleted the refactor-config-parsing-zfrm9p branch March 5, 2026 13:36
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.

1 participant