mirror of
https://github.com/coralogix/cx-cli.git
synced 2026-09-14 16:15:45 +08:00
13915ea843
AIAG-696 ## Summary ### Full API coverage - Implement remaining cx CLI commands covering all Coralogix APIs (dashboards, views, SLOs, incidents, notifications, webhooks, enrichments, parsing-rules, TCO, quotas, usage, archive, integrations, IAM, recording-rules, E2M, search-fields) - 27 command groups total - Add user-facing skills for all command groups (cx-observability-setup, cx-cost-optimization, cx-incident-management, cx-data-pipeline, cx-platform-admin, cx-create-dashboard) - Rename all skills to cx- prefix for consistency, update cross-references and docs - Add integration tests (wiremock) and E2E test scaffolding for all new commands - Update CLAUDE.md, README, CODEOWNERS, architecture docs, and contributor guides - Bug fixes: data_usage timestamp parsing, users error propagation, team_groups unused params, dashboard skill API wire values, stale skill paths ### CLI safety features - Add `--read-only` global flag that blocks all write operations before credential resolution - safe for exploration and agent use - Add agent mode detection (`CX_AGENT_MODE`, `CLAUDECODE`, `CLAUDE_CODE` env vars) - blocks write operations unless `--yes` is passed - Wire `confirm_destructive()` to all write subcommands across all 27 command groups (104 call sites) - interactive confirmation for create/update/delete/enable/disable operations - Mark `iam` and `archive` as `(risky)` in help output - Add `[requires --yes]` tags to all write subcommand help text (102 annotations) - Update skills with read-only and agent mode guidance ### Testing - 441 tests passing (88 ignored E2E) - Add `tests/write_command_gating/` - systematic verification that every write command is gated - Add `tests/read_only/` - read-only mode enforcement tests - Add `tests/agent_mode/` - agent mode detection and blocking tests - Add integration tests (wiremock) for all new command groups - Add E2E test scaffolding for all commands ## Test plan - [x] `cargo fmt --check` passes - [x] `cargo clippy` passes with no warnings - [x] `cargo test` passes (441 unit + integration tests) - [ ] `cargo test --test e2e -- --ignored --test-threads=1` passes against test team (requires CX_API_KEY) - [ ] `cx schema` outputs updated command tree with all new commands - [ ] Skills trigger correctly in Claude Code sessions --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
87 lines
2.8 KiB
Rust
87 lines
2.8 KiB
Rust
use std::sync::OnceLock;
|
|
|
|
use crate::harness;
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn api_keys_list() {
|
|
if harness::require_creds("api_keys_list").is_none() {
|
|
return;
|
|
}
|
|
let v = harness::run_ok_json(&["iam", "api-keys", "list", "-o", "json"]);
|
|
harness::assert_nonempty_array_of_objects_with_keys(&v, &["id", "name"]);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn api_keys_get() {
|
|
if harness::require_creds("api_keys_get").is_none() {
|
|
return;
|
|
}
|
|
let id = match discover_api_key_id() {
|
|
Some(id) => id,
|
|
None => {
|
|
eprintln!("[e2e] skipping api_keys_get: no API keys available on test team");
|
|
return;
|
|
}
|
|
};
|
|
let v = harness::run_ok_json(&["iam", "api-keys", "get", &id, "-o", "json"]);
|
|
harness::assert_get_response(&v, &["id", "name"]);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn api_keys_send_data_keys() {
|
|
if harness::require_creds("api_keys_send_data_keys").is_none() {
|
|
return;
|
|
}
|
|
let _v = harness::run_ok_json(&["iam", "api-keys", "send-data-keys", "-o", "json"]);
|
|
}
|
|
|
|
fn discover_api_key_id() -> Option<String> {
|
|
static CACHE: OnceLock<Option<String>> = OnceLock::new();
|
|
CACHE
|
|
.get_or_init(|| {
|
|
if harness::require_creds("api_keys_discover").is_none() {
|
|
return None;
|
|
}
|
|
let stdout = harness::run_ok(&["iam", "api-keys", "list", "-o", "json"]);
|
|
let v = harness::parse_json(&stdout)?;
|
|
// The list response may be a flat array with "id" field,
|
|
// or wrapped in a "keys" array with nested "keyInfo.keyId".
|
|
let val_to_string = |x: &serde_json::Value| match x {
|
|
serde_json::Value::String(s) => s.clone(),
|
|
other => other.to_string().trim_matches('"').to_string(),
|
|
};
|
|
if let Some(arr) = v.as_array() {
|
|
if let Some(id) = arr
|
|
.first()
|
|
.and_then(|item| item.get("id"))
|
|
.map(&val_to_string)
|
|
{
|
|
return Some(id);
|
|
}
|
|
// Try nested keyInfo.keyId pattern
|
|
if let Some(id) = arr
|
|
.first()
|
|
.and_then(|item| item.get("keyInfo"))
|
|
.and_then(|ki| ki.get("keyId"))
|
|
.map(&val_to_string)
|
|
{
|
|
return Some(id);
|
|
}
|
|
}
|
|
// Try wrapped "keys" array
|
|
v.get("keys")
|
|
.and_then(|k| k.as_array())
|
|
.and_then(|arr| arr.first())
|
|
.and_then(|item| {
|
|
item.get("keyInfo")
|
|
.and_then(|ki| ki.get("keyId"))
|
|
.map(&val_to_string)
|
|
.or_else(|| item.get("id").map(&val_to_string))
|
|
})
|
|
})
|
|
.clone()
|
|
}
|