Offline project scaffolding — the on-ramp to pull/plan/apply. Writes a `picloud.toml` describing a minimal *working* app (a `hello` endpoint bound to GET /hello) plus commented examples for the other blocks, `scripts/hello.rhai`, and a `.gitignore` that pre-ignores the project tool's `.picloud/` link-state directory (the home for the bound-plan state token, landing next). Refuses to overwrite an existing manifest without `--force`; derives the app slug from the directory name when not given (validated against the canonical slug rule). The active manifest is rendered through the same `to_toml` model the rest of the tool uses, so a freshly-init'd project is guaranteed valid and immediately `pic plan`-able. Offline — no server contact, so the journey tests need no DATABASE_URL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78 lines
2.2 KiB
Rust
78 lines
2.2 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_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}"
|
|
);
|
|
}
|