clone

one developer profile for links and private tcp services, reached peer to peer over iroh with no open ports

main
devsite / crates / devsite-cli / tests / json.rs
json.rs 6.0K
1use std::process::{Command, Output};2use std::sync::atomic::{AtomicUsize, Ordering};34static NEXT_HOME: AtomicUsize = AtomicUsize::new(0);56fn devsite(args: &[&str]) -> Output {7 let suffix = NEXT_HOME.fetch_add(1, Ordering::Relaxed);8 let home =9 std::env::temp_dir().join(format!("devsite-json-test-{}-{suffix}", std::process::id()));10 let output = Command::new(env!("CARGO_BIN_EXE_devsite"))11 .args(args)12 .env("DEVSITE_HOME", &home)13 .output()14 .unwrap();15 if home.exists() {16 std::fs::remove_dir_all(home).unwrap();17 }18 output19}2021fn json(output: &Output) -> serde_json::Value {22 serde_json::from_slice(&output.stdout).unwrap_or_else(|err| {23 panic!(24 "stdout was not one JSON value: {err}\n{}",25 String::from_utf8_lossy(&output.stdout)26 )27 })28}2930#[test]31fn global_json_flag_structures_success() {32 let output = devsite(&["--json", "status"]);33 assert!(output.status.success());34 let value = json(&output);35 assert_eq!(value["schema_version"], 1);36 assert_eq!(value["ok"], true);37 assert_eq!(value["command"], "status");38 assert_eq!(value["result"]["daemon"]["running"], false);39 assert!(value["result"]["services"].is_array());40 assert!(value["result"]["identity_path"]41 .as_str()42 .unwrap()43 .ends_with("devsite-endpoint.key"));44 assert!(value["result"]["identity"]["public_path"]45 .as_str()46 .unwrap()47 .ends_with("devsite-endpoint.pub"));48 assert!(output.stderr.is_empty());49}5051#[test]52fn global_json_flag_works_after_a_subcommand() {53 let output = devsite(&["daemon", "status", "--json"]);54 assert!(output.status.success());55 let value = json(&output);56 assert_eq!(value["command"], "daemon.status");57 assert_eq!(value["result"]["running"], false);58}5960#[test]61fn json_structures_runtime_errors() {62 let output = devsite(&["--json", "service", "host", "0"]);63 assert_eq!(output.status.code(), Some(1));64 let value = json(&output);65 assert_eq!(value["ok"], false);66 assert_eq!(value["error"]["kind"], "runtime");67 assert_eq!(value["error"]["message"], "port 0 cannot be hosted");68 assert!(output.stderr.is_empty());69}7071#[test]72fn json_structures_usage_errors() {73 let output = devsite(&["--json", "not-a-command"]);74 assert_eq!(output.status.code(), Some(2));75 let value = json(&output);76 assert_eq!(value["ok"], false);77 assert_eq!(value["error"]["kind"], "usage");78 assert!(value["error"]["suggestions"][0]79 .as_str()80 .unwrap()81 .contains("devsite --help"));82 assert!(output.stderr.is_empty());83}8485#[test]86fn json_login_never_prompts() {87 let output = devsite(&["--json", "login"]);88 assert_eq!(output.status.code(), Some(1));89 let value = json(&output);90 assert_eq!(value["error"]["message"], "TOKEN is required with --json");91 assert!(!String::from_utf8_lossy(&output.stdout).contains("token:"));92}9394#[test]95fn json_structures_command_help() {96 let output = devsite(&["link", "set", "--json", "--help"]);97 assert!(output.status.success());98 let value = json(&output);99 assert_eq!(value["command"], "help");100 assert_eq!(value["result"]["command"], "link.set");101 assert!(value["result"]["text"]102 .as_str()103 .unwrap()104 .contains("Create or replace a named link"));105}106107#[test]108fn human_runtime_errors_end_with_a_recovery_suggestion() {109 let output = devsite(&["service", "host", "0"]);110 assert_eq!(output.status.code(), Some(1));111 let stderr = String::from_utf8_lossy(&output.stderr);112 assert!(stderr.contains("error: port 0 cannot be hosted"));113 assert!(stderr.contains("suggestion: Run `devsite service host --help`"));114}115116#[test]117fn doctor_reports_state_and_actions_without_enrollment() {118 let output = devsite(&["doctor", "--json"]);119 assert!(output.status.success());120 let value = json(&output);121 assert_eq!(value["command"], "doctor");122 assert_eq!(value["result"]["healthy"], true);123 assert!(value["result"]["checks"].is_array());124 assert_eq!(value["result"]["actions"][0]["id"], "login");125}126127#[test]128fn resources_and_plan_flags_are_discoverable_in_structured_help() {129 let resources = devsite(&["resources", "list", "--json", "--help"]);130 assert!(resources.status.success());131 assert_eq!(json(&resources)["result"]["command"], "resources.list");132133 let plan = devsite(&["link", "set", "--json", "--help"]);134 assert!(plan.status.success());135 let text = json(&plan)["result"]["text"].as_str().unwrap().to_string();136 assert!(text.contains("--plan"));137 assert!(text.contains("--dry-run"));138}139140#[test]141fn access_request_writes_a_shareable_request_and_separate_private_key() {142 let suffix = NEXT_HOME.fetch_add(1, Ordering::Relaxed);143 let root = std::env::temp_dir().join(format!(144 "devsite-access-request-test-{}-{suffix}",145 std::process::id()146 ));147 std::fs::create_dir_all(&root).unwrap();148 let request = root.join("request.json");149 let key = root.join("requester.key");150 let output = Command::new(env!("CARGO_BIN_EXE_devsite"))151 .args([152 "--json",153 "access",154 "request",155 "postgres",156 "--request",157 request.to_str().unwrap(),158 "--key",159 key.to_str().unwrap(),160 ])161 .env("DEVSITE_HOME", root.join("home"))162 .output()163 .unwrap();164 assert!(165 output.status.success(),166 "{}",167 String::from_utf8_lossy(&output.stderr)168 );169 let value = json(&output);170 assert_eq!(value["command"], "access.request");171 assert_eq!(value["result"]["request"]["service"], "postgres");172 assert!(value["result"]["request"]["request_id"]173 .as_str()174 .unwrap()175 .starts_with("agr_"));176 assert_eq!(std::fs::read(&key).unwrap().len(), 32);177 let stored: serde_json::Value =178 serde_json::from_slice(&std::fs::read(&request).unwrap()).unwrap();179 assert_eq!(stored, value["result"]["request"]);180 std::fs::remove_dir_all(root).unwrap();181}