//! `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\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}" ); }