Skip to content

feat(account): query handlers for account list/get/traffic (task 22)#126

Merged
mpiton merged 2 commits intomainfrom
feat/task-22-queries-accounts
Apr 29, 2026
Merged

feat(account): query handlers for account list/get/traffic (task 22)#126
mpiton merged 2 commits intomainfrom
feat/task-22-queries-accounts

Conversation

@mpiton
Copy link
Copy Markdown
Owner

@mpiton mpiton commented Apr 28, 2026

Summary

• Add three async query handlers for accounts: list_accounts, get_account, get_account_traffic
• Implement AccountViewDto read model without password field (type-level security guarantee)
• Support AND-combining optional filters on service_name, account_type, enabled
• Deliberately keep get_account_traffic side-effect-free (upstream refresh lives in command handler per CQRS rule)

Details

  • Query handlers: list (with filters), get (by id), traffic (persisted counters only)
  • Read model: AccountViewDto + AccountTrafficDto with serde camelCase for frontend
  • Test coverage: 16 new tests across 4 files, all passing (1122 tests total, 7 ignored)
  • Type safety: Password field intentionally omitted from DTO — enforced by Rust type system, not runtime checks
  • Filter semantics: service_name delegates to repo.list_by_service for SQL efficiency, others filter in-memory

Acceptance Criteria

✅ All 3 handlers implemented and async-tested
✅ AccountViewDto never serializes password (compiler enforced)
✅ Filters combine correctly (service + type + enabled = AND)
✅ Tests: 16 new unit tests with InMemoryAccountRepoForQueries mock
✅ Coverage: 98-100% on query handlers
✅ All acceptance criteria from task spec verified


Summary by cubic

Adds account query handlers for list, get, and traffic, wired into QueryBus, with safe account DTOs that include an opaque credential_ref but never a password. Implements Linear task 22 and keeps traffic queries side-effect free.

  • New Features

    • list_accounts, get_account, get_account_traffic handlers added to QueryBus.
    • AccountViewDto and AccountTrafficDto use camelCase, include credential_ref, and never include a password or raw credential.
    • Filters (service_name, account_type, enabled) AND-combine; service_name is pruned via repo list_by_service, others filter in memory.
    • Traffic query returns persisted counters only; refresh stays in the validate_account command.
  • Migration

    • When constructing QueryBus, pass the account repo via .with_account_repo(...).

Written for commit 991d18f. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Added account management with persistent storage, listing, and AND-combinable filtering (service, type, enabled).
    • Added account detail and traffic endpoints showing usage counters and timestamps.
    • Added credential import/export bundle support and opaque credential references; raw passwords are not exposed.
  • Documentation

    • Updated changelog with the new account-related features and behaviors.

Adds three CQRS query handlers wired through `QueryBus.with_account_repo`:
`list_accounts`, `get_account`, `get_account_traffic`. New read models
`AccountViewDto` and `AccountTrafficDto` (camelCase serde) carry every
persisted column except passwords — no credential field exists on the
type, so JSON output cannot leak one even by accident.

`AccountFilter` AND-combines `service_name`, `account_type`, `enabled`.
Service name is delegated to the repo's `list_by_service` for SQL-level
pruning; type and enabled filter in memory afterwards.

`get_account_traffic` returns persisted counters only — the upstream
refresh path is the existing `account_validate` command (task 21), so
queries stay side-effect free per the project CQRS rule.

21 new unit tests against an `InMemoryAccountRepoForQueries` fixture
cover filter combinations, missing-id 404s, missing-repo validation,
camelCase shape, and explicit "no password field" assertions on
`serde_json::to_value` output. Coverage: account_view 100%,
list_accounts 99.23%, get_account 98.29%, get_account_traffic 98.43%.
Unblocks task 23 (Vue Accounts).
@github-actions github-actions Bot added documentation Improvements or additions to documentation rust labels Apr 28, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

📝 Walkthrough

Walkthrough

Adds account-focused CQRS query handlers, new account read-model DTOs, QueryBus wiring for an optional account repository, in-memory test repository support, and CHANGELOG entry describing the account features.

Changes

Cohort / File(s) Summary
Query Handlers
src-tauri/src/application/queries/get_account.rs, src-tauri/src/application/queries/get_account_traffic.rs, src-tauri/src/application/queries/list_accounts.rs, src-tauri/src/application/queries/mod.rs
New handlers: handle_get_account, handle_get_account_traffic, handle_list_accounts. Each validates presence of account_repo, maps domain Account → DTOs, handles NotFound/Validation errors, and includes unit tests for DTO shape and error cases. List supports AND-combined filters.
Read Models & DTOs
src-tauri/src/application/read_models/account_view.rs, src-tauri/src/application/read_models/mod.rs
Adds AccountViewDto and AccountTrafficDto with serde camelCase, From<Account> conversions, and tests ensuring sensitive fields (password/credential) are excluded from serialization.
QueryBus Infrastructure
src-tauri/src/application/query_bus.rs
Adds optional account_repo: Option<Arc<dyn AccountRepository>>, builder with_account_repo, and accessor account_repo(). Tests verify default None, injection semantics, and call routing.
Test Support
src-tauri/src/application/test_support.rs
Introduces InMemoryAccountRepoForQueries with mutex-backed storage, uniqueness enforcement, deterministic ordering, and query_bus_with_accounts helper for tests.
Changelog
CHANGELOG.md
Documents the newly implemented account features (CQRS query handlers, command handlers and persistence — high-level entry).

Sequence Diagram

sequenceDiagram
    actor Client
    participant QueryBus
    participant Handler as Query Handler
    participant Repo as AccountRepository
    participant Domain as Account (domain)
    participant DTO as AccountViewDto

    Client->>QueryBus: handle_list_accounts(query)
    QueryBus->>Handler: dispatch query
    Handler->>QueryBus: account_repo()? 
    alt repository present
        Handler->>Repo: list() / list_by_service(filter.service_name)
        Repo-->>Handler: Vec<Account>
        Handler->>Handler: apply remaining filters (type, enabled)
        loop per matching Account
            Handler->>Domain: select Account
            Domain->>DTO: From<Account> -> AccountViewDto
            DTO-->>Handler: AccountViewDto
        end
        Handler-->>QueryBus: Ok(Vec<AccountViewDto>)
    else repository missing
        Handler-->>QueryBus: Err(AppError::Validation)
    end
    QueryBus-->>Client: Result<Vec<AccountViewDto>, AppError>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 In burrows of code I hop and cheer,

Filters combined and DTOs appear,
No passwords shown, just traffic and name,
The bus carries accounts, tidy and tame,
Hooray — a hop for query-kind! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: implementation of CQRS query handlers for three account operations (list, get, traffic), matching the changeset content perfectly.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/task-22-queries-accounts

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src-tauri/src/application/query_bus.rs (1)

62-72: Add a focused unit test for the new account-repo builder/accessor pair.

This new wiring is central to the account query handlers; a small constructor-chain test here would guard regressions in with_account_repo(...).account_repo().

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

In `@src-tauri/src/application/query_bus.rs` around lines 62 - 72, Add a focused
unit test that constructs the builder, wraps a simple test implementation of
AccountRepository in an Arc, calls with_account_repo(...) and then asserts that
account_repo() returns Some(...) and refers to the same underlying instance;
implement a tiny test-double struct implementing AccountRepository (or use a
mock helper if available), pass Arc::new(test_double) into with_account_repo on
the builder, and assert the returned Option<&dyn AccountRepository> is Some and
behaves or compares equal to the original Arc-held instance to guard the
with_account_repo/account_repo wiring.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src-tauri/src/application/queries/list_accounts.rs`:
- Around line 4-6: The module-level doc comment in list_accounts.rs should be
reworded to clarify that “no credential can leak” means there is no password or
raw secret material in the DTO, not that it contains no credential-related
identifiers; update the comment that currently reads “The DTO has no password
field, so no credential can leak through this path by construction.” to
explicitly state something like “The DTO contains no password or raw secret
material (no plaintext secrets), though non-secret credential references (e.g.
usernames or credential IDs) may still be present.” Ensure you edit the
top-of-file doc comment where that sentence appears.

In `@src-tauri/src/application/read_models/account_view.rs`:
- Around line 15-18: The doc comment currently says the DTO excludes credential
references but AccountViewDto contains the field credential_ref; update the
comment to state that the DTO mirrors persisted accounts and does include a
non-secret credential_ref (used only by the frontend to look up a keyring entry
for "test connection"), and clarify that the actual password/secret is fetched
server-side and not carried in credential_ref; refer to AccountViewDto and its
credential_ref field when making this doc change.

---

Nitpick comments:
In `@src-tauri/src/application/query_bus.rs`:
- Around line 62-72: Add a focused unit test that constructs the builder, wraps
a simple test implementation of AccountRepository in an Arc, calls
with_account_repo(...) and then asserts that account_repo() returns Some(...)
and refers to the same underlying instance; implement a tiny test-double struct
implementing AccountRepository (or use a mock helper if available), pass
Arc::new(test_double) into with_account_repo on the builder, and assert the
returned Option<&dyn AccountRepository> is Some and behaves or compares equal to
the original Arc-held instance to guard the with_account_repo/account_repo
wiring.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6c86d702-04c5-4949-9cd3-057370d3db09

📥 Commits

Reviewing files that changed from the base of the PR and between f83264a and 2ff5ccc.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • src-tauri/src/application/queries/get_account.rs
  • src-tauri/src/application/queries/get_account_traffic.rs
  • src-tauri/src/application/queries/list_accounts.rs
  • src-tauri/src/application/queries/mod.rs
  • src-tauri/src/application/query_bus.rs
  • src-tauri/src/application/read_models/account_view.rs
  • src-tauri/src/application/read_models/mod.rs
  • src-tauri/src/application/test_support.rs

Comment thread src-tauri/src/application/queries/list_accounts.rs Outdated
Comment thread src-tauri/src/application/read_models/account_view.rs Outdated
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 9 files

- Reword list_accounts.rs doc to make explicit the secret-leak guarantee
  is about password/raw secret material, not non-secret credential
  references like usernames or opaque keyring URIs.
- Fix AccountViewDto doc that contradicted the struct: it does carry
  a non-secret credential_ref (opaque keyring URI), while passwords
  remain server-side via the keyring.
- Add unit test wiring with_account_repo() through account_repo() to
  guard the builder/accessor pair against silent regressions.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src-tauri/src/application/queries/list_accounts.rs (1)

29-40: Optional: avoid intermediate allocation in filter→map pipeline.

You can map directly to AccountViewDto after filtering to skip building a temporary Vec<Account>.

♻️ Suggested micro-refactor
-        let filtered: Vec<Account> = accounts
-            .into_iter()
-            .filter(|a| match &query.filter {
-                None => true,
-                Some(f) => {
-                    f.account_type.is_none_or(|t| a.account_type() == t)
-                        && f.enabled.is_none_or(|e| a.is_enabled() == e)
-                }
-            })
-            .collect();
-
-        Ok(filtered.into_iter().map(AccountViewDto::from).collect())
+        Ok(accounts
+            .into_iter()
+            .filter(|a| match &query.filter {
+                None => true,
+                Some(f) => {
+                    f.account_type.is_none_or(|t| a.account_type() == t)
+                        && f.enabled.is_none_or(|e| a.is_enabled() == e)
+                }
+            })
+            .map(AccountViewDto::from)
+            .collect())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/application/queries/list_accounts.rs` around lines 29 - 40,
Current code creates an intermediate Vec<Account> named filtered before mapping
to AccountViewDto; change the pipeline to avoid that allocation by chaining the
iterator: call accounts.into_iter().filter(...) and then
.map(AccountViewDto::from).collect() directly, keeping the same filter closure
that uses query.filter, f.account_type/is_none_or and f.enabled/is_none_or with
a.account_type()/a.is_enabled() so the behavior is unchanged but the temporary
Vec<Account> is eliminated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src-tauri/src/application/queries/list_accounts.rs`:
- Around line 29-40: Current code creates an intermediate Vec<Account> named
filtered before mapping to AccountViewDto; change the pipeline to avoid that
allocation by chaining the iterator: call accounts.into_iter().filter(...) and
then .map(AccountViewDto::from).collect() directly, keeping the same filter
closure that uses query.filter, f.account_type/is_none_or and
f.enabled/is_none_or with a.account_type()/a.is_enabled() so the behavior is
unchanged but the temporary Vec<Account> is eliminated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 956e2f44-8615-4ead-af41-374d5f388a9e

📥 Commits

Reviewing files that changed from the base of the PR and between 2ff5ccc and 991d18f.

📒 Files selected for processing (3)
  • src-tauri/src/application/queries/list_accounts.rs
  • src-tauri/src/application/query_bus.rs
  • src-tauri/src/application/read_models/account_view.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src-tauri/src/application/query_bus.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation rust

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant