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>
267 lines
8.2 KiB
Rust
267 lines
8.2 KiB
Rust
//! `pic scripts deploy` / `pic scripts ls` edge cases beyond the
|
||
//! smoke test: unknown app, name override, version bumping, missing
|
||
//! file, and the no-`--app` walk across every accessible app.
|
||
|
||
use predicates::prelude::*;
|
||
|
||
use crate::common;
|
||
use crate::common::cleanup::AppGuard;
|
||
|
||
/// Extract a field value from a `pic` KvBlock (`key<pad>\tvalue` per line).
|
||
/// `pic scripts deploy` prints `name`/`version`/`action` rows, not a prose
|
||
/// "Created X vN" line, so version-bump tests assert on these fields.
|
||
fn kv_field<'a>(stdout: &'a str, key: &str) -> Option<&'a str> {
|
||
stdout.lines().find_map(|l| {
|
||
let (k, v) = l.split_once('\t')?;
|
||
(k.trim() == key).then(|| v.trim())
|
||
})
|
||
}
|
||
|
||
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
||
#[test]
|
||
fn deploy_against_unknown_app_errors() {
|
||
let Some(fx) = common::fixture_or_skip() else {
|
||
return;
|
||
};
|
||
let env = common::admin_env(fx);
|
||
let fixture = common::fixture_path("hello.rhai");
|
||
let bogus_slug = common::unique_slug("nope");
|
||
|
||
common::pic_as(&env)
|
||
.args([
|
||
"scripts",
|
||
"deploy",
|
||
fixture.to_str().unwrap(),
|
||
"--app",
|
||
&bogus_slug,
|
||
])
|
||
.assert()
|
||
.failure()
|
||
// Specifically 404 — `apps_get` short-circuits before the deploy
|
||
// request even starts. Loose `"HTTP 4"` would have matched a
|
||
// regressed 401 from broken auth.
|
||
.stderr(predicate::str::contains("HTTP 404"));
|
||
}
|
||
|
||
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
||
#[test]
|
||
fn deploy_with_name_override() {
|
||
let Some(fx) = common::fixture_or_skip() else {
|
||
return;
|
||
};
|
||
let env = common::admin_env(fx);
|
||
let slug = common::unique_slug("scripts-named");
|
||
common::pic_as(&env)
|
||
.args(["apps", "create", &slug])
|
||
.assert()
|
||
.success();
|
||
let _guard = AppGuard::new(&env.url, &env.token, &slug);
|
||
|
||
let fixture = common::fixture_path("hello.rhai");
|
||
let out = common::pic_as(&env)
|
||
.args([
|
||
"scripts",
|
||
"deploy",
|
||
fixture.to_str().unwrap(),
|
||
"--app",
|
||
&slug,
|
||
"--name",
|
||
"custom-name",
|
||
])
|
||
.output()
|
||
.expect("deploy v1");
|
||
let v1 = String::from_utf8(out.stdout).unwrap();
|
||
assert_eq!(kv_field(&v1, "name"), Some("custom-name"), "v1 name: {v1}");
|
||
assert_eq!(kv_field(&v1, "version"), Some("1"), "v1 version: {v1}");
|
||
assert_eq!(kv_field(&v1, "action"), Some("created"), "v1 action: {v1}");
|
||
|
||
let out = common::pic_as(&env)
|
||
.args([
|
||
"scripts",
|
||
"deploy",
|
||
fixture.to_str().unwrap(),
|
||
"--app",
|
||
&slug,
|
||
"--name",
|
||
"custom-name",
|
||
])
|
||
.output()
|
||
.expect("deploy v2");
|
||
let v2 = String::from_utf8(out.stdout).unwrap();
|
||
assert_eq!(kv_field(&v2, "version"), Some("2"), "v2 version: {v2}");
|
||
assert_eq!(kv_field(&v2, "action"), Some("updated"), "v2 action: {v2}");
|
||
|
||
let out = common::pic_as(&env)
|
||
.args(["scripts", "ls", "--app", &slug])
|
||
.output()
|
||
.expect("scripts ls");
|
||
assert!(out.status.success());
|
||
let stdout = String::from_utf8(out.stdout).unwrap();
|
||
assert!(
|
||
stdout
|
||
.lines()
|
||
.map(common::cells)
|
||
.any(|c| c.get(2).copied() == Some("custom-name") && c.get(3).copied() == Some("2")),
|
||
"expected custom-name v2 row, got: {stdout}",
|
||
);
|
||
}
|
||
|
||
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
||
#[test]
|
||
fn deploy_bumps_version_each_redeploy() {
|
||
let Some(fx) = common::fixture_or_skip() else {
|
||
return;
|
||
};
|
||
let env = common::admin_env(fx);
|
||
let slug = common::unique_slug("scripts-bump");
|
||
common::pic_as(&env)
|
||
.args(["apps", "create", &slug])
|
||
.assert()
|
||
.success();
|
||
let _guard = AppGuard::new(&env.url, &env.token, &slug);
|
||
|
||
let fixture = common::fixture_path("hello.rhai");
|
||
for (version, action) in [("1", "created"), ("2", "updated"), ("3", "updated")] {
|
||
let out = common::pic_as(&env)
|
||
.args([
|
||
"scripts",
|
||
"deploy",
|
||
fixture.to_str().unwrap(),
|
||
"--app",
|
||
&slug,
|
||
])
|
||
.output()
|
||
.expect("deploy");
|
||
assert!(out.status.success(), "deploy v{version} failed: {out:?}");
|
||
let stdout = String::from_utf8(out.stdout).unwrap();
|
||
assert_eq!(
|
||
kv_field(&stdout, "version"),
|
||
Some(version),
|
||
"version: {stdout}"
|
||
);
|
||
assert_eq!(
|
||
kv_field(&stdout, "action"),
|
||
Some(action),
|
||
"action: {stdout}"
|
||
);
|
||
}
|
||
}
|
||
|
||
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
||
#[test]
|
||
fn deploy_missing_file_errors() {
|
||
let Some(fx) = common::fixture_or_skip() else {
|
||
return;
|
||
};
|
||
let env = common::admin_env(fx);
|
||
let slug = common::unique_slug("scripts-missing");
|
||
common::pic_as(&env)
|
||
.args(["apps", "create", &slug])
|
||
.assert()
|
||
.success();
|
||
let _guard = AppGuard::new(&env.url, &env.token, &slug);
|
||
|
||
let missing = std::env::temp_dir().join(common::unique_slug("ghost") + ".rhai");
|
||
common::pic_as(&env)
|
||
.args([
|
||
"scripts",
|
||
"deploy",
|
||
missing.to_str().unwrap(),
|
||
"--app",
|
||
&slug,
|
||
])
|
||
.assert()
|
||
.failure()
|
||
.stderr(predicate::str::contains("reading"));
|
||
}
|
||
|
||
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
||
#[test]
|
||
fn ls_without_app_walks_every_accessible_app() {
|
||
let Some(fx) = common::fixture_or_skip() else {
|
||
return;
|
||
};
|
||
let env = common::admin_env(fx);
|
||
let slug_a = common::unique_slug("scripts-walk-a");
|
||
let slug_b = common::unique_slug("scripts-walk-b");
|
||
|
||
common::pic_as(&env)
|
||
.args(["apps", "create", &slug_a])
|
||
.assert()
|
||
.success();
|
||
let _guard_a = AppGuard::new(&env.url, &env.token, &slug_a);
|
||
common::pic_as(&env)
|
||
.args(["apps", "create", &slug_b])
|
||
.assert()
|
||
.success();
|
||
let _guard_b = AppGuard::new(&env.url, &env.token, &slug_b);
|
||
|
||
let fixture = common::fixture_path("hello.rhai");
|
||
for slug in [&slug_a, &slug_b] {
|
||
common::pic_as(&env)
|
||
.args([
|
||
"scripts",
|
||
"deploy",
|
||
fixture.to_str().unwrap(),
|
||
"--app",
|
||
slug,
|
||
])
|
||
.assert()
|
||
.success();
|
||
}
|
||
|
||
// `pic scripts ls` (no `--app`) issues a single `GET /admin/scripts`
|
||
// against the server now — there's nothing per-app to race against
|
||
// a concurrent AppGuard drop. The previous implementation walked
|
||
// `apps_list` followed by per-app `scripts_list_by_app` calls and
|
||
// aborted on the first 404, which forced this test to retry 5× to
|
||
// paper over the bug. Both the walk and the retry are gone.
|
||
let out = common::pic_as(&env)
|
||
.args(["scripts", "ls"])
|
||
.output()
|
||
.expect("scripts ls");
|
||
assert!(out.status.success(), "scripts ls failed: {out:?}");
|
||
let stdout = String::from_utf8(out.stdout).unwrap();
|
||
|
||
let slugs: std::collections::HashSet<&str> = stdout
|
||
.lines()
|
||
.map(common::cells)
|
||
.filter_map(|c| c.get(1).copied())
|
||
.collect();
|
||
assert!(
|
||
slugs.contains(slug_a.as_str()),
|
||
"missing app A in: {stdout}"
|
||
);
|
||
assert!(
|
||
slugs.contains(slug_b.as_str()),
|
||
"missing app B in: {stdout}"
|
||
);
|
||
}
|
||
|
||
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
||
#[test]
|
||
fn delete_removes_script_from_ls() {
|
||
let Some(fx) = common::fixture_or_skip() else {
|
||
return;
|
||
};
|
||
let env = common::admin_env(fx);
|
||
let (id, _guard) = common::deploy_fixture(&env, "scripts-del", "hello.rhai");
|
||
|
||
common::pic_as(&env)
|
||
.args(["scripts", "delete", &id])
|
||
.assert()
|
||
.success()
|
||
.stdout(predicate::str::contains(format!("Deleted script {id}")));
|
||
|
||
let out = common::pic_as(&env)
|
||
.args(["scripts", "ls"])
|
||
.output()
|
||
.expect("scripts ls");
|
||
assert!(out.status.success());
|
||
let stdout = String::from_utf8(out.stdout).unwrap();
|
||
assert!(
|
||
!stdout.contains(&id),
|
||
"deleted script id should not appear in ls: {stdout}"
|
||
);
|
||
}
|