Phase 9 R1: RepoEnsure — peer RPC + aggregator fan-out + daemon wiring #106
@@ -241,6 +241,90 @@ mod tests {
|
||||
assert_eq!(p.parent().unwrap().parent().unwrap(), root.as_path());
|
||||
}
|
||||
|
||||
/// End-to-end: seed a bare git repo in a tempdir, ensure it into
|
||||
/// a fresh repo_root, verify cached=true on the second call, then
|
||||
/// release. Requires `git` on PATH; marked `#[ignore]` so CI
|
||||
/// without git-installed runners skips it silently.
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn ensure_then_cached_then_release() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let source = tmp.path().join("source.git");
|
||||
let repo_root = tmp.path().join("repos");
|
||||
|
||||
// Init a bare-ish source repo with one commit on branch `main`.
|
||||
let seed_dir = tmp.path().join("seed");
|
||||
std::fs::create_dir_all(&seed_dir).unwrap();
|
||||
let git = |args: &[&str], cwd: &std::path::Path| {
|
||||
let out = std::process::Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"git {:?} failed: {}",
|
||||
args,
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
};
|
||||
git(&["init", "-b", "main"], &seed_dir);
|
||||
git(&["config", "user.email", "t@t"], &seed_dir);
|
||||
git(&["config", "user.name", "t"], &seed_dir);
|
||||
std::fs::write(seed_dir.join("README"), "hi").unwrap();
|
||||
git(&["add", "."], &seed_dir);
|
||||
git(&["commit", "-m", "seed"], &seed_dir);
|
||||
git(
|
||||
&["clone", "--bare", seed_dir.to_str().unwrap(), source.to_str().unwrap()],
|
||||
tmp.path(),
|
||||
);
|
||||
|
||||
let req = RepoEnsureRequest {
|
||||
url: format!("file://{}", source.display()),
|
||||
git_ref: "main".into(),
|
||||
workspace: "workspace:test".into(),
|
||||
};
|
||||
|
||||
// First ensure → fresh clone.
|
||||
let r1 = ensure_repo(&repo_root, &req).await.unwrap();
|
||||
assert!(!r1.cached, "first ensure should not be cached");
|
||||
assert!(!r1.head_sha.is_empty());
|
||||
assert!(std::path::Path::new(&r1.path).join(".git").exists());
|
||||
|
||||
// Second ensure → cache hit.
|
||||
let r2 = ensure_repo(&repo_root, &req).await.unwrap();
|
||||
assert!(r2.cached, "second ensure should hit cache");
|
||||
assert_eq!(r1.head_sha, r2.head_sha);
|
||||
assert_eq!(r1.path, r2.path);
|
||||
|
||||
// Release removes it.
|
||||
let rel = release_repo(
|
||||
&repo_root,
|
||||
&RepoReleaseRequest {
|
||||
url: req.url.clone(),
|
||||
git_ref: req.git_ref.clone(),
|
||||
workspace: req.workspace.clone(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(rel.removed);
|
||||
assert!(!std::path::Path::new(&r1.path).exists());
|
||||
|
||||
// Idempotent release.
|
||||
let rel2 = release_repo(
|
||||
&repo_root,
|
||||
&RepoReleaseRequest {
|
||||
url: req.url,
|
||||
git_ref: req.git_ref,
|
||||
workspace: req.workspace,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!rel2.removed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sanitize_component_replaces_traversal() {
|
||||
assert_eq!(sanitize_component(".."), "_");
|
||||
|
||||
@@ -226,6 +226,15 @@ impl ClusterServices {
|
||||
r = r.with_tag_store(store.clone());
|
||||
}
|
||||
r = r.with_outbound_client(outbound_client);
|
||||
// Phase 9 R1a wiring: enable RepoEnsure/RepoRelease when
|
||||
// the daemon has a blob_store_root (which is the
|
||||
// canonical anchor for all fleet on-disk state). Repos
|
||||
// materialize under <blob_store_root>/repos/<workspace>/…
|
||||
if let Some(root) = blob_store_root.as_ref() {
|
||||
let repo_root = root.join("repos");
|
||||
let _ = std::fs::create_dir_all(&repo_root);
|
||||
r = r.with_repo_root(repo_root);
|
||||
}
|
||||
let router = Arc::new(r);
|
||||
|
||||
let server =
|
||||
|
||||
Reference in New Issue
Block a user