Skip to content

Adopt new ortho_config merge API - #147

Merged
leynos merged 13 commits into
mainfrom
codex/update-imports-and-method-calls-after-upgrade
Sep 21, 2025
Merged

Adopt new ortho_config merge API#147
leynos merged 13 commits into
mainfrom
codex/update-imports-and-method-calls-after-upgrade

Conversation

@leynos

@leynos leynos commented Sep 19, 2025

Copy link
Copy Markdown
Owner

Summary

  • update the CLI to use SubcmdConfigMerge::load_and_merge after upgrading ortho_config
  • adjust subcommand merge tests to call the new helper method

Testing

  • make test

https://chatgpt.com/codex/tasks/task_e_68ccb4884d2083229ad87e863f89441e

Summary by Sourcery

Adopt the new ortho_config merge API for subcommands and centralize environment handling. Replace all raw std::env calls with a synchronized environment helper, refactor test utilities, and expand merge tests for full CLI/env/config precedence across pr, issue, and resolve commands.

New Features:

  • Introduce vk::environment module to provide thread-safe wrappers around environment variable access.

Enhancements:

  • Replace load_and_merge_subcommand_for with SubcmdConfigMerge::load_and_merge in the main CLI and subcommand tests.
  • Update all code and tests to use environment::var, environment::set_var, and environment::remove_var instead of raw std::env calls.
  • Refactor and consolidate subcommand merge helpers into reusable support modules under tests/support.

Tests:

  • Extend subcommand merge tests to cover the resolve command and all CLI/environment/config precedence scenarios.
  • Add a new cli_args_merge.rs suite for behavioral tests of pr, issue, and resolve argument merging.

Chores:

  • Remove legacy environment lock code and obsolete merge helper functions.

@sourcery-ai

sourcery-ai Bot commented Sep 19, 2025

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR upgrades the CLI to leverage the new SubcmdConfigMerge API by replacing standalone merge helper calls with the load_and_merge method on subcommands, centralizes environment variable handling behind a synchronized environment module, and refactors the test suite to use newly introduced support helpers and restructure merge tests for better coverage.

Sequence diagram for CLI subcommand config merging with new API

sequenceDiagram
    participant Main
    participant PrCli
    participant SubcmdConfigMerge
    Main->>PrCli: call load_and_merge()
    PrCli->>SubcmdConfigMerge: load_and_merge()
    SubcmdConfigMerge-->>PrCli: merged config
    PrCli-->>Main: merged config
Loading

Sequence diagram for synchronized environment variable access

sequenceDiagram
    participant Caller
    participant environment
    participant ENV_LOCK
    Caller->>environment: set_var(key, value)
    environment->>ENV_LOCK: acquire lock
    ENV_LOCK-->>environment: lock acquired
    environment->>std::env: set_var(key, value)
    environment-->>Caller: done
Loading

Class diagram for SubcmdConfigMerge API adoption

classDiagram
    class SubcmdConfigMerge {
        +load_and_merge()
    }
    class PrCli {
        +load_and_merge()
    }
    class IssueCli {
        +load_and_merge()
    }
    class ResolveCli {
        +load_and_merge()
    }
    PrCli --|> SubcmdConfigMerge : implements
    IssueCli --|> SubcmdConfigMerge : implements
    ResolveCli --|> SubcmdConfigMerge : implements
Loading

File-Level Changes

Change Details Files
Adopt new SubcmdConfigMerge API for loading and merging subcommand configurations
  • Replace load_and_merge_subcommand_for(cli) with cli.load_and_merge() in main
  • Update subcommand match arms in main.rs to use the new helper method
  • Adjust subcommand_merge tests to call load_and_merge on CLI structs
src/main.rs
tests/subcommand_merge.rs
Centralize environment variable access using environment module
  • Introduce src/environment.rs with synchronized var, set_var, remove_var helpers
  • Replace std::env::var calls with environment::var in runtime and API code
  • Refactor test_utils and test_utils_env modules to delegate to environment helpers
src/environment.rs
src/main.rs
src/test_utils_env.rs
src/test_utils.rs
src/api/mod.rs
src/resolve/rest.rs
tests/subcommand_merge.rs
Refactor and restructure configuration merge tests with support modules
  • Extract merge helpers, environment guards, and directory guards into tests/support/env.rs and subcommand.rs
  • Remove duplicated setup code in subcommand_merge.rs and import support modules
  • Add comprehensive behavioral tests in tests/cli_args_merge.rs covering CLI, env, and config precedence
tests/subcommand_merge.rs
tests/cli_args_merge.rs
tests/support/env.rs
tests/support/subcommand.rs

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

@coderabbitai

coderabbitai Bot commented Sep 19, 2025

Copy link
Copy Markdown

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

  • New Features

    • None; no changes to commands, flags, or outputs.
  • Refactor

    • Streamlined how subcommands load and merge configuration, with no impact on behaviour.
  • Tests

    • Added comprehensive precedence tests covering CLI, environment, and config across PR, Issue, and Resolve subcommands.
    • Introduced reusable test utilities for environment/config setup and subcommand scaffolding.
  • Chores

    • Improved test isolation and reliability via environment and working-directory guards.

Walkthrough

Replace the free-function subcommand config merge with an inherent load_and_merge method on CLI arg types. Update src/main.rs to import SubcmdConfigMerge and call load_and_merge() for PrArgs, IssueArgs and ResolveArgs. Add and refactor tests and support utilities to validate CLI/env/config precedence, including Resolve.

Changes

Cohort / File(s) Summary
Main entrypoint
src/main.rs
Replace calls to load_and_merge_subcommand_for(...) with inherent load_and_merge() on PrArgs, IssueArgs, and ResolveArgs. Import SubcmdConfigMerge. No runner logic changed.
Subcommand merge tests
tests/subcommand_merge.rs
Refactor tests to use shared support helpers; add Resolve variant and precedence cases; introduce PrMergeTestCase; remove in-file scaffolding and free-function merge usage.
New CLI args merge tests
tests/cli_args_merge.rs
Add full test module exercising precedence (CLI > env > config > defaults) for pr, issue and resolve; provide scenario scaffolding, fixtures, helpers and assertions for merged fields and CLI instance preservation.
Test env support
tests/support/env.rs
Add EnvGuard and DirGuard RAII helpers plus write_config and setup_env_and_config to isolate environment and manage temporary config files for tests.
Test subcommand support
tests/support/subcommand.rs
Add merge_with_sources orchestrator that applies config and env and calls cli.load_and_merge(); add pr_cli, issue_cli, resolve_cli constructors for building test CLI args.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant CLI as CLI Parser
  participant Args as <cmd>_cli (Args)
  participant Cfg as Config Loader
  participant Env as Environment
  participant Run as Runner

  U->>CLI: Invoke subcommand (pr / issue / resolve)
  CLI->>Args: Construct CLI args struct
  Args->>Args: call load_and_merge()
  par Read sources
    Args->>Cfg: Load .vk.toml and config sections
    Args->>Env: Read environment variables
  end
  Args-->>CLI: Return merged args
  CLI->>Run: run_<cmd>(merged args)
  Run-->>U: Produce output
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

Free function bows, methods take the stage,
Subcommands merge with orderly sage.
Env whispers, config hums in tune,
CLI leads, decisions come soon.
Tests parade by, resolve joins the show— 🎻✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed State that the title succinctly identifies the primary change — adopting the new ortho_config merge API which is the central modification visible in main.rs and the tests. Emphasise that the title is concise, focused, and avoids noise or vague phrasing. Confirm that it clearly communicates intent for a teammate scanning the project history. Recommend keeping the title as-is.
Description Check ✅ Passed State that the pull request description directly describes the changeset by noting the switch to SubcmdConfigMerge::load_and_merge and the accompanying test updates, and that it includes brief testing instructions. Emphasise that the description is on-topic and sufficiently related to the changes to pass this lenient check. Confirm that the level of detail is acceptable for reviewers seeking context.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

codescene-delta-analysis[bot]

This comment was marked as outdated.

@sourcery-ai sourcery-ai 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.

Hey there - I've reviewed your changes and found some issues that need to be addressed.

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

## Individual Comments

### Comment 1
<location> `src/main.rs:438` </location>
<code_context>
     let result = match cli.command {
         Commands::Pr(pr_cli) => {
-            let args = load_and_merge_subcommand_for(&pr_cli)?;
+            let args = pr_cli.load_and_merge()?;
             run_pr(args, &global).await
         }
</code_context>

<issue_to_address>
**issue (review_instructions):** Add behavioural and unit tests for the new load_and_merge method on pr_cli.

You replaced load_and_merge_subcommand_for with pr_cli.load_and_merge, which is a change to an existing feature. Ensure that both behavioural and unit tests cover the new method's functionality and edge cases.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</issue_to_address>

### Comment 2
<location> `src/main.rs:442` </location>
<code_context>
         }
         Commands::Issue(issue_cli) => {
-            let args = load_and_merge_subcommand_for(&issue_cli)?;
+            let args = issue_cli.load_and_merge()?;
             run_issue(args, &global).await
         }
</code_context>

<issue_to_address>
**issue (review_instructions):** Add behavioural and unit tests for the new load_and_merge method on issue_cli.

You replaced load_and_merge_subcommand_for with issue_cli.load_and_merge, which is a change to an existing feature. Ensure that both behavioural and unit tests cover the new method's functionality and edge cases.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</issue_to_address>

### Comment 3
<location> `src/main.rs:446` </location>
<code_context>
         }
         Commands::Resolve(resolve_cli) => {
-            let args = load_and_merge_subcommand_for(&resolve_cli)?;
+            let args = resolve_cli.load_and_merge()?;
             run_resolve(args, &global).await
         }
</code_context>

<issue_to_address>
**issue (review_instructions):** Add behavioural and unit tests for the new load_and_merge method on resolve_cli.

You replaced load_and_merge_subcommand_for with resolve_cli.load_and_merge, which is a change to an existing feature. Ensure that both behavioural and unit tests cover the new method's functionality and edge cases.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</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 src/main.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 517a3ae and b2221fc.

📒 Files selected for processing (2)
  • src/main.rs (2 hunks)
  • tests/subcommand_merge.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clarity over cleverness: favour explicit, readable Rust over terse or obscure idioms
Use functions and composition; extract reusable logic; prefer declarative iterator-based code when readable
Keep functions small, single-responsibility, and obey command/query segregation
Name things precisely; use is/has/should prefixes for booleans
Use consistent en-GB-oxendict spelling and grammar in code comments (except external API references)
Function documentation must include clear examples; omit redundant examples in test docs
Keep code files ≤ 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; fix warnings in code instead of silencing
Extract helper functions when functions are too long; maintain separation of concerns and CQRS
Group numerous related parameters into meaningful structs
Consider using Arc for large error values to reduce returned data size
Begin every module with a module-level //! comment describing purpose and utility
Document public APIs with /// Rustdoc so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut
Use Result for error handling; avoid panicking where feasible
Avoid unsafe unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
Scope lint suppressions narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint suppression
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of backslash newline escapes
Prefer semantic error enums deriving std::error::Error via thiserror for caller-inspectable conditions
Never export eyre::Report from libraries; convert to domain error enums at API boundaries; use eyre only in main()/top-level async task

Files:

  • tests/subcommand_merge.rs
  • src/main.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in tests ...

Files:

  • tests/subcommand_merge.rs
  • src/main.rs
{src,tests}/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

{src,tests}/**/*.rs: Write unit and behavioural tests for new functionality; run both before and after changes
Use an opaque error (eyre::Report) only at app boundary; not in public APIs

Files:

  • tests/subcommand_merge.rs
  • src/main.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs

Files:

  • tests/subcommand_merge.rs
🧬 Code graph analysis (1)
src/main.rs (1)
tests/subcommand_merge.rs (2)
  • pr_cli (67-76)
  • issue_cli (78-84)
🪛 GitHub Actions: CI
tests/subcommand_merge.rs

[error] 52-56: Rustfmt formatting check failed. Detected formatting difference in tests/subcommand_merge.rs:52. Run 'cargo fmt' to reformat the file.

🔍 Remote MCP Deepwiki

Summary — additional repository context relevant to review

  • src/main.rs defines load_with_reference_fallback(cli_args: T) that currently calls load_and_merge_subcommand_for::(&cli_args) and falls back to returning the original cli_args when OrthoError::Gathering contains a missing-reference error. Verify the new SubcmdConfigMerge::load_and_merge preserves this fallback behavior.
    Snippet (from repo search):
    fn load_with_reference_fallback(cli_args: T) -> Result<T, OrthoError> where T: OrthoConfig + ... { match load_and_merge_subcommand_for::(&cli_args) { Ok(v) => Ok(v), Err(OrthoError::Gathering(e)) => { if missing_reference(&e) { Ok(cli_args) } else { Err(OrthoError::Gathering(e)) } } Err(e) => Err(e), } }

  • The main command dispatch currently uses load_with_reference_fallback for PR and Issue subcommands before calling the runners; confirm the PR’s replacement (pr_cli.load_and_merge(), issue_cli.load_and_merge()) is used at the same call sites and that error propagation/await semantics are unchanged:
    Commands::Pr(pr_cli) => { let args = load_with_reference_fallback::(pr_cli.clone())?; run_pr(args, &global).await }
    Commands::Issue(issue_cli) => { let args = load_with_reference_fallback::(issue_cli.clone())?; run_issue(args, &global).await }

  • The trait SubcmdConfigMerge was not found in this repository (it appears to come from the external ortho_config crate). As part of the review, ensure the crate dependency was bumped/updated so SubcmdConfigMerge::load_and_merge is available and that the CLI arg types (PrArgs, IssueArgs, ResolveArgs) implement/derive whatever ortho_config requires (signatures/Result types must match expected usage).

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (4)
tests/subcommand_merge.rs (2)

9-9: Import the trait to enable method resolution.

Keep SubcmdConfigMerge in scope so .load_and_merge() is available on the CLI types.


52-56: Fix rustfmt failure in tests/subcommand_merge.rs

Run cargo fmt --all and commit the reformatted file to unblock CI.

File: tests/subcommand_merge.rs (lines 52-56)

        }
    }

    let merged = cli.load_and_merge()
        .unwrap_or_else(|err| panic!("merge {} args: {err}", std::any::type_name::<T>()));
src/main.rs (2)

57-57: Import the new merge API traits.

This enables the trait-based .load_and_merge() usage below.


438-447: Verify missing-reference fallback semantics are preserved.

The previous load_with_reference_fallback path returned the original CLI args when config gathering failed due to a missing reference. Confirm .load_and_merge() now embeds that behaviour; if not, reintroduce an equivalent wrapper and update these call sites to use it.

#!/bin/bash
set -euo pipefail
# Inspect whether the old fallback helper or equivalent logic still exists
rg -n 'load_with_reference_fallback|missing_reference|OrthoError::Gathering' -C2 src

Comment thread tests/subcommand_merge.rs Outdated
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
tests/subcommand_merge.rs (1)

39-42: Tighten the generic bound with SubcmdConfigMerge.

Add SubcmdConfigMerge to the where‑clause so the call site’s requirements are explicit and Clippy errors are clearer. This repeats prior feedback.

 fn merge_with_sources<T>(config: &str, env: &[(&str, Option<&str>)], cli: &T) -> T
 where
-    T: ortho_config::OrthoConfig + serde::Serialize + Default + CommandFactory,
+    T: SubcmdConfigMerge + ortho_config::OrthoConfig + serde::Serialize + Default + CommandFactory,
 {
#!/bin/bash
# Verify the tightened bound and absence of the old helper.
rg -nP -C2 '\bfn\s+merge_with_sources<[^>]*>\([^)]*\)\s*->\s*T\s*where\s*T:\s*(?s).*SubcmdConfigMerge' tests/subcommand_merge.rs
rg -n 'load_and_merge_subcommand_for' -g '!**/target/**'
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2221fc and b617320.

📒 Files selected for processing (2)
  • src/cli_args.rs (1 hunks)
  • tests/subcommand_merge.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clarity over cleverness: favour explicit, readable Rust over terse or obscure idioms
Use functions and composition; extract reusable logic; prefer declarative iterator-based code when readable
Keep functions small, single-responsibility, and obey command/query segregation
Name things precisely; use is/has/should prefixes for booleans
Use consistent en-GB-oxendict spelling and grammar in code comments (except external API references)
Function documentation must include clear examples; omit redundant examples in test docs
Keep code files ≤ 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; fix warnings in code instead of silencing
Extract helper functions when functions are too long; maintain separation of concerns and CQRS
Group numerous related parameters into meaningful structs
Consider using Arc for large error values to reduce returned data size
Begin every module with a module-level //! comment describing purpose and utility
Document public APIs with /// Rustdoc so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut
Use Result for error handling; avoid panicking where feasible
Avoid unsafe unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
Scope lint suppressions narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint suppression
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of backslash newline escapes
Prefer semantic error enums deriving std::error::Error via thiserror for caller-inspectable conditions
Never export eyre::Report from libraries; convert to domain error enums at API boundaries; use eyre only in main()/top-level async task

Files:

  • src/cli_args.rs
  • tests/subcommand_merge.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in tests ...

Files:

  • src/cli_args.rs
  • tests/subcommand_merge.rs
{src,tests}/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

{src,tests}/**/*.rs: Write unit and behavioural tests for new functionality; run both before and after changes
Use an opaque error (eyre::Report) only at app boundary; not in public APIs

Files:

  • src/cli_args.rs
  • tests/subcommand_merge.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs

Files:

  • tests/subcommand_merge.rs
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
PR: leynos/ortho-config#0
File: docs/subcommand-refinements.md:0-0
Timestamp: 2025-08-28T21:56:52.319Z
Learning: Applies to docs/**/*.rs : Use the SubcmdConfigMerge trait to enable a default load_and_merge that borrows self; import with `use ortho_config::SubcmdConfigMerge;`
🧬 Code graph analysis (1)
src/cli_args.rs (2)
src/test_utils_env.rs (2)
  • remove_var (22-27)
  • set_var (12-17)
tests/subcommand_merge.rs (1)
  • write_config (17-21)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-test
  • GitHub Check: Sourcery review
🔇 Additional comments (10)
src/cli_args.rs (6)

127-136: Scope tests to the trait by importing SubcmdConfigMerge.

Importing the trait enables the .load_and_merge() method; this is correct for the new API.


160-165: Keep tempdir alive for the duration of reads.

Returning the TempDir alongside the path is correct; it keeps the directory alive for the caller’s scope. LGTM.


167-200: Validate CLI precedence over env and file.

Assertions correctly exercise “CLI over env over file” for PrArgs. Nice coverage.


201-234: Assert selective merging (Option-only) from env.

Test correctly verifies that only the optional reference is sourced from env and that eagerly-initialised fields (Vec/bool) are not overridden.


235-255: Assert absence behaviour.

Test correctly ensures no sources leave Option fields as None and booleans at their CLI defaults.


256-282: Prove non-mutating merge.

Test verifies .load_and_merge() does not mutate the CLI instance; this guards the borrow‑based API. Good.

tests/subcommand_merge.rs (4)

9-9: Import the trait to enable .load_and_merge().

Trait import is required for method resolution; this aligns with the upgrade.


55-57: Use the trait method .load_and_merge().

Call site matches the new API and keeps error messages informative with type_name::<T>(). LGTM.


173-194: Verify env reference does not override eager fields.

Test correctly asserts env fills only the optional reference and leaves Vec/bool untouched. Good signal against regressions.


196-218: Verify CLI overrides env for all fields.

Test asserts the highest‑precedence path. Looks sound.

Comment thread src/cli_args.rs Outdated
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
tests/subcommand_merge.rs (1)

39-43: Add explicit SubcmdConfigMerge bound to the generic

Tighten the signature so compile errors are precise and Clippy stays quiet.

Apply this diff:

 where
-    T: ortho_config::OrthoConfig + serde::Serialize + Default + CommandFactory,
+    T: SubcmdConfigMerge + ortho_config::OrthoConfig + serde::Serialize + Default + CommandFactory,
Refactor the function `merge_with_sources` in tests/subcommand_merge.rs to add `SubcmdConfigMerge` to the generic bounds. Re-run `cargo clippy` to confirm the previous warning disappears.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b617320 and e7c9b42.

📒 Files selected for processing (1)
  • tests/subcommand_merge.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clarity over cleverness: favour explicit, readable Rust over terse or obscure idioms
Use functions and composition; extract reusable logic; prefer declarative iterator-based code when readable
Keep functions small, single-responsibility, and obey command/query segregation
Name things precisely; use is/has/should prefixes for booleans
Use consistent en-GB-oxendict spelling and grammar in code comments (except external API references)
Function documentation must include clear examples; omit redundant examples in test docs
Keep code files ≤ 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; fix warnings in code instead of silencing
Extract helper functions when functions are too long; maintain separation of concerns and CQRS
Group numerous related parameters into meaningful structs
Consider using Arc for large error values to reduce returned data size
Begin every module with a module-level //! comment describing purpose and utility
Document public APIs with /// Rustdoc so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut
Use Result for error handling; avoid panicking where feasible
Avoid unsafe unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
Scope lint suppressions narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint suppression
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of backslash newline escapes
Prefer semantic error enums deriving std::error::Error via thiserror for caller-inspectable conditions
Never export eyre::Report from libraries; convert to domain error enums at API boundaries; use eyre only in main()/top-level async task

Files:

  • tests/subcommand_merge.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in tests ...

Files:

  • tests/subcommand_merge.rs
{src,tests}/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

{src,tests}/**/*.rs: Write unit and behavioural tests for new functionality; run both before and after changes
Use an opaque error (eyre::Report) only at app boundary; not in public APIs

Files:

  • tests/subcommand_merge.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs

Files:

  • tests/subcommand_merge.rs
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (2)
tests/subcommand_merge.rs (2)

9-9: Import the trait to enable method-call syntax

Keep this use so .load_and_merge() resolves via trait method syntax.


172-236: Approve parametrised PR merge tests — verified

Merge changes. Good use of rstest; tests in src/cli_args.rs and tests/subcommand_merge.rs confirm VKCMDS_PR_REFERENCE is read from the environment while VKCMDS_PR_FILES and VKCMDS_PR_SHOW_OUTDATED remain CLI-only.

Comment thread tests/subcommand_merge.rs Outdated
Repository owner deleted a comment from coderabbitai Bot Sep 19, 2025
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
tests/subcommand_merge.rs (2)

40-43: Tighten the generic bound to include SubcmdConfigMerge.

Make compile errors clearer and keep Clippy quiet.

 fn merge_with_sources<T>(config: &str, env: &[(&str, Option<&str>)], cli: &T) -> T
 where
-    T: ortho_config::OrthoConfig + serde::Serialize + Default + CommandFactory,
+    T: ortho_config::OrthoConfig
+        + serde::Serialize
+        + Default
+        + CommandFactory
+        + SubcmdConfigMerge,
 {

56-58: Include the config path in the panic for better diagnostics.

Print which config was used on failure.

-    let merged = cli
-        .load_and_merge()
-        .unwrap_or_else(|err| panic!("merge {} args: {err}", std::any::type_name::<T>()));
+    let merged = cli.load_and_merge().unwrap_or_else(|err| {
+        panic!(
+            "merge {} args with config {}: {err}",
+            std::any::type_name::<T>(),
+            config_path.display()
+        )
+    });
src/cli_args.rs (1)

137-158: Restore previous environment values in EnvGuard.

Snapshot each key’s prior value and restore it on Drop to avoid leaking state when keys were already set.

Apply:

-    struct EnvGuard {
-        keys: Vec<&'static str>,
-    }
+    struct EnvGuard {
+        saved: Vec<(&'static str, Option<std::ffi::OsString>)>,
+    }
@@
-        fn new(keys: &[&'static str]) -> Self {
-            for key in keys {
-                remove_var(key);
-            }
-            Self {
-                keys: keys.to_vec(),
-            }
-        }
+        fn new(keys: &[&'static str]) -> Self {
+            let saved = keys
+                .iter()
+                .map(|&k| (k, std::env::var_os(k)))
+                .collect::<Vec<_>>();
+            for (key, _) in &saved {
+                remove_var(key);
+            }
+            Self { saved }
+        }
@@
-        fn drop(&mut self) {
-            for key in &self.keys {
-                remove_var(key);
-            }
-        }
+        fn drop(&mut self) {
+            for (key, val) in self.saved.drain(..) {
+                match val {
+                    Some(v) => set_var(key, &v),
+                    None => remove_var(key),
+                }
+            }
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7c9b42 and a38a6ea.

📒 Files selected for processing (2)
  • src/cli_args.rs (1 hunks)
  • tests/subcommand_merge.rs (5 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clarity over cleverness: favour explicit, readable Rust over terse or obscure idioms
Use functions and composition; extract reusable logic; prefer declarative iterator-based code when readable
Keep functions small, single-responsibility, and obey command/query segregation
Name things precisely; use is/has/should prefixes for booleans
Use consistent en-GB-oxendict spelling and grammar in code comments (except external API references)
Function documentation must include clear examples; omit redundant examples in test docs
Keep code files ≤ 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; fix warnings in code instead of silencing
Extract helper functions when functions are too long; maintain separation of concerns and CQRS
Group numerous related parameters into meaningful structs
Consider using Arc for large error values to reduce returned data size
Begin every module with a module-level //! comment describing purpose and utility
Document public APIs with /// Rustdoc so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut
Use Result for error handling; avoid panicking where feasible
Avoid unsafe unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
Scope lint suppressions narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint suppression
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of backslash newline escapes
Prefer semantic error enums deriving std::error::Error via thiserror for caller-inspectable conditions
Never export eyre::Report from libraries; convert to domain error enums at API boundaries; use eyre only in main()/top-level async task

Files:

  • src/cli_args.rs
  • tests/subcommand_merge.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in tests ...

Files:

  • src/cli_args.rs
  • tests/subcommand_merge.rs
{src,tests}/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

{src,tests}/**/*.rs: Write unit and behavioural tests for new functionality; run both before and after changes
Use an opaque error (eyre::Report) only at app boundary; not in public APIs

Files:

  • src/cli_args.rs
  • tests/subcommand_merge.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs

Files:

  • tests/subcommand_merge.rs
🧬 Code graph analysis (1)
src/cli_args.rs (2)
src/test_utils_env.rs (2)
  • remove_var (22-27)
  • set_var (12-17)
tests/subcommand_merge.rs (2)
  • drop (28-30)
  • write_config (18-22)
🔍 Remote MCP Deepwiki

Here are the key additional facts relevant to reviewing PR #147 “Adopt new ortho_config merge API,” drawn from the repository documentation and source:

• Definition and Removal of the Old Free Function
– The free-function load_and_merge_subcommand_for (from module ortho_config::subcommand) was originally imported and invoked in src/main.rs around lines 830–832. It has now been removed.

• Introduction of the New Trait and Methods
– The ortho_config::SubcmdConfigMerge trait is introduced in the updated code. It provides an inherent .load_and_merge() method on each subcommand arg type (e.g. PrArgs, IssueArgs, ResolveArgs).
– These methods replace calls to the old free function, e.g.:
• Before:
let args = load_and_merge_subcommand_for(&pr_cli)?;
• After:
let args = pr_cli.load_and_merge()?;
This change occurred in src/main.rs around lines 830–840.

• Tests Updated to Reflect the API Change
– In tests/subcommand_merge.rs, all invocations of load_and_merge_subcommand_for(...) were replaced by calls to cli.load_and_merge(). The import path was changed from ortho_config::subcommand::load_and_merge_subcommand_for to ortho_config::SubcmdConfigMerge.
– In src/cli_args.rs, the SubcmdConfigMerge trait is imported and applied in unit tests for PrArgs, IssueArgs, and ResolveArgs under the #[cfg(test)] module.

• Ortho_config Version
– The project depends on ortho_config version v0.2.0, specified in Cargo.toml under dependencies. It is used throughout for hierarchical configuration merging via Figment.

These collected details confirm that PR #147 comprehensively replaces the old free-function merge API with the new inherent-trait-based API (SubcmdConfigMerge), and that both production code and tests have been updated accordingly.

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review

Comment thread src/cli_args.rs Outdated
Comment thread src/cli_args.rs Outdated
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Sep 20, 2025

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 20, 2025

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

codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a38a6ea and be9e66b.

📒 Files selected for processing (2)
  • src/cli_args.rs (1 hunks)
  • tests/subcommand_merge.rs (6 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clarity over cleverness: favour explicit, readable Rust over terse or obscure idioms
Use functions and composition; extract reusable logic; prefer declarative iterator-based code when readable
Keep functions small, single-responsibility, and obey command/query segregation
Name things precisely; use is/has/should prefixes for booleans
Use consistent en-GB-oxendict spelling and grammar in code comments (except external API references)
Function documentation must include clear examples; omit redundant examples in test docs
Keep code files ≤ 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; fix warnings in code instead of silencing
Extract helper functions when functions are too long; maintain separation of concerns and CQRS
Group numerous related parameters into meaningful structs
Consider using Arc for large error values to reduce returned data size
Begin every module with a module-level //! comment describing purpose and utility
Document public APIs with /// Rustdoc so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut
Use Result for error handling; avoid panicking where feasible
Avoid unsafe unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
Scope lint suppressions narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint suppression
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of backslash newline escapes
Prefer semantic error enums deriving std::error::Error via thiserror for caller-inspectable conditions
Never export eyre::Report from libraries; convert to domain error enums at API boundaries; use eyre only in main()/top-level async task

Files:

  • tests/subcommand_merge.rs
  • src/cli_args.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in tests ...

Files:

  • tests/subcommand_merge.rs
  • src/cli_args.rs
{src,tests}/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

{src,tests}/**/*.rs: Write unit and behavioural tests for new functionality; run both before and after changes
Use an opaque error (eyre::Report) only at app boundary; not in public APIs

Files:

  • tests/subcommand_merge.rs
  • src/cli_args.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs

Files:

  • tests/subcommand_merge.rs
🧬 Code graph analysis (2)
tests/subcommand_merge.rs (1)
src/cli_args.rs (3)
  • merged (232-233)
  • merged (238-239)
  • merged (244-245)
src/cli_args.rs (2)
src/test_utils_env.rs (2)
  • remove_var (22-27)
  • set_var (12-17)
tests/subcommand_merge.rs (2)
  • drop (28-30)
  • write_config (18-22)
🔍 Remote MCP Ref

Summary of additional relevant facts for reviewing PR #147 (Adopt new ortho_config merge API)

  • API change: The project replaces the free function ortho_config::subcommand::load_and_merge_subcommand_for with the trait-based ortho_config::SubcmdConfigMerge and its inherent method .load_and_merge() on subcommand CLI types (PrArgs, IssueArgs, ResolveArgs). Call sites in src/main.rs now use pr_cli.load_and_merge(), issue_cli.load_and_merge(), resolve_cli.load_and_merge().

  • Tests updated/expanded:

    • tests/subcommand_merge.rs updated to call cli.load_and_merge() instead of the removed free function; import updated to ortho_config::SubcmdConfigMerge. Resolve subcommand added to test coverage and precedence cases expanded (CliOverEnv, EnvOverFile, FileOverDefaults).
    • src/cli_args.rs: large #[cfg(test)] module added with unit tests exercising load_and_merge() for PrArgs, IssueArgs, ResolveArgs; includes EnvGuard helper, temporary config file creation, manipulation of VK_CONFIG_PATH and VKCMDS_* env vars, and tests ensuring CLI instance immutability after merge. Tests are run serially to avoid interference.
  • Surface-level implications for review:

    • API compatibility: ensure ortho_config v0.2.0 (the dependency upgrade) exposes SubcmdConfigMerge and that the trait impls for all affected CLI arg types exist and return the expected Result<T, VkError> types used by callers.
    • Behavioral change risk is low at call sites (only how merged config is obtained changed), but review trait implementations to confirm merge precedence and semantics match previous free-function behavior (tests were added/updated to cover precedence).
    • Confirm no remaining usages of the removed free function remain and that imports were updated consistently across code and tests.

Sources used

  • PR page and diff for PR #147 (Adopt new ortho_config merge API) on GitHub (leynos/vk).
🔇 Additional comments (8)
tests/subcommand_merge.rs (5)

24-38: LGTM: RAII guard for cwd is correct and panic‑safe.


40-48: LGTM: Generic merge helper and environment hygiene read well.


116-119: Good coverage: add Resolve scenarios to precedence matrix.

Also applies to: 182-220


56-58: Include the config path in the panic for diagnostics.

Augment the message so failures name the config used.

-    let merged = cli
-        .load_and_merge()
-        .unwrap_or_else(|err| panic!("merge {} args: {err}", std::any::type_name::<T>()));
+    let merged = cli.load_and_merge().unwrap_or_else(|err| {
+        panic!(
+            "merge {} args with config {}: {err}",
+            std::any::type_name::<T>(),
+            config_path.display()
+        )
+    });

13-16: Normalise imports for ResolveArgs.

File: tests/subcommand_merge.rs — lines 13–16
Import ResolveArgs alongside IssueArgs and PrArgs from the crate root; if ResolveArgs is not re-exported at the crate root, re-export it or keep all three under vk::cli_args::ResolveArgs.

-use vk::cli_args::ResolveArgs;
-use vk::test_utils::{remove_var, set_var};
-use vk::{IssueArgs, PrArgs};
+use vk::{IssueArgs, PrArgs, ResolveArgs};
+use vk::test_utils::{remove_var, set_var};

Verify whether ResolveArgs is re-exported at the crate root by running:

rg -nP '^pub\s+use\s+(.+::)?cli_args::ResolveArgs\b' -C2 || rg -n "ResolveArgs" -S
src/cli_args.rs (3)

139-160: Fix EnvGuard to restore previous environment values.

Snapshot prior values and restore them on Drop to avoid leaking state across serial tests.

-    struct EnvGuard {
-        keys: Vec<&'static str>,
-    }
+    struct EnvGuard {
+        saved: Vec<(&'static str, Option<std::ffi::OsString>)>,
+    }
@@
-        fn new(keys: &[&'static str]) -> Self {
-            for key in keys {
-                remove_var(key);
-            }
-            Self {
-                keys: keys.to_vec(),
-            }
-        }
+        fn new(keys: &[&'static str]) -> Self {
+            let saved = keys
+                .iter()
+                .map(|&k| (k, std::env::var_os(k)))
+                .collect::<Vec<_>>();
+            for (key, _) in &saved {
+                remove_var(key);
+            }
+            Self { saved }
+        }
@@
-    impl Drop for EnvGuard {
-        fn drop(&mut self) {
-            for key in &self.keys {
-                remove_var(key);
-            }
-        }
-    }
+    impl Drop for EnvGuard {
+        fn drop(&mut self) {
+            for (key, val) in self.saved.drain(..) {
+                match val {
+                    Some(v) => set_var(key, v),
+                    None => remove_var(key),
+                }
+            }
+        }
+    }

409-463: Guard working‑directory changes with RAII and actually test discovery.

Wrap cwd changes in a DirGuard to avoid leaks on panic, and clear VK_CONFIG_PATH when you intend to test discovery from the current directory.

@@
-        let (_config_dir, config_path) = setup_env_and_config(&scenario, &cfg);
+        let (_config_dir, config_path) = setup_env_and_config(&scenario, &cfg);
@@
-                let prev_dir = std::env::current_dir().expect("current dir");
-                let config_dir = config_path.parent().expect("config dir");
-                std::env::set_current_dir(config_dir).expect("set dir");
-                let merged = cli.load_and_merge().expect("merge issue args");
-                std::env::set_current_dir(prev_dir).expect("restore dir");
+                // Use discovery, not VK_CONFIG_PATH
+                remove_var("VK_CONFIG_PATH");
+                struct DirGuard(std::path::PathBuf);
+                impl DirGuard {
+                    fn enter<P: AsRef<std::path::Path>>(dir: P) -> Self {
+                        let prev = std::env::current_dir().expect("current dir");
+                        std::env::set_current_dir(dir).expect("set dir");
+                        Self(prev)
+                    }
+                }
+                impl Drop for DirGuard {
+                    fn drop(&mut self) {
+                        let _ = std::env::set_current_dir(&self.0);
+                    }
+                }
+                let config_dir = config_path.parent().expect("config dir");
+                let _dir = DirGuard::enter(config_dir);
+                let merged = cli.load_and_merge().expect("merge issue args");
@@
-                let prev_dir = std::env::current_dir().expect("current dir");
-                let config_dir = config_path.parent().expect("config dir");
-                std::env::set_current_dir(config_dir).expect("set dir");
-                let merged = cli.load_and_merge().expect("merge resolve args");
-                std::env::set_current_dir(prev_dir).expect("restore dir");
+                remove_var("VK_CONFIG_PATH");
+                let config_dir = config_path.parent().expect("config dir");
+                let _dir = DirGuard::enter(config_dir);
+                let merged = cli.load_and_merge().expect("merge resolve args");

Follow‑up: extract DirGuard to a shared test_utils module and reuse here and in tests/subcommand_merge.rs.


127-134: Resolve: no in-source uses of load_and_merge_subcommand_for; SubcmdConfigMerge imports present.

Close this review — ripgrep shows load_and_merge_subcommand_for only in docs; SubcmdConfigMerge is imported in src/cli_args.rs:131, src/main.rs:57 and tests/subcommand_merge.rs:9.

Comment thread src/cli_args.rs Outdated
Comment thread src/cli_args.rs Outdated
@leynos

leynos commented Sep 20, 2025

Copy link
Copy Markdown
Owner Author

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

src/cli_args.rs
Comment on lines +184 to +195

    fn pr_scenario() -> TestScenario {
        TestScenario {
            subcommand: SubcommandType::Pr,
            env_vars: &[
                "VK_CONFIG_PATH",
                "VKCMDS_PR_REFERENCE",
                "VKCMDS_PR_FILES",
                "VKCMDS_PR_SHOW_OUTDATED",
            ],
            config_section: "pr",
        }
    }

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: tests.pr_scenario,tests.resolve_scenario

@coderabbitai

coderabbitai Bot commented Sep 20, 2025

Copy link
Copy Markdown

Tip

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

🧩 Analysis chain

@leynos I'll examine the code duplication and suggest a fix. Let me first look at the current implementation to understand the pattern.

Now I can see the duplication pattern. Here's the fix:

Suggested Fix

Replace the individual scenario functions with a data-driven approach using a macro or a parameterized function. The most elegant solution is to use a const array of scenario data:

// Replace the individual functions with this data structure and helper
const SCENARIO_DATA: &[(SubcommandType, &[&str], &str)] = &[
    (SubcommandType::Pr, &[
        "VK_CONFIG_PATH",
        "VKCMDS_PR_REFERENCE", 
        "VKCMDS_PR_FILES",
        "VKCMDS_PR_SHOW_OUTDATED",
    ], "pr"),
    (SubcommandType::Issue, &[
        "VK_CONFIG_PATH",
        "VKCMDS_ISSUE_REFERENCE",
        "VKCMDS_ISSUE_MESSAGE", 
    ], "issue"),
    (SubcommandType::Resolve, &[
        "VK_CONFIG_PATH",
        "VKCMDS_RESOLVE_REFERENCE",
        "VKCMDS_RESOLVE_MESSAGE",
    ], "resolve"),
];

fn get_scenario(subcommand: SubcommandType) -> TestScenario {
    let (subcmd, env_vars, config_section) = SCENARIO_DATA
        .iter()
        .find(|(s, _, _)| *s == subcommand)
        .expect("Unknown subcommand type");
    
    TestScenario {
        subcommand: *subcmd,
        env_vars,
        config_section,
    }
}

// Then replace calls like pr_scenario() with get_scenario(SubcommandType::Pr)

AI Agent Prompt

You are tasked with refactoring code duplication in Rust test functions. 

**Context**: In the file `src/cli_args.rs`, there are multiple similar functions (`pr_scenario()`, `resolve_scenario()`, and likely `issue_scenario()`) that return `TestScenario` structs with only different parameter values.

**Task**: Replace the duplicated scenario functions with a data-driven approach:

1. **Remove** the existing `pr_scenario()`, `resolve_scenario()`, and `issue_scenario()` functions
2. **Add** a const array `SCENARIO_DATA` containing tuples of `(SubcommandType, &[&str], &str)` with the scenario data
3. **Add** a helper function `get_scenario(subcommand: SubcommandType) -> TestScenario` that looks up the data
4. **Update** all call sites to use `get_scenario(SubcommandType::Pr)` instead of `pr_scenario()`

**Requirements**:
- Preserve exact functionality - all tests must continue to pass
- Use const data for compile-time efficiency  
- Include proper error handling with `.expect()` for unknown subcommand types
- Update all call sites consistently
- Ensure the SubcommandType enum supports equality comparison (derive Eq, PartialEq if needed)

**Files to modify**: `src/cli_args.rs` (test module only)

🏁 Script executed:

#!/bin/bash
# Examine the test functions in src/cli_args.rs to understand the duplication
rg -n -A 10 -B 2 "fn (pr_scenario|resolve_scenario|issue_scenario)" src/cli_args.rs

Length of output: 1096


🏁 Script executed:

#!/bin/bash
# Look at the TestScenario struct definition and SubcommandType enum
rg -n -A 10 -B 2 "(struct TestScenario|enum SubcommandType)" src/cli_args.rs

Length of output: 508


🏁 Script executed:

#!/bin/bash
# Get more context around the test scenario functions
rg -n -A 15 "fn pr_scenario" src/cli_args.rs
rg -n -A 15 "fn resolve_scenario" src/cli_args.rs

Length of output: 1137


Perfect! Now I can see the full pattern. These are rstest fixtures (indicated by #[fixture]), so the refactoring approach needs to account for that. Here's the updated fix:

Suggested Fix

Replace the duplicated fixture functions with a data-driven approach while preserving the rstest fixture pattern:

// Add this const data structure
const SCENARIO_DATA: &[(SubcommandType, &[&str], &str)] = &[
    (SubcommandType::Pr, &[
        "VK_CONFIG_PATH",
        "VKCMDS_PR_REFERENCE",
        "VKCMDS_PR_FILES", 
        "VKCMDS_PR_SHOW_OUTDATED",
    ], "pr"),
    (SubcommandType::Issue, &[
        "VK_CONFIG_PATH",
        "VKCMDS_ISSUE_REFERENCE",
    ], "issue"),
    (SubcommandType::Resolve, &[
        "VK_CONFIG_PATH",
        "VKCMDS_RESOLVE_REFERENCE",
        "VKCMDS_RESOLVE_MESSAGE",
    ], "resolve"),
];

// Helper function to create scenarios
fn create_scenario(subcommand: SubcommandType) -> TestScenario {
    let (subcmd, env_vars, config_section) = SCENARIO_DATA
        .iter()
        .find(|(s, _, _)| *s == subcommand)
        .expect("Unknown subcommand type");
    
    TestScenario {
        subcommand: *subcmd,
        env_vars,
        config_section,
    }
}

// Replace the three fixture functions with these simplified versions
#[fixture]
fn pr_scenario() -> TestScenario {
    create_scenario(SubcommandType::Pr)
}

#[fixture] 
fn issue_scenario() -> TestScenario {
    create_scenario(SubcommandType::Issue)
}

#[fixture]
fn resolve_scenario() -> TestScenario {
    create_scenario(SubcommandType::Resolve)
}

AI Agent Prompt

You are tasked with refactoring code duplication in Rust test fixture functions using a data-driven approach.

**Context**: In the file `src/cli_args.rs` within the test module, there are three rstest fixture functions (`pr_scenario()`, `issue_scenario()`, `resolve_scenario()`) that contain nearly identical code, differing only in their data values.

**Task**: Refactor to eliminate duplication while preserving the rstest fixture interface:

1. **Add** a const array `SCENARIO_DATA` containing tuples of `(SubcommandType, &[&str], &str)` with the scenario configuration data for all three subcommands
2. **Add** a helper function `create_scenario(subcommand: SubcommandType) -> TestScenario` that looks up data from the const array
3. **Refactor** the three existing `#[fixture]` functions to call the helper function instead of duplicating the struct creation
4. **Preserve** the existing function names and signatures - tests should continue to work without changes
5. **Ensure** SubcommandType supports equality comparison (it already derives Clone, Copy - add PartialEq, Eq if needed)

**Requirements**:
- Keep all `#[fixture]` attributes and function signatures unchanged
- All existing tests must continue to pass without modification
- Use const data for compile-time efficiency
- Include proper error handling in the helper function
- Maintain the same exact functionality and return values

**Files to modify**: `src/cli_args.rs` (test module only)

**Expected outcome**: The three fixture functions should become one-liners calling the helper function, with all duplication moved to the const data array.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be9e66b and e4de317.

📒 Files selected for processing (1)
  • src/cli_args.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clarity over cleverness: favour explicit, readable Rust over terse or obscure idioms
Use functions and composition; extract reusable logic; prefer declarative iterator-based code when readable
Keep functions small, single-responsibility, and obey command/query segregation
Name things precisely; use is/has/should prefixes for booleans
Use consistent en-GB-oxendict spelling and grammar in code comments (except external API references)
Function documentation must include clear examples; omit redundant examples in test docs
Keep code files ≤ 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; fix warnings in code instead of silencing
Extract helper functions when functions are too long; maintain separation of concerns and CQRS
Group numerous related parameters into meaningful structs
Consider using Arc for large error values to reduce returned data size
Begin every module with a module-level //! comment describing purpose and utility
Document public APIs with /// Rustdoc so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut
Use Result for error handling; avoid panicking where feasible
Avoid unsafe unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
Scope lint suppressions narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint suppression
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of backslash newline escapes
Prefer semantic error enums deriving std::error::Error via thiserror for caller-inspectable conditions
Never export eyre::Report from libraries; convert to domain error enums at API boundaries; use eyre only in main()/top-level async task

Files:

  • src/cli_args.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in tests ...

Files:

  • src/cli_args.rs
{src,tests}/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

{src,tests}/**/*.rs: Write unit and behavioural tests for new functionality; run both before and after changes
Use an opaque error (eyre::Report) only at app boundary; not in public APIs

Files:

  • src/cli_args.rs
🧬 Code graph analysis (1)
src/cli_args.rs (2)
src/test_utils_env.rs (2)
  • remove_var (22-27)
  • set_var (12-17)
tests/subcommand_merge.rs (2)
  • drop (28-30)
  • write_config (18-22)
🔍 Remote MCP Ref

Relevant findings for reviewing PR #147 (Adopt new ortho_config merge API)

  • API change: the free function ortho_config::subcommand::load_and_merge_subcommand_for was replaced by the trait ortho_config::SubcmdConfigMerge exposing an inherent method .load_and_merge() on subcommand CLI types; call sites in main.rs are updated to use pr_cli.load_and_merge(), issue_cli.load_and_merge(), resolve_cli.load_and_merge().,

  • Tests updated:

    • tests/subcommand_merge.rs now imports ortho_config::SubcmdConfigMerge and calls cli.load_and_merge(); Resolve subcommand added; precedence cases expanded (CliOverEnv, EnvOverFile, FileOverDefaults).
    • src/cli_args.rs gained a comprehensive #[cfg(test)] module with EnvGuard, TempDir-based config helpers, and many tests validating merge precedence, preservation of original CLI instance, and config discovery. Tests run serially to avoid env interference.
  • Review action items / risk areas:

    • Verify ortho_config dependency version used in Cargo.lock exposes SubcmdConfigMerge and implements load_and_merge for PrArgs, IssueArgs, ResolveArgs with the same error/result types expected by callers. (If not, CI/build will fail.)
    • Confirm no remaining uses of the removed free function remain in codebase or tests.
    • Validate tests cover the previous precedence semantics (CLI > env > file > defaults) — updated tests indicate coverage for PR and Resolve; ensure Issue coverage too.
    • Consider applying the suggested test refactor (consolidate pr_scenario/issue_scenario/resolve_scenario) to remove duplication in src/cli_args.rs test fixtures (not required for correctness but recommended for maintainability). The PR discussion already includes concrete refactor options.

Cited sources/tools used:

  • GitHub PR page for leynos/vk PR #147 (diff, description, tests)
  • Documentation/search query for ortho_config SubcmdConfigMerge (search results)
🔇 Additional comments (4)
src/cli_args.rs (4)

183-223: Good data‑driven fixtures; SCENARIO_DATA + create_scenario is the right shape.

Keep the one‑line rstest fixtures to preserve names while centralising data.

Also applies to: 224-237


139-160: Fix EnvGuard to snapshot and restore environment, not just remove keys.

Prevent state leaks when tests run after a pre-set environment.

-    struct EnvGuard {
-        keys: Vec<&'static str>,
-    }
+    struct EnvGuard {
+        saved: Vec<(&'static str, Option<std::ffi::OsString>)>,
+    }
@@
-        fn new(keys: &[&'static str]) -> Self {
-            for key in keys {
-                remove_var(key);
-            }
-            Self {
-                keys: keys.to_vec(),
-            }
-        }
+        fn new(keys: &[&'static str]) -> Self {
+            let saved = keys
+                .iter()
+                .map(|&k| (k, std::env::var_os(k)))
+                .collect::<Vec<_>>();
+            for (key, _) in &saved {
+                remove_var(key);
+            }
+            Self { saved }
+        }
@@
-        fn drop(&mut self) {
-            for key in &self.keys {
-                remove_var(key);
-            }
-        }
+        fn drop(&mut self) {
+            for (key, val) in self.saved.drain(..) {
+                match val {
+                    Some(v) => set_var(key, &v),
+                    None => remove_var(key),
+                }
+            }
+        }

460-465: Use an RAII guard for working-directory changes.

Avoid cwd leaks on panic by guarding directory switches.

Add a small guard (place after write_config):

+    struct DirGuard(std::path::PathBuf);
+    impl DirGuard {
+        fn enter<P: AsRef<std::path::Path>>(dir: P) -> Self {
+            let prev = std::env::current_dir().expect("current dir");
+            std::env::set_current_dir(dir).expect("set dir");
+            Self(prev)
+        }
+    }
+    impl Drop for DirGuard {
+        fn drop(&mut self) {
+            let _ = std::env::set_current_dir(&self.0);
+        }
+    }

Replace manual save/restore:

-                let prev_dir = std::env::current_dir().expect("current dir");
-                let config_dir = config_path.parent().expect("config dir");
-                std::env::set_current_dir(config_dir).expect("set dir");
-                let merged = cli.load_and_merge().expect("merge issue args");
-                std::env::set_current_dir(prev_dir).expect("restore dir");
+                let config_dir = config_path.parent().expect("config dir");
+                let _dir = DirGuard::enter(config_dir);
+                let merged = cli.load_and_merge().expect("merge issue args");
-                let prev_dir = std::env::current_dir().expect("current dir");
-                let config_dir = config_path.parent().expect("config dir");
-                std::env::set_current_dir(config_dir).expect("set dir");
-                let merged = cli.load_and_merge().expect("merge resolve args");
-                std::env::set_current_dir(prev_dir).expect("restore dir");
+                let config_dir = config_path.parent().expect("config dir");
+                let _dir = DirGuard::enter(config_dir);
+                let merged = cli.load_and_merge().expect("merge resolve args");

Also applies to: 474-479, 162-168


249-270: Drop Any/downcasts; assert via a tiny typed accessor trait.

Remove dynamic typing and simplify assertions.

Apply within this block:

-    fn assert_reference_equals(subcommand: SubcommandType, merged: &dyn Any, expected: &str) {
-        match subcommand {
-            SubcommandType::Pr => {
-                let args = merged
-                    .downcast_ref::<PrArgs>()
-                    .expect("reference downcasts to PrArgs");
-                assert_eq!(args.reference.as_deref(), Some(expected));
-            }
-            SubcommandType::Issue => {
-                let args = merged
-                    .downcast_ref::<IssueArgs>()
-                    .expect("reference downcasts to IssueArgs");
-                assert_eq!(args.reference.as_deref(), Some(expected));
-            }
-            SubcommandType::Resolve => {
-                let args = merged
-                    .downcast_ref::<ResolveArgs>()
-                    .expect("reference downcasts to ResolveArgs");
-                assert_eq!(args.reference, expected);
-            }
-        }
-    }
+    fn assert_reference_equals<T: RefStr>(merged: &T, expected: &str) {
+        assert_eq!(merged.reference_str(), Some(expected));
+    }

Add the trait and impls near the helpers:

trait RefStr {
    fn reference_str(&self) -> Option<&str>;
}
impl RefStr for PrArgs {
    fn reference_str(&self) -> Option<&str> { self.reference.as_deref() }
}
impl RefStr for IssueArgs {
    fn reference_str(&self) -> Option<&str> { self.reference.as_deref() }
}
impl RefStr for ResolveArgs {
    fn reference_str(&self) -> Option<&str> { Some(self.reference.as_str()) }
}

Update call sites:

-assert_reference_equals(SubcommandType::Pr, &merged, "cli_ref");
+assert_reference_equals(&merged, "cli_ref");
-assert_reference_equals(SubcommandType::Issue, &merged, "cli_ref");
+assert_reference_equals(&merged, "cli_ref");
-assert_reference_equals(SubcommandType::Resolve, &merged, "cli_ref");
+assert_reference_equals(&merged, "cli_ref");
-assert_reference_equals(SubcommandType::Pr, &merged, "env_ref");
+assert_reference_equals(&merged, "env_ref");
-assert_reference_equals(SubcommandType::Issue, &merged, "env_ref");
+assert_reference_equals(&merged, "env_ref");
-assert_reference_equals(SubcommandType::Resolve, &merged, "cli_ref");
+assert_reference_equals(&merged, "cli_ref");
-assert_reference_equals(SubcommandType::Issue, &merged, "file_ref");
+assert_reference_equals(&merged, "file_ref");
-assert_reference_equals(SubcommandType::Resolve, &merged, "cli_ref");
+assert_reference_equals(&merged, "cli_ref");
-assert_reference_equals(SubcommandType::Pr, &merged, "cli_ref");
+assert_reference_equals(&merged, "cli_ref");

Comment thread src/cli_args.rs Outdated
Comment thread src/cli_args.rs Outdated
codescene-delta-analysis[bot]

This comment was marked as outdated.

Repository owner deleted a comment from coderabbitai Bot Sep 21, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
tests/cli_args.rs (1)

1-413: Keep test files ≤ 400 lines

Split this file (e.g., move precedence tests into tests/cli_args_merge.rs) or extract repeated config‑string builders to helpers to drop below the limit.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e4de317 and 9de20c2.

📒 Files selected for processing (1)
  • tests/cli_args.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clarity over cleverness: favour explicit, readable Rust over terse or obscure idioms
Use functions and composition; extract reusable logic; prefer declarative iterator-based code when readable
Keep functions small, single-responsibility, and obey command/query segregation
Name things precisely; use is/has/should prefixes for booleans
Use consistent en-GB-oxendict spelling and grammar in code comments (except external API references)
Function documentation must include clear examples; omit redundant examples in test docs
Keep code files ≤ 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; fix warnings in code instead of silencing
Extract helper functions when functions are too long; maintain separation of concerns and CQRS
Group numerous related parameters into meaningful structs
Consider using Arc for large error values to reduce returned data size
Begin every module with a module-level //! comment describing purpose and utility
Document public APIs with /// Rustdoc so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut
Use Result for error handling; avoid panicking where feasible
Avoid unsafe unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
Scope lint suppressions narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint suppression
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of backslash newline escapes
Prefer semantic error enums deriving std::error::Error via thiserror for caller-inspectable conditions
Never export eyre::Report from libraries; convert to domain error enums at API boundaries; use eyre only in main()/top-level async task

Files:

  • tests/cli_args.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in tests ...

Files:

  • tests/cli_args.rs
{src,tests}/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

{src,tests}/**/*.rs: Write unit and behavioural tests for new functionality; run both before and after changes
Use an opaque error (eyre::Report) only at app boundary; not in public APIs

Files:

  • tests/cli_args.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs

Files:

  • tests/cli_args.rs
🧬 Code graph analysis (1)
tests/cli_args.rs (1)
src/cli_args.rs (2)
  • default (91-93)
  • default (119-124)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (6)
tests/cli_args.rs (6)

1-12: Adopt the new merge API correctly

Import the trait and call .load_and_merge() on the CLI structs. Method resolution will work across all tests.


43-96: Deduplicate scenario data via a single source of truth

Use SCENARIO_DATA and create_scenario to kill duplication and keep fixtures DRY. This matches the requested refactor.


98-111: Keep rstest fixtures as one‑liners

Preserve fixture names while delegating to create_scenario. This minimises churn at call sites.


146-223: Validate CLI > env > file precedence for all subcommands

Exercise each field and assert CLI wins. Coverage looks correct.


225-296: Assert env fills only unset optionals

Document the clap behaviour and verify only optional fields are overridden. Good negative checks on vectors/bools.


360-412: Ensure .load_and_merge() does not mutate the CLI instance

Clone/snapshot assertions cover immutability nicely across structs.

Comment thread tests/cli_args.rs Outdated
Comment thread tests/cli_args.rs Outdated
Comment thread tests/cli_args.rs Outdated
Comment thread tests/cli_args.rs Outdated
Comment thread tests/cli_args.rs
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
tests/subcommand_merge.rs (1)

64-76: Eliminate flakiness by clearing VKCMDS_PR_SHOW_OUTDATED in FileOverDefaults

The test does not clear VKCMDS_PR_SHOW_OUTDATED. If present in the host environment, it can affect results despite expectations focusing on reference. Clear it explicitly.

Apply this diff:

         (SubcommandType::Pr, PrecedenceScenario::FileOverDefaults) => {
@@
             let merged = merge_with_sources(
                 cfg,
-                &[("VKCMDS_PR_REFERENCE", None), ("VKCMDS_PR_FILES", None)],
+                &[
+                    ("VKCMDS_PR_REFERENCE", None),
+                    ("VKCMDS_PR_FILES", None),
+                    ("VKCMDS_PR_SHOW_OUTDATED", None),
+                ],
                 &cli,
             );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9de20c2 and 29ad38c.

📒 Files selected for processing (4)
  • tests/cli_args_merge.rs (1 hunks)
  • tests/subcommand_merge.rs (3 hunks)
  • tests/support/env.rs (1 hunks)
  • tests/support/subcommand.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clarity over cleverness: favour explicit, readable Rust over terse or obscure idioms
Use functions and composition; extract reusable logic; prefer declarative iterator-based code when readable
Keep functions small, single-responsibility, and obey command/query segregation
Name things precisely; use is/has/should prefixes for booleans
Use consistent en-GB-oxendict spelling and grammar in code comments (except external API references)
Function documentation must include clear examples; omit redundant examples in test docs
Keep code files ≤ 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; fix warnings in code instead of silencing
Extract helper functions when functions are too long; maintain separation of concerns and CQRS
Group numerous related parameters into meaningful structs
Consider using Arc for large error values to reduce returned data size
Begin every module with a module-level //! comment describing purpose and utility
Document public APIs with /// Rustdoc so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut
Use Result for error handling; avoid panicking where feasible
Avoid unsafe unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
Scope lint suppressions narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint suppression
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of backslash newline escapes
Prefer semantic error enums deriving std::error::Error via thiserror for caller-inspectable conditions
Never export eyre::Report from libraries; convert to domain error enums at API boundaries; use eyre only in main()/top-level async task

Files:

  • tests/support/env.rs
  • tests/support/subcommand.rs
  • tests/subcommand_merge.rs
  • tests/cli_args_merge.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in tests ...

Files:

  • tests/support/env.rs
  • tests/support/subcommand.rs
  • tests/subcommand_merge.rs
  • tests/cli_args_merge.rs
{src,tests}/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

{src,tests}/**/*.rs: Write unit and behavioural tests for new functionality; run both before and after changes
Use an opaque error (eyre::Report) only at app boundary; not in public APIs

Files:

  • tests/support/env.rs
  • tests/support/subcommand.rs
  • tests/subcommand_merge.rs
  • tests/cli_args_merge.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs

Files:

  • tests/support/env.rs
  • tests/support/subcommand.rs
  • tests/subcommand_merge.rs
  • tests/cli_args_merge.rs
🧬 Code graph analysis (3)
tests/support/subcommand.rs (1)
tests/support/env.rs (3)
  • setup_env_and_config (76-80)
  • new (22-31)
  • enter (54-58)
tests/subcommand_merge.rs (1)
tests/support/subcommand.rs (4)
  • issue_cli (48-54)
  • merge_with_sources (14-33)
  • pr_cli (36-45)
  • resolve_cli (57-62)
tests/cli_args_merge.rs (1)
tests/support/env.rs (3)
  • setup_env_and_config (76-80)
  • new (22-31)
  • enter (54-58)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (7)
tests/subcommand_merge.rs (2)

103-119: LGTM: Resolve precedence path is exercised thoroughly

The CLI-over-env branch asserts both fields and matches the new API usage correctly.


145-208: Good targeted precedence coverage for PR with structured cases

Tests read well and validate CLI/env/file precedence.

Run the following locally (verification script failed to run in the sandbox):

#!/bin/bash
set -euo pipefail
cargo fmt --all
rg -nF -e 'todo!(' -e 'dbg!(' src tests || true

Fix any formatting or debug/todo occurrences and push; re-run CI.

tests/support/subcommand.rs (1)

14-17: Correct trait bounds for clearer errors

Adding SubcmdConfigMerge alongside OrthoConfig/Serialize/Default/CommandFactory tightens the API and keeps Clippy quiet.

tests/cli_args_merge.rs (4)

49-75: Nice de-duplication with SCENARIO_DATA mapping

The data-driven table removes repeated fixture logic cleanly while keeping readability.


90-103: Fixtures reduced to one-liners — duplication resolved

The wrapper fixtures now delegate to create_scenario(...) as discussed. Keep this pattern.

Confirm rstest’s recommended pattern for data-driven fixtures and one-line wrappers remains stable for the current rstest release.

220-236: Comment accurately explains boolean/vector precedence behaviour

The note on Clap initialising non-Option fields is helpful context for future readers.


320-367: Validate no mutation of the original CLI instance

Good defensive snapshotting; keep this to guard against future regressions.

Comment thread tests/cli_args_merge.rs Outdated
Comment thread tests/subcommand_merge.rs
Comment thread tests/support/env.rs
Comment thread tests/support/env.rs
Comment thread tests/support/subcommand.rs Outdated
Comment thread tests/support/subcommand.rs
codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Sep 21, 2025

Copy link
Copy Markdown
Owner Author

@coderabbitai pause

@coderabbitai

coderabbitai Bot commented Sep 21, 2025

Copy link
Copy Markdown
✅ Actions performed

Reviews paused.

Import the crate-level environment helper for the GraphQL client so the GitHub endpoint lookup holds the shared mutex. Also update the integration test guard to snapshot variables under the same lock before clearing them.
@leynos
leynos merged commit bb12bc9 into main Sep 21, 2025
5 checks passed
@leynos

leynos commented Sep 21, 2025

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@sourcery-ai sourcery-ai 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.

Hey there - I've reviewed your changes - here's some feedback:

  • Verify that replacing std::env::args_os() with env::args_os() still resolves to the correct API—your new environment module doesn’t expose args_os(), so this call might be broken.
  • There’s a lot of duplicated precedence logic between tests/subcommand_merge.rs and tests/cli_args_merge.rs; consider consolidating them or extracting shared fixtures to reduce maintenance overhead.
  • The new cli_args_merge tests are very verbose—see if you can further parameterize or abstract the common scenario setup to make the suite more concise and DRY.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Verify that replacing std::env::args_os() with env::args_os() still resolves to the correct API—your new environment module doesn’t expose args_os(), so this call might be broken.
- There’s a lot of duplicated precedence logic between tests/subcommand_merge.rs and tests/cli_args_merge.rs; consider consolidating them or extracting shared fixtures to reduce maintenance overhead.
- The new cli_args_merge tests are very verbose—see if you can further parameterize or abstract the common scenario setup to make the suite more concise and DRY.

## Individual Comments

### Comment 1
<location> `src/environment.rs:26` </location>
<code_context>
+pub fn set_var<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, value: V) {
+    let _guard = lock();
+    // SAFETY: the mutex serialises access to the unsynchronised std env calls.
+    unsafe { env::set_var(key, value) };
+}
+
</code_context>

<issue_to_address>
**issue:** The use of unsafe here is unnecessary and could be misleading.

env::set_var and env::remove_var are safe and do not require unsafe blocks. Please remove the unsafe block unless absolutely necessary.
</issue_to_address>

### Comment 2
<location> `tests/subcommand_merge.rs:165` </location>
<code_context>
+#[derive(Debug)]
+struct PrMergeTestCase {
</code_context>

<issue_to_address>
**suggestion (testing):** Suggestion to add edge case tests for empty and malformed environment variable values.

Add tests for cases where environment variables are empty or contain invalid values to verify correct fallback behavior.
</issue_to_address>

### Comment 3
<location> `tests/cli_args_merge.rs:213-222` </location>
<code_context>
+fn load_and_merge_uses_environment_when_cli_defaults(#[case] scenario: TestScenario) {
</code_context>

<issue_to_address>
**suggestion (testing):** Suggestion to add tests for missing environment variables.

Please add test cases where required environment variables are absent to verify that the merge logic handles missing values gracefully and defaults as intended.

Suggested implementation:

```rust
#[rstest]
#[case::pr(pr_scenario())]
#[case::issue(issue_scenario())]
#[case::resolve(resolve_scenario())]
#[case::pr_missing_env(pr_scenario())]
#[case::issue_missing_env(issue_scenario())]
#[case::resolve_missing_env(resolve_scenario())]
#[serial]
fn load_and_merge_uses_environment_when_cli_defaults(#[case] scenario: TestScenario) {
    let missing_env = scenario.name.ends_with("_missing_env");
    if missing_env {
        // Unset all relevant environment variables for this scenario
        match scenario.subcommand {
            SubcommandType::Pr => {
                std::env::remove_var("VKCMDS_PR_REFERENCE");
                std::env::remove_var("VKCMDS_PR_FILES");
                std::env::remove_var("VKCMDS_PR_SHOW_OUTDATED");
            }
            SubcommandType::Issue => {
                std::env::remove_var("VKCMDS_ISSUE_REFERENCE");
                std::env::remove_var("VKCMDS_ISSUE_LABELS");
            }
            SubcommandType::Resolve => {
                std::env::remove_var("VKCMDS_RESOLVE_REFERENCE");
                std::env::remove_var("VKCMDS_RESOLVE_STATUS");
            }
        }
    } else {
        let _guard = EnvGuard::new(scenario.env_vars);
        match scenario.subcommand {
            SubcommandType::Pr => {
                set_var("VKCMDS_PR_REFERENCE", "env_ref");
                set_var("VKCMDS_PR_FILES", "env_one.rs,env_two.rs");
                set_var("VKCMDS_PR_SHOW_OUTDATED", "true");
            }
            SubcommandType::Issue => {
                set_var("VKCMDS_ISSUE_REFERENCE", "env_issue_ref");
                set_var("VKCMDS_ISSUE_LABELS", "env_bug,env_feature");
            }
            SubcommandType::Resolve => {
                set_var("VKCMDS_RESOLVE_REFERENCE", "env_resolve_ref");
                set_var("VKCMDS_RESOLVE_STATUS", "env_closed");
            }
        }
    }

    let cfg = config_for_scenario(&scenario, ConfigVariant::EnvFallback);
    let (_config_dir, _config_path) = setup_env_and_config(&cfg);

    match scenario.subcommand {
        SubcommandType::Pr => {
            if missing_env {
                // Check that defaults are used when env vars are missing
                let merged = merge_cli_and_env(&cfg, &scenario.cli_args);
                assert_eq!(merged.reference.as_deref(), Some("cli_ref"));
                assert_eq!(merged.files.as_deref(), Some("cli_one.rs,cli_two.rs"));
                assert_eq!(merged.show_outdated, Some(false));
            } else {
                set_var("VKCMDS_PR_REFERENCE", "env_ref");
                set_var("VKCMDS_PR_FILES", "env_one.rs,env_two.rs");
                set_var("VKCMDS_PR_SHOW_OUTDATED", "true");
                let merged = merge_cli_and_env(&cfg, &scenario.cli_args);
                assert_eq!(merged.reference.as_deref(), Some("env_ref"));
                assert_eq!(merged.files.as_deref(), Some("env_one.rs,env_two.rs"));
                assert_eq!(merged.show_outdated, Some(true));
            }
        }
        SubcommandType::Issue => {
            if missing_env {
                let merged = merge_cli_and_env(&cfg, &scenario.cli_args);
                assert_eq!(merged.reference.as_deref(), Some("cli_issue_ref"));
                assert_eq!(merged.labels.as_deref(), Some("cli_bug,cli_feature"));
            } else {
                set_var("VKCMDS_ISSUE_REFERENCE", "env_issue_ref");
                set_var("VKCMDS_ISSUE_LABELS", "env_bug,env_feature");
                let merged = merge_cli_and_env(&cfg, &scenario.cli_args);
                assert_eq!(merged.reference.as_deref(), Some("env_issue_ref"));
                assert_eq!(merged.labels.as_deref(), Some("env_bug,env_feature"));
            }
        }
        SubcommandType::Resolve => {
            if missing_env {
                let merged = merge_cli_and_env(&cfg, &scenario.cli_args);
                assert_eq!(merged.reference.as_deref(), Some("cli_resolve_ref"));
                assert_eq!(merged.status.as_deref(), Some("cli_closed"));
            } else {
                set_var("VKCMDS_RESOLVE_REFERENCE", "env_resolve_ref");
                set_var("VKCMDS_RESOLVE_STATUS", "env_closed");
                let merged = merge_cli_and_env(&cfg, &scenario.cli_args);
                assert_eq!(merged.reference.as_deref(), Some("env_resolve_ref"));
                assert_eq!(merged.status.as_deref(), Some("env_closed"));
            }
        }
    }

```

You may need to update the `TestScenario` struct to include a `name` field or another way to distinguish the new "missing env" cases. Also, ensure that the `pr_scenario()`, `issue_scenario()`, and `resolve_scenario()` functions can generate scenarios for both normal and missing environment variable cases.
</issue_to_address>

### Comment 4
<location> `tests/cli_args_merge.rs:265-274` </location>
<code_context>
+fn load_and_merge_uses_config_or_defaults(#[case] scenario: TestScenario) {
</code_context>

<issue_to_address>
**suggestion (testing):** Suggestion to add tests for invalid or corrupted config files.

Add tests for cases where the config file is missing, empty, or contains invalid TOML to verify error handling and robustness.

Suggested implementation:

```rust
#[rstest]
#[case::pr(pr_scenario())]
#[case::issue(issue_scenario())]
#[case::resolve(resolve_scenario())]
#[serial]
fn load_and_merge_uses_config_or_defaults(#[case] scenario: TestScenario) {
    let _guard = EnvGuard::new(scenario.env_vars);

    let cfg = config_for_scenario(&scenario, ConfigVariant::ConfigFallback);
    let (config_dir, _config_path) = setup_env_and_config(&cfg);

    match scenario.subcommand {
        SubcommandType::Pr => {
            let cli = PrArgs::default();
            let merged = cli.load_and_merge().expect("merge pr args");
        }
        // ... other subcommands
    }
}

/// Test: missing config file should fall back to defaults or error gracefully
#[test]
fn test_missing_config_file() {
    let scenario = pr_scenario();
    let _guard = EnvGuard::new(scenario.env_vars);

    // Simulate missing config by not creating any config file
    let config_dir = tempfile::tempdir().unwrap();
    std::env::set_var("CONFIG_DIR", config_dir.path());

    let cli = PrArgs::default();
    let result = cli.load_and_merge();
    assert!(result.is_ok(), "Should handle missing config gracefully");
}

/// Test: empty config file should fall back to defaults or error gracefully
#[test]
fn test_empty_config_file() {
    let scenario = pr_scenario();
    let _guard = EnvGuard::new(scenario.env_vars);

    let config_dir = tempfile::tempdir().unwrap();
    let config_path = config_dir.path().join("config.toml");
    std::fs::write(&config_path, "").unwrap();
    std::env::set_var("CONFIG_DIR", config_dir.path());

    let cli = PrArgs::default();
    let result = cli.load_and_merge();
    assert!(result.is_ok(), "Should handle empty config gracefully");
}

/// Test: corrupted/invalid TOML config file should error
#[test]
fn test_invalid_toml_config_file() {
    let scenario = pr_scenario();
    let _guard = EnvGuard::new(scenario.env_vars);

    let config_dir = tempfile::tempdir().unwrap();
    let config_path = config_dir.path().join("config.toml");
    std::fs::write(&config_path, "not = valid = toml").unwrap();
    std::env::set_var("CONFIG_DIR", config_dir.path());

    let cli = PrArgs::default();
    let result = cli.load_and_merge();
    assert!(result.is_err(), "Should error on invalid TOML config");
}

```

- If your config loading logic uses a different environment variable or file path, adjust the test setup accordingly.
- If `load_and_merge()` does not return a `Result`, you may need to update its implementation to do so, or adjust the assertions to match its error handling.
- You may need to import `tempfile` and other dependencies at the top of the file if not already present:
  ```rust
  use tempfile;
  use std::fs;
  use std::env;
  ```
</issue_to_address>

### Comment 5
<location> `src/environment.rs:10` </location>
<code_context>
+use std::ffi::OsStr;
+use std::sync::{Mutex, MutexGuard, OnceLock};
+
+static ENV_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
+
+fn lock() -> MutexGuard<'static, ()> {
</code_context>

<issue_to_address>
**issue (complexity):** Consider replacing OnceLock and unsafe usage with a static Mutex to simplify environment locking.

```rust
// ─ Simplify the global lock: no OnceLock or unsafe required ─

use std::env;
use std::ffi::OsStr;
use std::sync::Mutex;

// make the mutex a true `const fn` on recent Rust:
static ENV_LOCK: Mutex<()> = Mutex::new(());

// helper stays tiny (optional—tests can lock ENV_LOCK directly)
pub fn with_lock<T, F: FnOnce() -> T>(f: F) -> T {
    let _guard = ENV_LOCK.lock().unwrap();
    f()
}

pub fn set_var<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, val: V) {
    let _guard = ENV_LOCK.lock().unwrap();
    // safe std call, no `unsafe` needed
    env::set_var(key, val);
}

pub fn remove_var<K: AsRef<OsStr>>(key: K) {
    let _guard = ENV_LOCK.lock().unwrap();
    env::remove_var(key);
}

pub fn var<K: AsRef<OsStr>>(key: K) -> Result<String, env::VarError> {
    let _guard = ENV_LOCK.lock().unwrap();
    env::var(key)
}
```

Tests would then simply call `env::set_var`/`remove_var` (or `with_lock`) under the global mutex—no unsafe or OnceLock boilerplate remains.
</issue_to_address>

### Comment 6
<location> `src/environment.rs:59` </location>
<code_context>
+    op()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
</code_context>

<issue_to_address>
**issue (review_instructions):** Add behavioural tests for environment helpers; only unit tests are present.

The new environment helpers module includes unit tests, but does not demonstrate behavioural tests that exercise the integration of these helpers in realistic scenarios. Add behavioural tests to ensure correct synchronisation and interaction in multi-threaded or multi-test contexts.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</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 src/environment.rs
Comment thread tests/subcommand_merge.rs
Comment thread tests/cli_args_merge.rs
Comment thread tests/cli_args_merge.rs
Comment thread src/environment.rs
@leynos

leynos commented Sep 22, 2025

Copy link
Copy Markdown
Owner Author

@sourcery-ai resolve

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.

1 participant