Skip to content

feat: add folders, global SSH key download, and account resources#31

Open
MartaKar wants to merge 2 commits into
mainfrom
feat/api-coverage-folders-ssh-account
Open

feat: add folders, global SSH key download, and account resources#31
MartaKar wants to merge 2 commits into
mainfrom
feat/api-coverage-folders-ssh-account

Conversation

@MartaKar

@MartaKar MartaKar commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends the CLI's coverage of the DeployHQ API (DHQ-639). Adds six previously-missing resources, each with an SDK client, cobra command, agent metadata, assist allowlist entry, and unit tests.

Command Endpoints
dhq folders list|create|update|delete /folders (project categories)
dhq ssh-keys download <id> [--output] GET /ssh_keys/:id/download_private_key
dhq users list|show|create|update|delete|resend-invitation /users
dhq account get|update|billing /account, /account/billing_status
dhq profile get|update /profile
dhq api-keys create|delete /security/api_keys

Notes

  • Contracts were derived from the Rails backend (the bundled OpenAPI spec was stale and missed /folders), so request/response shapes, identifier-vs-id keying, and PATCH-vs-PUT usage match the real API. Users/account/profile updates use PATCH; folders uses PUT per its endpoint.
  • api-keys create returns the plaintext key once — human output prints it prominently with a "shown only once" warning; JSON mode includes it in the payload.
  • ssh-keys download writes to stdout (raw key in a TTY, JSON when piped) or to a file via --output, which is written with secure 0600 permissions (tightened even on a pre-existing file).
  • Nullable fields (account.project_limit, billing scheduled_change, folder position) are modeled as pointers/omitempty with explicit null round-trip tests.

Testing

  • Full unit-test suite green (go test ./...), go vet clean.
  • Every command verified end-to-end against a live backend — CRUD lifecycles, --json/--quiet/--table output, and 422/404/403 error paths.

Summary by CodeRabbit

  • New Features
    • Added account commands for viewing/updating account details and checking billing status.
    • Added profile commands to get and update your authenticated profile.
    • Added api-keys commands to create and delete API keys, with plaintext key shown once.
    • Added folders commands to list, create, update/rename, and delete account folders.
    • Added users team member commands (list/show/create/update/delete and resend invitations).
    • Added ssh-keys download to retrieve private keys, supporting JSON output and optional secure file writing.
  • Tests
    • Added CLI help/output and safety/automation metadata coverage across the new commands.

Extend CLI coverage of the DeployHQ API (DHQ-639):

- folders (project categories): dhq folders list|create|update|delete
- global SSH key download: dhq ssh-keys download <id> [--output]
- users:    dhq users list|show|create|update|delete|resend-invitation
- account:  dhq account get|update|billing
- profile:  dhq profile get|update
- api-keys: dhq api-keys create|delete

Each resource ships an SDK client, cobra command, agent metadata, assist
allowlist entry, and unit tests. Verified end-to-end against a live backend.
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 62f3aa93-f0a4-4d16-91eb-8c64b91a001c

📥 Commits

Reviewing files that changed from the base of the PR and between 786bf1f and 0780585.

📒 Files selected for processing (3)
  • internal/commands/agent_metadata.go
  • internal/commands/ssh_keys.go
  • internal/commands/ssh_keys_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/commands/ssh_keys_test.go
  • internal/commands/agent_metadata.go
  • internal/commands/ssh_keys.go

Walkthrough

Adds SDK and CLI support for account, profile, users, folders, API keys, and SSH private-key downloads. Registers the commands, adds JSON and human-readable output paths, expands agent metadata and prompt allowlists, and adds endpoint and CLI tests.

Changes

Resource command expansion

Layer / File(s) Summary
Account and profile SDK contracts
pkg/sdk/account.go, pkg/sdk/profile.go, pkg/sdk/*_test.go
Adds account, billing, and profile models, client methods, partial-update payloads, and HTTP tests.
User management SDK
pkg/sdk/users.go, pkg/sdk/users_test.go
Adds user listing, retrieval, creation, updates, deletion, and invitation resend operations with error coverage.
Folders and API key SDKs
pkg/sdk/folders.go, pkg/sdk/api_keys.go, pkg/sdk/*_test.go
Adds folder and API key endpoints, wrapped request payloads, and success and failure tests.
SSH private-key download
pkg/sdk/ssh_keys.go, internal/commands/ssh_keys.go, **/*ssh_keys_test.go
Adds private-key retrieval, stdout/file output handling, secure 0600 file permissions, and command metadata tests.
CLI resource command wiring
internal/commands/{account,profile,users,folders,api_keys}.go, internal/commands/root.go, internal/commands/*_test.go
Registers new command groups and implements JSON, table, quiet, validation, and status output paths.
Agent metadata and prompt allowlists
internal/commands/agent_metadata.go, internal/assist/prompt.go
Adds safety metadata and assistant allowlist entries for the new commands.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested reviewers: thdurante, facundofarias

🚥 Pre-merge checks | ✅ 4
✅ 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 reflects major parts of the PR, including folders, SSH key download, and account resources, though it omits users, profile, and API keys.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/api-coverage-folders-ssh-account

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

@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

🧹 Nitpick comments (6)
pkg/sdk/profile_test.go (1)

18-25: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Use a literal recorded JSON fixture for the profile response.

Encoding Profile here reuses the SDK tags being tested and can hide wire-format regressions. Return the actual response JSON shape instead.

As per coding guidelines, **/*_test.go should use httptest.NewServer for recorded response shapes and integration_test.go for real API JSON golden validation.

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

In `@pkg/sdk/profile_test.go` around lines 18 - 25, Replace the json.Encoder-based
Profile serialization in the test handler with a literal JSON fixture matching
the recorded API response shape, so the test does not reuse Profile’s JSON tags.
Keep the handler served through httptest.NewServer, and reserve real API JSON
golden validation for integration_test.go.

Source: Coding guidelines

pkg/sdk/account_test.go (1)

19-23: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Use literal recorded JSON fixtures for response-shape tests.

These handlers serialize Account and BillingStatus from the same SDK types being tested, so incorrect JSON tags or wire shapes could still round-trip successfully. Return captured JSON payloads instead.

As per coding guidelines, **/*_test.go should use httptest.NewServer for recorded response shapes and integration_test.go for real API JSON golden validation.

Also applies to: 68-68, 99-102

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

In `@pkg/sdk/account_test.go` around lines 19 - 23, Replace the test server
responses in the account response-shape tests with literal recorded JSON
fixtures instead of encoding `Account` or `BillingStatus` values, so
serialization mistakes cannot round-trip undetected. Update the handlers in the
affected tests, including the locations around the account and billing cases,
while continuing to use `httptest.NewServer`; reserve real API JSON golden
validation for `integration_test.go`.

Source: Coding guidelines

pkg/sdk/folders.go (1)

17-21: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Naming: FolderCreateRequest is reused for update, not just create.

Minor naming clarity nit; consider FolderRequest since the same type backs both CreateFolder and UpdateFolder.

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

In `@pkg/sdk/folders.go` around lines 17 - 21, Rename FolderCreateRequest to
FolderRequest because it is used by both CreateFolder and UpdateFolder, and
update all references consistently, including method signatures, comments, and
any related documentation.
internal/commands/folders_test.go (1)

13-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add execution coverage for folder handlers.

The tests do not invoke list/create/update/delete, so output precedence, API payloads, JSON responses, and --name validation are untested. Add command execution tests for these paths.

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

In `@internal/commands/folders_test.go` around lines 13 - 62, Expand the folder
command tests beyond registration and help checks by executing the list, create,
update, and delete handlers. Reuse the existing command/test setup and mock API
dependencies to assert output precedence, request payloads, JSON responses, and
required --name validation, covering each command’s success and relevant
validation paths.
internal/commands/ssh_keys_test.go (1)

11-34: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Exercise the download command, not only its registration.

These tests never execute the command, so regressions in --output, 0600 permissions, empty-key rejection, JSON output, raw stdout, and write-error handling can pass unnoticed. Add command-level execution tests using the existing test client/server setup.

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

In `@internal/commands/ssh_keys_test.go` around lines 11 - 34, Expand
TestSSHKeysDownloadCommand_Help and related SSH key tests to execute ssh-keys
download through the existing test client/server setup, not just inspect help or
metadata. Cover --output file creation with 0600 permissions, empty-key
rejection, JSON and raw stdout modes, and write-error handling, using the
command’s actual execution path and asserting results and errors.
internal/commands/users.go (1)

112-158: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce positional-parameter risk in userRequestFromFlags/addUserFlags.

Both helpers take 10-11 positional pointer params shared across two call sites each (create/update). A future flag insertion out of order (e.g. swapping two *bool params) would silently misassign fields since the compiler can't catch same-type-category swaps.

♻️ Suggested refactor: bundle flags into a struct
type userFlagVars struct {
	firstName, lastName, email, timeZone                                          string
	allProjects, admin, manageUsers, manageBilling, manageAgents, createProjects bool
}

func addUserFlags(cmd *cobra.Command, v *userFlagVars) {
	cmd.Flags().StringVar(&v.firstName, "first-name", "", "First name")
	// ... etc
}

func userRequestFromFlags(cmd *cobra.Command, v *userFlagVars) sdk.UserRequest {
	req := sdk.UserRequest{}
	if cmd.Flags().Changed("first-name") {
		req.FirstName = &v.firstName
	}
	// ... etc
	return req
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/commands/users.go` around lines 112 - 158, Reduce
positional-parameter risk in userRequestFromFlags and addUserFlags by
introducing a userFlagVars struct containing all string and boolean flag values.
Change both helpers and their create/update call sites to accept the struct
pointer, bind flags through its fields, and populate sdk.UserRequest pointers
from those fields only when the corresponding flags changed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/commands/agent_metadata.go`:
- Around line 279-283: Update the `dhq ssh-keys download` metadata entry to set
`RequiresConfirmation: true` and `SafeForAutomation: false`; remove
`SupportsJSON` unless its JSON output excludes the `private_key` material.

In `@internal/commands/api_keys.go`:
- Around line 71-93: The newAPIKeysDeleteCmd deletion flow lacks the required
confirmation gate. Before calling client.DeleteAPIKey, invoke the shared
cliCtx.RequiresConfirmation path with the API key identifier and return any
resulting error, ensuring non-interactive or declined confirmations prevent the
API call.

In `@internal/commands/ssh_keys.go`:
- Around line 162-176: Update writeSecureFile to avoid truncating the
destination directly: create a uniquely named temporary file in the destination
directory with 0600 permissions, write and close the complete contents, then
atomically rename it over the target. Ensure all failure paths close the file
and remove the temporary file, while preserving the existing permission
enforcement.
- Line 150: Handle the error returned by fmt.Fprintln when writing privateKey to
env.Stdout in the relevant command handler. If the write fails, propagate the
error through the handler’s existing error-return path instead of reporting
success.

In `@pkg/sdk/users.go`:
- Around line 54-93: Escape user identifiers with url.PathEscape before
interpolating them into endpoint paths in GetUser, UpdateUser, DeleteUser, and
ResendUserInvitation; add the required net/url import and use the escaped value
consistently when constructing each /users/... URL.

---

Nitpick comments:
In `@internal/commands/folders_test.go`:
- Around line 13-62: Expand the folder command tests beyond registration and
help checks by executing the list, create, update, and delete handlers. Reuse
the existing command/test setup and mock API dependencies to assert output
precedence, request payloads, JSON responses, and required --name validation,
covering each command’s success and relevant validation paths.

In `@internal/commands/ssh_keys_test.go`:
- Around line 11-34: Expand TestSSHKeysDownloadCommand_Help and related SSH key
tests to execute ssh-keys download through the existing test client/server
setup, not just inspect help or metadata. Cover --output file creation with 0600
permissions, empty-key rejection, JSON and raw stdout modes, and write-error
handling, using the command’s actual execution path and asserting results and
errors.

In `@internal/commands/users.go`:
- Around line 112-158: Reduce positional-parameter risk in userRequestFromFlags
and addUserFlags by introducing a userFlagVars struct containing all string and
boolean flag values. Change both helpers and their create/update call sites to
accept the struct pointer, bind flags through its fields, and populate
sdk.UserRequest pointers from those fields only when the corresponding flags
changed.

In `@pkg/sdk/account_test.go`:
- Around line 19-23: Replace the test server responses in the account
response-shape tests with literal recorded JSON fixtures instead of encoding
`Account` or `BillingStatus` values, so serialization mistakes cannot round-trip
undetected. Update the handlers in the affected tests, including the locations
around the account and billing cases, while continuing to use
`httptest.NewServer`; reserve real API JSON golden validation for
`integration_test.go`.

In `@pkg/sdk/folders.go`:
- Around line 17-21: Rename FolderCreateRequest to FolderRequest because it is
used by both CreateFolder and UpdateFolder, and update all references
consistently, including method signatures, comments, and any related
documentation.

In `@pkg/sdk/profile_test.go`:
- Around line 18-25: Replace the json.Encoder-based Profile serialization in the
test handler with a literal JSON fixture matching the recorded API response
shape, so the test does not reuse Profile’s JSON tags. Keep the handler served
through httptest.NewServer, and reserve real API JSON golden validation for
integration_test.go.
🪄 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: 3d7af336-04dd-45aa-ada9-f9aa86d2a8b1

📥 Commits

Reviewing files that changed from the base of the PR and between 00822d6 and 786bf1f.

📒 Files selected for processing (27)
  • internal/assist/prompt.go
  • internal/commands/account.go
  • internal/commands/account_test.go
  • internal/commands/agent_metadata.go
  • internal/commands/api_keys.go
  • internal/commands/api_keys_test.go
  • internal/commands/folders.go
  • internal/commands/folders_test.go
  • internal/commands/profile.go
  • internal/commands/profile_test.go
  • internal/commands/root.go
  • internal/commands/ssh_keys.go
  • internal/commands/ssh_keys_test.go
  • internal/commands/users.go
  • internal/commands/users_test.go
  • pkg/sdk/account.go
  • pkg/sdk/account_test.go
  • pkg/sdk/api_keys.go
  • pkg/sdk/api_keys_test.go
  • pkg/sdk/folders.go
  • pkg/sdk/folders_test.go
  • pkg/sdk/profile.go
  • pkg/sdk/profile_test.go
  • pkg/sdk/ssh_keys.go
  • pkg/sdk/ssh_keys_test.go
  • pkg/sdk/users.go
  • pkg/sdk/users_test.go

Comment thread internal/commands/agent_metadata.go
Comment thread internal/commands/api_keys.go
Comment thread internal/commands/ssh_keys.go Outdated
Comment thread internal/commands/ssh_keys.go Outdated
Comment thread pkg/sdk/users.go
- check errcheck on best-effort stdout write (fixes failing golangci-lint)
- write downloaded private keys atomically (temp file + rename) so an
  interrupted write can't destroy an existing key
- mark 'ssh-keys download' as requires-confirmation / not safe-for-automation
  since it emits raw private key material
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