Skip to content

Code cleanup before v1 - #12

Merged
ayarotsky merged 1 commit into
masterfrom
chore_code_clean_up
Apr 21, 2026
Merged

Code cleanup before v1#12
ayarotsky merged 1 commit into
masterfrom
chore_code_clean_up

Conversation

@ayarotsky

@ayarotsky ayarotsky commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added config unset and config reset commands
    • Global --organization CLI option to override organization per request
    • Task to refresh and update the schema fixture/snapshot
  • Bug Fixes / Reliability

    • Atomic, safer config file writes to avoid partial/corrupt saves
    • More consistent token parsing/storage and safer secret entry (hidden password prompt)
  • Documentation

  • Tests

    • Expanded integration and snapshot tests covering config, auth, schema, and organization override behavior

@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Fixes OAuth callback to a fixed localhost port, introduces TokenSet parsing/storage, centralizes atomic file writes in a persistence module, exposes crate-scoped schema constructor, adds config unset/reset and store_tokens, threads a global --organization override, and updates CLI, docs, tests, and snapshot tooling.

Changes

Cohort / File(s) Summary
Dependencies & Docs
Cargo.toml, README.md, justfile
Added tempfile, rpassword, and insta; README OAuth redirect now http://127.0.0.1:19876/callback with exact-match/port note; added just task refresh-schema-fixture.
Persistence module
src/persistence.rs, src/schema.rs
New public write_atomic(path, bytes) implementing temp-file -> persist with Unix perms; schema.rs uses shared persistence and ApiSchema::from_schema made pub(crate).
Authentication
src/auth.rs, tests
Switched callback binding to fixed CALLBACK_PORT/CALLBACK_REDIRECT_URI; build_auth_url returns Result; introduced exported TokenSet and TokenSet::from_json; token exchange/refresh use TokenSet and persisted via config.store_tokens; tests updated.
Configuration core
src/config.rs, tests
Struct-level serde(default) and moved defaults to Default impl; added ensure_dir(), store_tokens(), unset(), and reset(); save() uses write_atomic; tests for token expiry clearing and reset/unset behavior added.
Config CLI commands
src/config_commands.rs
Added config unset and config reset; set token/set_pat now parse via TokenSet::from_json and call store_tokens; OAuth setup shows 127.0.0.1 redirect and uses rpassword for secret input; env writing uses ensure_dir + write_atomic.
API & CLI runtime
src/api.rs, src/command_index.rs, src/main.rs
run_dynamic signature gains organization_override: Option<u64> and forwards it to resolution; replaced local atomic writes with persistence::write_atomic; added insta snapshot test schema_command_table_snapshot; CLI gains global --organization and config unset/reset subcommands; wiring updated to call auth directly.
Removed delegation module
src/login.rs
Removed trivial login() / logout() delegations; callers now call auth directly.
Integration & snapshot tests
tests/commands_test.rs, src/command_index.rs test
Tests now use per-test temp XDG dirs; added tests for config set token behavior, config unset/reset cases and failures, --organization override precedence, and an insta snapshot for the schema command table.

Sequence Diagram(s)

sequenceDiagram
    participant User as User (CLI)
    participant Browser as Browser
    participant OAuth as OAuth Server
    participant Callback as Local Callback (127.0.0.1:19876)
    participant TokenAPI as Token Endpoint
    participant Config as Config Store
    participant FS as Persistence (write_atomic)

    User->>Browser: open auth URL (build_auth_url)
    Browser->>OAuth: authenticate & authorize
    OAuth->>Callback: redirect with code
    Callback->>User: deliver code to CLI flow
    User->>TokenAPI: exchange code for tokens
    TokenAPI-->>User: return token JSON
    User->>Config: TokenSet::from_json & store_tokens
    Config->>FS: write_atomic(config.toml)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'Code cleanup before v1' is vague and generic, using a non-descriptive phrase that doesn't convey the specific changes involved. Use a more specific title that captures the main changes, such as 'Refactor auth flow, add config commands, and support org override' or describe the most impactful change explicitly.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 chore_code_clean_up

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

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

🧹 Nitpick comments (2)
src/api.rs (1)

665-667: Resolve organization ID once per operation fill pass.

If both required path and query organization_id are present, this can call resolve_organization more than once. Cache it locally to avoid repeated resolution.

♻️ Proposed refactor
 fn fill_defaults_from_config(
     operation: &Operation,
     client: &HubstaffClient,
     organization_override: Option<u64>,
     path_values: &mut HashMap<String, String>,
     query_values: &mut HashMap<String, String>,
 ) -> Result<(), CliError> {
+    let mut resolved_organization_id: Option<String> = None;
+
     for parameter in operation
         .parameters
         .iter()
         .filter(|parameter| parameter.required)
     {
@@
-        let organization_id = client
-            .resolve_organization(organization_override)?
-            .to_string();
+        let organization_id = if let Some(value) = &resolved_organization_id {
+            value.clone()
+        } else {
+            let value = client.resolve_organization(organization_override)?.to_string();
+            resolved_organization_id = Some(value.clone());
+            value
+        };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api.rs` around lines 665 - 667, The code calls
client.resolve_organization(organization_override) multiple times when both path
and query organization_id are present; change the logic to call
client.resolve_organization once per operation fill pass, store its result in a
local variable (e.g., organization_id from resolve_organization) and reuse that
cached string wherever organization resolution is needed (referencing
resolve_organization and organization_id) so the resolution is not performed
repeatedly.
justfile (1)

39-39: Consider hardening schema fixture download for transient network failures.

Adding retry/timeout flags will make this maintenance command less flaky.

🛠️ Suggested tweak
-    curl -sSf https://api.hubstaff.com/v2/docs -o tests/fixtures/schema.json
+    curl -sSf --retry 3 --retry-all-errors --connect-timeout 10 --max-time 60 \
+      https://api.hubstaff.com/v2/docs -o tests/fixtures/schema.json
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@justfile` at line 39, Update the curl invocation "curl -sSf
https://api.hubstaff.com/v2/docs -o tests/fixtures/schema.json" to be resilient
to transient network failures by adding retry and timeout flags: configure a
finite retry count (e.g. 2–5 retries), a short retry delay, enable retry on
connection refused, and set an overall max-time/timeout so the command fails
fast on long hangs; keep existing silent/fail behavior and still write to
tests/fixtures/schema.json.
🤖 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/auth.rs`:
- Around line 10-11: The redirect URI constant and the local HTTP listener
disagree on host (CALLBACK_REDIRECT_URI uses "localhost" while the server binds
to "127.0.0.1"), causing missed callbacks when "localhost" resolves to ::1;
update the code so both sides use the same host: either change
CALLBACK_REDIRECT_URI to "http://127.0.0.1:19876/callback" (preferred) or modify
the listener that currently binds to "127.0.0.1" to bind to both IPv4/IPv6
loopback (e.g., "::1" or both addresses) so CALLBACK_PORT and
CALLBACK_REDIRECT_URI remain consistent; apply the same change to the other
occurrences referenced around the server binding (the code that constructs the
listener and uses CALLBACK_PORT/CALLBACK_REDIRECT_URI).

In `@src/config.rs`:
- Around line 123-128: The store_tokens method currently retains the old
auth.expires_at when the incoming TokenSet has expires_at == None; update the
logic in pub fn store_tokens(&mut self, tokens: TokenSet) so that if
tokens.expires_at is Some(value) you set self.auth.expires_at = Some(value),
otherwise you explicitly clear self.auth.expires_at = None to avoid carrying
stale expiry metadata from a previous token; reference store_tokens,
TokenSet::expires_at, and self.auth.expires_at to locate and change the
assignment.

In `@src/persistence.rs`:
- Around line 6-15: The current code uses a fixed sibling temp path
(path.with_extension("tmp")) which can collide across concurrent writers; change
the temp file creation to use a unique per-write temporary filename in the same
directory (e.g., include a UUID/timestamp+PID/random suffix or use a temp file
API that creates a NamedTempFile in the target dir) instead of a fixed ".tmp"
name, write bytes to that unique temp_path, set permissions via the existing
unix block (fs::set_permissions / PermissionsExt) and then atomically fs::rename
the unique temp_path to path; update references to temp_path, fs::write, and
fs::rename accordingly so each write uses its own temp file.

In `@tests/commands_test.rs`:
- Around line 217-218: Replace the fixed "/tmp/hcli-test-*" paths with per-test
temporary directories (e.g., using tempfile::TempDir or tempfile::Builder to
create a unique dir) and update uses of the dir variable accordingly;
specifically change the `let dir = "/tmp/hcli-test-set-token-clears-refresh";`
(and the other occurrences at the mentioned locations) to create a TempDir, use
its path() for subsequent filesystem operations, and remove the manual
std::fs::remove_dir_all call (or scope cleanup) so tests no longer rely on
deterministic global paths and are safe for parallel runs and aborted runs.

---

Nitpick comments:
In `@justfile`:
- Line 39: Update the curl invocation "curl -sSf
https://api.hubstaff.com/v2/docs -o tests/fixtures/schema.json" to be resilient
to transient network failures by adding retry and timeout flags: configure a
finite retry count (e.g. 2–5 retries), a short retry delay, enable retry on
connection refused, and set an overall max-time/timeout so the command fails
fast on long hangs; keep existing silent/fail behavior and still write to
tests/fixtures/schema.json.

In `@src/api.rs`:
- Around line 665-667: The code calls
client.resolve_organization(organization_override) multiple times when both path
and query organization_id are present; change the logic to call
client.resolve_organization once per operation fill pass, store its result in a
local variable (e.g., organization_id from resolve_organization) and reuse that
cached string wherever organization resolution is needed (referencing
resolve_organization and organization_id) so the resolution is not performed
repeatedly.
🪄 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: b80b30cc-28c0-4e0c-94a0-359867522473

📥 Commits

Reviewing files that changed from the base of the PR and between 77cf0c0 and 317fd60.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • src/snapshots/hubstaff__command_index__tests__schema_command_table_snapshot.snap is excluded by !**/*.snap
📒 Files selected for processing (14)
  • Cargo.toml
  • README.md
  • justfile
  • src/api.rs
  • src/auth.rs
  • src/command_index.rs
  • src/config.rs
  • src/config_commands.rs
  • src/login.rs
  • src/main.rs
  • src/persistence.rs
  • src/schema.rs
  • tests/commands_test.rs
  • tests/fixtures/schema.json
💤 Files with no reviewable changes (1)
  • src/login.rs

Comment thread src/auth.rs Outdated
Comment thread src/config.rs Outdated
Comment thread src/persistence.rs Outdated
Comment thread tests/commands_test.rs 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

🤖 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/auth.rs`:
- Around line 145-156: In from_json, do not call chrono::Duration::seconds() or
use direct DateTime + Duration arithmetic on the raw expires_in because those
can panic on out-of-range values; instead parse expires_in with as_i64(),
convert to a Duration using chrono::Duration::try_seconds(secs) and then use
chrono::Utc::now().checked_add_signed(duration) to produce expires_at, and if
either try_seconds or checked_add_signed returns None or the field is missing,
return Err(CliError::Auth(...)) so malformed/too-large expires_in values produce
an error rather than a panic.

In `@src/config.rs`:
- Around line 154-175: When the "auth_url" branch updates self.auth_url (the
match arm that sets DEFAULT_AUTH_URL) you must also clear any preserved
authentication to avoid using stale refresh tokens; update that branch to
reset/clear self.auth when changing auth_url. Likewise, modify pub fn reset(&mut
self) so it does not keep the prior auth: instead of preserving auth via let
auth = std::mem::take(&mut self.auth) and reusing it, set self.auth to its
default/empty state (or otherwise couple tokens to the endpoint) so reset()
clears credentials along with other fields.
🪄 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: 4f9c6ca6-9239-4c74-ac6e-53f821c10a77

📥 Commits

Reviewing files that changed from the base of the PR and between 317fd60 and 6fff317.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • Cargo.toml
  • README.md
  • src/auth.rs
  • src/config.rs
  • src/config_commands.rs
  • src/persistence.rs
  • tests/commands_test.rs
✅ Files skipped from review due to trivial changes (1)
  • Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (3)
  • README.md
  • src/persistence.rs
  • tests/commands_test.rs

Comment thread src/auth.rs
Comment thread src/config.rs
@ayarotsky
ayarotsky force-pushed the chore_code_clean_up branch from 77f5044 to 9426a1b Compare April 21, 2026 19:43
@ayarotsky
ayarotsky merged commit 491b447 into master Apr 21, 2026
6 of 7 checks passed
@ayarotsky
ayarotsky deleted the chore_code_clean_up branch April 21, 2026 19:49
This was referenced Apr 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant