Files
PiCloud/crates/picloud-cli/tests/scripts.rs
MechaCat02 79f8c9d420 test(cli): refresh stale deploy + logs journey assertions
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>
2026-06-24 22:37:36 +02:00

267 lines
8.2 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! `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}"
);
}