mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
Rewrite the CLI as a thin client over the authenticated API so agents and scripts reach every resource the UI does (ROADMAP2 phase A2). - shared Client with bearer auth, error-envelope decoding, and stable exit codes: 0 ok / 1 error / 2 auth / 3 not-found / 4 validation / 5 unreachable - named profiles in ~/.containr.yaml (current_profile + profiles.*); resolution order --flag > env > profile > localhost default; CONTAINR_TOKEN / CONTAINR_API_URL / CONTAINR_PROFILE env support - --json prints raw API responses; --yes skips confirmations; destructive commands refuse non-interactively without it - auth login verifies the token against /user/profile before saving, interactive prompt via term.ReadPassword - commands for every resource: tokens, projects, services, deploy, deployments (logs/rollback), logs --follow (SSE), exec, variables (set/unset merge with secret masking preserved), databases (backup/restore/download), cron, templates, nodes + agent tokens, scaling, ha, security, gateway, admin, notifications - containr up: git remote + branch → create-or-reuse service → deploy Verified live: login/status/tokens/projects/services, up creating a service + deployment, non-TTY confirmation refusal, scope-capped admin rejection (exit 2), revoke. Authored By: TDvorak <[email protected]>
58 lines
1.7 KiB
Go
58 lines
1.7 KiB
Go
package commands
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
func TestResolveAPIURLDefaultsAndTrimming(t *testing.T) {
|
|
viper.Set("api-url", "")
|
|
viper.Set("current_profile", "")
|
|
viper.Set("profiles.default.api_url", "")
|
|
if got := ResolveAPIURL(); got != "http://localhost:8080/api/v1" {
|
|
t.Fatalf("unexpected default api url: %s", got)
|
|
}
|
|
|
|
viper.Set("api-url", "https://api.example.com/api/v1/")
|
|
if got := ResolveAPIURL(); got != "https://api.example.com/api/v1" {
|
|
t.Fatalf("unexpected trimmed api url: %s", got)
|
|
}
|
|
|
|
// Bare hosts gain the /api/v1 suffix.
|
|
viper.Set("api-url", "https://containr.example.com")
|
|
if got := ResolveAPIURL(); got != "https://containr.example.com/api/v1" {
|
|
t.Fatalf("unexpected suffixed api url: %s", got)
|
|
}
|
|
viper.Set("api-url", "")
|
|
}
|
|
|
|
func TestResolveTokenProfileAndEnv(t *testing.T) {
|
|
viper.Set("token", "")
|
|
viper.Set("current_profile", "staging")
|
|
viper.Set("profiles.staging.token", "cnp_staging")
|
|
if got := ResolveToken(); got != "cnp_staging" {
|
|
t.Fatalf("expected profile token, got %s", got)
|
|
}
|
|
t.Setenv("CONTAINR_TOKEN", "cnp_env")
|
|
if got := ResolveToken(); got != "cnp_env" {
|
|
t.Fatalf("env should win over profile, got %s", got)
|
|
}
|
|
viper.Set("token", "cnp_flag")
|
|
if got := ResolveToken(); got != "cnp_flag" {
|
|
t.Fatalf("flag should win over env, got %s", got)
|
|
}
|
|
}
|
|
|
|
func TestExitCodeMapping(t *testing.T) {
|
|
if got := exitCodeFor(401); got != ExitAuth {
|
|
t.Fatalf("401 → %d, want %d", got, ExitAuth)
|
|
}
|
|
if got := exitCodeFor(404); got != ExitNotFound {
|
|
t.Fatalf("404 → %d, want %d", got, ExitNotFound)
|
|
}
|
|
if got := exitCodeFor(400); got != ExitValidation {
|
|
t.Fatalf("400 → %d, want %d", got, ExitValidation)
|
|
}
|
|
}
|