Six journeys asserted against output formats that changed in earlier commits and had been failing regardless of branch: * `pic scripts deploy` prints a `name`/`version`/`action` KvBlock, not a prose "Created X vN" line. The name-override, version-bump, and the editor-can-deploy role check now parse the KvBlock fields (asserting the exact version + created/updated action) via a small `kv_field` helper. * `pic logs` gained a `source` column (now `created_at, source, status, summary`); the success/error-status and summary-truncation checks now index the status at col 2 and the summary at col 3. No production code touched — the deploys/logs always succeeded; only the assertions were stale. Full `--test cli --include-ignored` suite is now green (101 passed, 0 failed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
180 lines
5.6 KiB
Rust
180 lines
5.6 KiB
Rust
//! `pic logs <script-id>` — 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\tsource\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(),
|
|
4,
|
|
"row should be 4 tab-delimited cells (created_at, source, status, summary): {rows:?}"
|
|
);
|
|
assert_eq!(cols[2], "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[2], "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(3).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}"
|
|
);
|
|
}
|