Remediation after an independent review of the two feature commits. pic init: - Fix the headline `pic init --dir <new-dir>` flow: slug derivation used `canonicalize()`, which fails on a not-yet-created directory → empty slug → bail. Derive from the path's own final component first, falling back to canonicalize only for `.`. Add a test that exercised the gap (the existing tests all passed an explicit slug). - `ensure_gitignored` no longer overwrites an existing-but-unreadable `.gitignore` (only a genuinely-absent file is treated as empty). Bound-plan staleness: - Token trigger coverage now mirrors the diff exactly via `current_trigger_identity`: dead-letter triggers (which the diff ignores and the manifest can't represent) and the currently-inert `enabled` flag are no longer hashed, so an out-of-band dead-letter change can't force a spurious `--force`. Add a test. - Correct two overclaiming comments: the check shares the diff's pool-read apply-vs-interactive-write window (it is not tx-scoped), and the token deliberately mirrors the diff's inputs. - `.picloud/` self-ignores via a `*` `.gitignore` written alongside the plan token, so projects created with `pic pull`/`plan` (not just `init`) keep it out of git. - `clear_plan` is now app-scoped: it won't discard a token recorded for a different app sharing the directory. Tested: manager-core lib 364 + cli bins 31 + 14 project-tool journeys green; clippy -D warnings clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
100 lines
2.8 KiB
Rust
100 lines
2.8 KiB
Rust
//! `pic init` journey — offline scaffolding, no server/DB needed (so these
|
|
//! tests are NOT gated on `DATABASE_URL` and never touch `common::fixture`).
|
|
|
|
use std::fs;
|
|
|
|
use assert_cmd::Command;
|
|
use tempfile::TempDir;
|
|
|
|
fn pic() -> Command {
|
|
Command::cargo_bin("pic").expect("pic binary")
|
|
}
|
|
|
|
#[test]
|
|
fn init_scaffolds_a_deployable_project() {
|
|
let dir = TempDir::new().unwrap();
|
|
pic()
|
|
.current_dir(dir.path())
|
|
.args(["init", "demo-app"])
|
|
.assert()
|
|
.success();
|
|
|
|
let toml = fs::read_to_string(dir.path().join("picloud.toml")).unwrap();
|
|
assert!(toml.contains("slug = \"demo-app\""), "got:\n{toml}");
|
|
assert!(
|
|
toml.contains("name = \"Demo App\""),
|
|
"name defaults to title-cased slug:\n{toml}"
|
|
);
|
|
assert!(
|
|
dir.path().join("scripts/hello.rhai").exists(),
|
|
"scaffold writes the example script"
|
|
);
|
|
let gitignore = fs::read_to_string(dir.path().join(".gitignore")).unwrap();
|
|
assert!(
|
|
gitignore.lines().any(|l| l.trim() == ".picloud/"),
|
|
"init must gitignore .picloud/:\n{gitignore}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn init_derives_slug_from_a_not_yet_created_dir() {
|
|
// The natural `pic init --dir new-project` flow: the target doesn't exist
|
|
// yet, and the slug is derived from its name (not via canonicalize, which
|
|
// would fail on a missing path).
|
|
let parent = TempDir::new().unwrap();
|
|
let target = parent.path().join("new-project");
|
|
pic()
|
|
.args(["init", "--dir"])
|
|
.arg(&target)
|
|
.assert()
|
|
.success();
|
|
let toml = fs::read_to_string(target.join("picloud.toml")).unwrap();
|
|
assert!(
|
|
toml.contains("slug = \"new-project\""),
|
|
"slug should derive from the dir name:\n{toml}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn init_refuses_to_overwrite_without_force() {
|
|
let dir = TempDir::new().unwrap();
|
|
pic()
|
|
.current_dir(dir.path())
|
|
.args(["init", "demo-app"])
|
|
.assert()
|
|
.success();
|
|
// A second run must refuse rather than clobber edits.
|
|
pic()
|
|
.current_dir(dir.path())
|
|
.args(["init", "demo-app"])
|
|
.assert()
|
|
.failure();
|
|
// `--force` overrides.
|
|
pic()
|
|
.current_dir(dir.path())
|
|
.args(["init", "demo-app", "--force"])
|
|
.assert()
|
|
.success();
|
|
}
|
|
|
|
#[test]
|
|
fn init_appends_to_an_existing_gitignore_once() {
|
|
let dir = TempDir::new().unwrap();
|
|
fs::write(dir.path().join(".gitignore"), "target/\n").unwrap();
|
|
pic()
|
|
.current_dir(dir.path())
|
|
.args(["init", "demo-app"])
|
|
.assert()
|
|
.success();
|
|
let gitignore = fs::read_to_string(dir.path().join(".gitignore")).unwrap();
|
|
assert!(
|
|
gitignore.contains("target/"),
|
|
"must preserve existing rules"
|
|
);
|
|
assert_eq!(
|
|
gitignore.matches(".picloud/").count(),
|
|
1,
|
|
"must add the ignore exactly once:\n{gitignore}"
|
|
);
|
|
}
|