//! `pic logs ` — emptiness, status labels, `--limit` //! clamping, error path for unknown ids, and the 120-char truncate //! applied to the summary column. use predicates::prelude::*; use crate::common; /// Pick out the data rows from `pic logs` TSV output — the header line /// (`created_at\tstatus\tsummary`) is now always present, so the old /// "no non-empty lines means no logs" check needs to skip it. fn data_rows(stdout: &str) -> Vec<&str> { stdout .lines() .filter(|l| !l.trim().is_empty()) .filter(|l| !l.starts_with("created_at")) .collect() } #[ignore = "needs DATABASE_URL pointing at a running Postgres"] #[test] fn logs_for_fresh_script_is_empty() { let Some(fx) = common::fixture_or_skip() else { return; }; let env = common::admin_env(fx); let (id, _guard) = common::deploy_fixture(&env, "logs-empty", "hello.rhai"); let out = common::pic_as(&env) .args(["logs", &id]) .output() .expect("logs"); assert!(out.status.success(), "logs failed: {out:?}"); let stdout = String::from_utf8(out.stdout).unwrap(); assert!( data_rows(&stdout).is_empty(), "expected no log rows (header is allowed), got: {stdout}" ); } #[ignore = "needs DATABASE_URL pointing at a running Postgres"] #[test] fn logs_after_invoke_records_success_row() { let Some(fx) = common::fixture_or_skip() else { return; }; let env = common::admin_env(fx); let (id, _guard) = common::deploy_fixture(&env, "logs-ok", "hello.rhai"); common::pic_as(&env) .args(["scripts", "invoke", &id]) .assert() .success(); let out = common::pic_as(&env) .args(["logs", &id]) .output() .expect("logs"); assert!(out.status.success(), "logs failed: {out:?}"); let stdout = String::from_utf8(out.stdout).unwrap(); let rows = data_rows(&stdout); assert_eq!(rows.len(), 1, "expected 1 data row, got: {stdout}"); let cols: Vec<&str> = rows[0].split('\t').map(str::trim).collect(); assert_eq!( cols.len(), 3, "row should be 3 tab-delimited cells: {rows:?}" ); assert_eq!(cols[1], "success"); } #[ignore = "needs DATABASE_URL pointing at a running Postgres"] #[test] fn logs_records_error_for_throwing_script() { let Some(fx) = common::fixture_or_skip() else { return; }; let env = common::admin_env(fx); let (id, _guard) = common::deploy_fixture(&env, "logs-err", "throw.rhai"); // The invoke is expected to fail — we only care that the execution // gets recorded with `Error` status. let _ = common::pic_as(&env) .args(["scripts", "invoke", &id]) .output(); let out = common::pic_as(&env) .args(["logs", &id]) .output() .expect("logs"); assert!(out.status.success(), "logs failed: {out:?}"); let stdout = String::from_utf8(out.stdout).unwrap(); let row = data_rows(&stdout) .into_iter() .next() .expect("at least one data row"); let cols: Vec<&str> = row.split('\t').map(str::trim).collect(); assert_eq!(cols[1], "error", "expected error status, got row: {row}"); } #[ignore = "needs DATABASE_URL pointing at a running Postgres"] #[test] fn logs_respects_limit_flag() { let Some(fx) = common::fixture_or_skip() else { return; }; let env = common::admin_env(fx); let (id, _guard) = common::deploy_fixture(&env, "logs-limit", "hello.rhai"); for _ in 0..3 { common::pic_as(&env) .args(["scripts", "invoke", &id]) .assert() .success(); } let out = common::pic_as(&env) .args(["logs", &id, "--limit", "1"]) .output() .expect("logs"); assert!(out.status.success(), "logs failed: {out:?}"); let stdout = String::from_utf8(out.stdout).unwrap(); let rows = data_rows(&stdout).len(); assert_eq!(rows, 1, "expected --limit 1, got rows: {stdout}"); } #[ignore = "needs DATABASE_URL pointing at a running Postgres"] #[test] fn logs_for_unknown_id_errors() { let Some(fx) = common::fixture_or_skip() else { return; }; let env = common::admin_env(fx); let bogus = "00000000-0000-0000-0000-000000000000"; common::pic_as(&env) .args(["logs", bogus]) .assert() .failure() // 404 specifically — same `NotFound(ScriptId)` path the get/edit // endpoints use. .stderr(predicate::str::contains("HTTP 404")); } #[ignore = "needs DATABASE_URL pointing at a running Postgres"] #[test] fn logs_truncates_long_summary() { let Some(fx) = common::fixture_or_skip() else { return; }; let env = common::admin_env(fx); let (id, _guard) = common::deploy_fixture(&env, "logs-loud", "loud.rhai"); common::pic_as(&env) .args(["scripts", "invoke", &id]) .assert() .success(); let out = common::pic_as(&env) .args(["logs", &id]) .output() .expect("logs"); assert!(out.status.success(), "logs failed: {out:?}"); let stdout = String::from_utf8(out.stdout).unwrap(); let row = data_rows(&stdout) .into_iter() .next() .expect("at least one data row"); let summary = row.split('\t').nth(2).expect("summary column"); assert!( summary.ends_with('…'), "summary should be truncated with `…`, got: {summary}" ); let chars = summary.chars().count(); assert!( chars <= 121, "summary should be ≤120 chars + the truncation marker, got {chars}: {summary}" ); }