use assert_cmd::cargo::cargo_bin;
use predicates::prelude::*;
use sylphx_wire_mock::WireMockServer;
use tokio::process::Command;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn cli_all_shipped_commands_twice() {
let mock = WireMockServer::start().await;
let base = mock.management_base_url();
let bin = cargo_bin!("sylphx");
let runs: &[&[&str]] = &[
&["health", "--json"],
&["whoami", "--json"],
&["orgs", "list", "--json"],
&["projects", "list", "--json"],
&["projects", "get", "prj_demo", "--json"],
&["projects", "create", "NewApp", "--slug", "new-app", "--json"],
&["environments", "list", "prj_demo", "--json"],
&["environments", "get", "prj_demo", "env_prod", "--json"],
&["deployments", "list", "prj_demo", "--json"],
&["deploy", "prj_demo", "--env", "production", "--json"],
&["rollback", "prj_demo", "--json"],
&["status", "prj_demo", "--json"],
&["env", "list", "prj_demo", "--env", "production", "--json"],
&[
"env",
"set",
"prj_demo",
"FOO=bar",
"--env",
"production",
"--json",
],
&["env", "rm", "prj_demo", "FOO", "--env", "production", "--json"],
&["logs", "--project", "prj_demo", "--type", "build", "--tail", "10", "--json"],
&[
"logs",
"--environment",
"env_prod",
"--type",
"runtime",
"--tail",
"10",
"--json",
],
&["resources", "list", "--kind", "database", "--json"],
&["resources", "get", "res_db1", "--json"],
&[
"resources",
"create",
"database",
"analytics",
"--project",
"prj_demo",
"--tier",
"standard",
"--storage",
"10",
"--json",
],
&["resources", "bind", "res_db1", "env_prod", "--json"],
&["resources", "delete", "res_analytics", "--json"],
&[
"db",
"create",
"app-db",
"--project",
"prj_demo",
"--tier",
"standard",
"--storage",
"10",
"--json",
],
&["db", "list", "--project", "prj_demo", "--json"],
&["db", "get", "res_db1", "--json"],
&["db", "branch", "res_db1", "--name", "staging-copy", "--json"],
&["db", "delete", "res_app-db", "--json"],
&["storage", "create", "prj_demo", "primary", "--json"],
&["storage", "list", "prj_demo", "--json"],
&["tasks", "list", "--project", "prj_demo", "--json"],
&[
"tasks",
"create",
"send-email",
"--mode",
"job",
"--handler",
"src/tasks/send-email.ts",
"--json",
],
&["tasks", "get", "task_1", "--json"],
&["backup", "list", "--resource", "res_db1", "--json"],
&["backup", "create", "--resource", "res_db1", "--tag", "pre", "--json"],
&["backup", "restore", "bak_1", "--resource", "res_db1", "--json"],
&["flags", "list", "--project", "prj_demo", "--json"],
&["flags", "create", "checkout-v2", "--json"],
&["flags", "toggle", "checkout-v2", "--enabled", "true", "--json"],
&["previews", "cost", "prj_demo", "1234", "--json"],
&["api", "get", "/projects", "--json"],
&["version-info", "--json"],
];
for run in 1..=2 {
for args in runs {
let mut cmd = Command::new(&bin);
if *args != &["version-info", "--json"] {
cmd.args(["--token", "svc_test", "--api-url", &base]);
}
let output = cmd.args(*args).output().await.expect("spawn");
assert!(
output.status.success(),
"run {run} {:?} failed: {}",
args,
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(!stdout.trim().is_empty(), "run {run} {:?} empty stdout", args);
match args[0] {
"health" => assert!(stdout.contains("ok"), "run {run}: {stdout}"),
"whoami" => assert!(stdout.contains("user_1"), "run {run}: {stdout}"),
"orgs" => assert!(stdout.contains("acme"), "run {run}: {stdout}"),
"projects" if args.get(1) == Some(&"list") => {
assert!(stdout.contains("prj_demo"), "run {run}: {stdout}")
}
"projects" if args.get(1) == Some(&"get") => {
assert!(stdout.contains("Demo"), "run {run}: {stdout}")
}
"projects" if args.get(1) == Some(&"create") => {
assert!(stdout.contains("prj_new") || stdout.contains("NewApp"), "run {run}: {stdout}")
}
"environments" if args.get(1) == Some(&"list") => {
assert!(stdout.contains("production"), "run {run}: {stdout}")
}
"environments" if args.get(1) == Some(&"get") => {
assert!(
stdout.contains("env_prod") || stdout.contains("production"),
"run {run}: {stdout}"
)
}
"deployments" => {
assert!(stdout.contains("dep_1"), "run {run}: {stdout}");
assert!(stdout.contains("ready"), "run {run}: {stdout}");
}
"deploy" => {
assert!(stdout.contains("dep_new") || stdout.contains("building"), "run {run}: {stdout}")
}
"rollback" => {
assert!(
stdout.contains("rolling_back") || stdout.contains("dep_rollback"),
"run {run}: {stdout}"
)
}
"status" => {
assert!(stdout.contains("running"), "run {run}: {stdout}");
assert!(stdout.contains("dep_1"), "run {run}: {stdout}");
}
"env" if args.get(1) == Some(&"list") => {
assert!(stdout.contains("NODE_ENV") || stdout.contains("FOO") || stdout.contains("key"), "run {run}: {stdout}")
}
"env" if args.get(1) == Some(&"set") => {
assert!(stdout.contains("FOO") || stdout.contains("created"), "run {run}: {stdout}")
}
"env" if args.get(1) == Some(&"rm") => {
assert!(stdout.contains("deleted") || stdout.contains("FOO"), "run {run}: {stdout}")
}
"logs" => assert!(stdout.contains("log line"), "run {run}: {stdout}"),
"resources" | "db" => {
assert!(
stdout.contains("res_")
|| stdout.contains("database")
|| stdout.contains("ready")
|| stdout.contains("provisioning")
|| stdout.contains("deleted")
|| stdout.contains("bound"),
"run {run}: {stdout}"
)
}
"storage" => {
assert!(
stdout.contains("storage.sylphx.com") || stdout.contains("sto_"),
"run {run}: {stdout}"
)
}
"tasks" => {
assert!(
stdout.contains("task_") || stdout.contains("send-email") || stdout.contains("queued"),
"run {run}: {stdout}"
)
}
"backup" => {
assert!(
stdout.contains("bak_") || stdout.contains("restoring") || stdout.contains("ready"),
"run {run}: {stdout}"
)
}
"flags" => {
assert!(
stdout.contains("checkout-v2") || stdout.contains("enabled"),
"run {run}: {stdout}"
)
}
"previews" => {
assert!(stdout.contains("1.23") || stdout.contains("USD"), "run {run}: {stdout}")
}
"api" => {
assert!(stdout.contains("\"status\""), "run {run}: {stdout}");
assert!(stdout.contains("demo"), "run {run}: {stdout}");
}
"version-info" => {
assert!(stdout.contains("sylphx-sdk-management"), "run {run}: {stdout}");
assert!(stdout.contains("shippedCommands"), "run {run}: {stdout}");
assert!(stdout.contains("deploy"), "run {run}: {stdout}");
assert!(stdout.contains("env list"), "run {run}: {stdout}");
assert!(stdout.contains("logs"), "run {run}: {stdout}");
assert!(
stdout.contains("\"product\": \"rust\"")
|| stdout.contains("\"product\":\"rust\""),
"run {run}: {stdout}"
);
}
_ => {}
}
eprintln!("OK run={run} args={args:?} fields_ok");
}
}
eprintln!("AGENT_PRODUCT_SURFACE_OK");
mock.shutdown().await;
}
#[test]
fn cli_rejects_missing_token() {
let bin = cargo_bin!("sylphx");
let assert = assert_cmd::Command::new(bin)
.env_remove("SYLPHX_TOKEN")
.args(["health"])
.assert();
assert
.failure()
.stderr(predicate::str::contains("SYLPHX_TOKEN").or(predicate::str::contains("token")));
}
#[test]
fn cli_help_lists_agent_operator_commands() {
let bin = cargo_bin!("sylphx");
let assert = assert_cmd::Command::new(bin).arg("--help").assert();
assert
.success()
.stdout(predicate::str::contains("deploy"))
.stdout(predicate::str::contains("rollback"))
.stdout(predicate::str::contains("env"))
.stdout(predicate::str::contains("logs"))
.stdout(predicate::str::contains("resources"))
.stdout(predicate::str::contains("db"))
.stdout(predicate::str::contains("storage"))
.stdout(predicate::str::contains("tasks"))
.stdout(predicate::str::contains("backup"))
.stdout(predicate::str::contains("flags"))
.stdout(predicate::str::contains("previews"));
}