use super::marker_refusal::{MARKER, check_paths, repo};
use crate::cli::check;
use crate::test_support::{
git_init, git_unresolvable, request_count, server_returning, write_site_policy,
};
fn marked_repository() -> tempfile::TempDir {
let dir = tempfile::tempdir().expect("tempdir");
git_init(dir.path());
std::fs::write(dir.path().join(MARKER), "").expect("marker");
std::fs::write(dir.path().join("lib.py"), "x = 1\n").expect("lib.py");
dir
}
#[tokio::test]
async fn a_marked_repository_is_refused_when_the_run_is_rooted_outside_it() {
let (unmarked, server, site) = repo(&[MARKER]).await;
let marked = marked_repository();
let exit = check_paths(
unmarked.path(),
unmarked.path(),
&site,
vec![marked.path().join("lib.py")],
)
.await
.expect("a refusal is a verdict, not a crash");
assert_eq!(
exit,
check::Exit::Unanalyzed,
"the repository whose source was about to be sent is the one whose policy \
decides"
);
assert_eq!(
request_count(&server).await,
0,
"the marked repository's source left the machine"
);
}
#[tokio::test]
async fn a_marked_repository_nested_in_an_unmarked_one_is_refused() {
let (outer, server, site) = repo(&[MARKER]).await;
let inner = outer.path().join("vendored-service");
std::fs::create_dir(&inner).expect("nested checkout");
git_init(&inner);
std::fs::write(inner.join(MARKER), "").expect("marker");
std::fs::write(inner.join("lib.py"), "y = 2\n").expect("lib.py");
let exit = check_paths(outer.path(), outer.path(), &site, Vec::new())
.await
.expect("a refusal is a verdict, not a crash");
assert_eq!(exit, check::Exit::Unanalyzed);
assert_eq!(request_count(&server).await, 0);
}
#[cfg(unix)]
#[tokio::test]
async fn a_symlink_in_an_unmarked_repo_cannot_expose_a_marked_repos_source() {
let (unmarked, server, site) = repo(&[MARKER]).await;
let marked = marked_repository();
let alias = unmarked.path().join("alias.py");
std::os::unix::fs::symlink(marked.path().join("lib.py"), &alias).expect("source alias");
let exit = check_paths(unmarked.path(), unmarked.path(), &site, vec![alias])
.await
.expect("the marked target is a policy refusal");
assert_eq!(exit, check::Exit::Unanalyzed);
assert_eq!(
request_count(&server).await,
0,
"the target bytes left the machine after only the link repository was probed"
);
}
#[cfg(unix)]
#[tokio::test]
async fn an_unmarked_target_is_not_refused_by_a_marker_beside_its_symlink() {
let (link_repo, server, site) = repo(&[MARKER]).await;
std::fs::write(link_repo.path().join(MARKER), "").expect("link-repo marker");
let target_repo = tempfile::tempdir().expect("target repo");
git_init(target_repo.path());
std::fs::write(target_repo.path().join("lib.py"), "x = 1\n").expect("target source");
let alias = link_repo.path().join("alias.py");
std::os::unix::fs::symlink(target_repo.path().join("lib.py"), &alias).expect("source alias");
let exit = check_paths(link_repo.path(), link_repo.path(), &site, vec![alias])
.await
.expect("the target repository is unmarked");
assert_eq!(exit, check::Exit::Clean);
assert_eq!(request_count(&server).await, 1);
}
#[tokio::test]
async fn an_unmarked_repository_outside_the_root_still_reviews() {
let (unmarked, server, site) = repo(&[MARKER]).await;
let beside = tempfile::tempdir().expect("tempdir");
git_init(beside.path());
std::fs::write(beside.path().join("lib.py"), "x = 1\n").expect("lib.py");
let exit = check_paths(
unmarked.path(),
unmarked.path(),
&site,
vec![beside.path().join("lib.py")],
)
.await
.expect("neither repository is marked");
assert_eq!(exit, check::Exit::Clean);
assert_eq!(request_count(&server).await, 1);
}
#[tokio::test]
async fn a_policy_that_cannot_be_evaluated_stops_the_check_rather_than_reviewing() {
let dir = tempfile::tempdir().expect("tempdir");
let server = server_returning(&[r#"{"issues": []}"#]).await;
crate::test_support::write_drep_toml(dir.path(), &format!("{}/v1", server.uri()));
std::fs::write(dir.path().join("lib.py"), "x = 1\n").expect("lib.py");
let site = write_site_policy(dir.path(), &[MARKER]);
git_unresolvable(dir.path());
let err = check_paths(
dir.path(),
dir.path(),
&site,
vec![dir.path().join("lib.py")],
)
.await
.expect_err("a policy that could not be evaluated is not a policy that permits");
let message = format!("{err:#}");
assert!(
message.contains(&site.display().to_string()),
"the operator has to be told which policy could not be evaluated; got {message}"
);
assert_eq!(
request_count(&server).await,
0,
"and the source must not have been sent while it could not be; got {message}"
);
}
#[tokio::test]
async fn a_policy_naming_no_markers_reviews_outside_a_repository() {
let dir = tempfile::tempdir().expect("tempdir");
let server = server_returning(&[r#"{"issues": []}"#]).await;
crate::test_support::write_drep_toml(dir.path(), &format!("{}/v1", server.uri()));
std::fs::write(dir.path().join("lib.py"), "x = 1\n").expect("lib.py");
let site = dir.path().join("site.toml");
std::fs::write(&site, "max_concurrent_ceiling = 4\n").expect("site policy");
git_unresolvable(dir.path());
let exit = check_paths(
dir.path(),
dir.path(),
&site,
vec![dir.path().join("lib.py")],
)
.await
.expect("a policy naming no markers needs no repository");
assert_eq!(exit, check::Exit::Clean);
assert_eq!(request_count(&server).await, 1);
}
#[tokio::test]
async fn a_file_deep_inside_a_marked_repository_is_refused() {
let (unmarked, server, site) = repo(&[MARKER]).await;
let marked = marked_repository();
let deep = marked.path().join("service/handlers");
std::fs::create_dir_all(&deep).expect("subdirectories");
std::fs::write(deep.join("lib.py"), "z = 3\n").expect("lib.py");
let exit = check_paths(
unmarked.path(),
unmarked.path(),
&site,
vec![deep.join("lib.py")],
)
.await
.expect("a refusal is a verdict, not a crash");
assert_eq!(exit, check::Exit::Unanalyzed);
assert_eq!(request_count(&server).await, 0);
}
#[tokio::test]
async fn files_from_an_unmarked_repository_are_refused_alongside_the_marked_one() {
let (unmarked, server, site) = repo(&[MARKER]).await;
let marked = marked_repository();
let exit = check_paths(
unmarked.path(),
unmarked.path(),
&site,
vec![unmarked.path().join("lib.py"), marked.path().join("lib.py")],
)
.await
.expect("a refusal is a verdict, not a crash");
assert_eq!(
exit,
check::Exit::Unanalyzed,
"one run reviews one work set, and half of it is not allowed to be sent"
);
assert_eq!(request_count(&server).await, 0);
}
#[tokio::test]
async fn the_unresolvable_fixture_really_has_no_repository_root() {
let dir = tempfile::tempdir().expect("tempdir");
git_unresolvable(dir.path());
let resolved = crate::diff::repository_root(dir.path()).await;
assert!(
resolved.is_err(),
"the fixture has to deny git a root wherever the temporary directory \
lives; got {resolved:?}"
);
}
#[tokio::test]
async fn a_root_outside_a_repository_does_not_stop_a_run_whose_files_resolve() {
let outside = tempfile::tempdir().expect("tempdir");
git_unresolvable(outside.path());
let server = server_returning(&[r#"{"issues": []}"#]).await;
crate::test_support::write_drep_toml(outside.path(), &format!("{}/v1", server.uri()));
let site = write_site_policy(outside.path(), &[MARKER]);
let unmarked = tempfile::tempdir().expect("tempdir");
git_init(unmarked.path());
std::fs::write(unmarked.path().join("lib.py"), "x = 1\n").expect("lib.py");
let exit = check_paths(
outside.path(),
outside.path(),
&site,
vec![unmarked.path().join("lib.py")],
)
.await
.expect("the reviewed file's repository is the one the probe asks about");
assert_eq!(exit, check::Exit::Clean);
assert_eq!(
request_count(&server).await,
1,
"the file's own repository answered, so the review proceeds"
);
}