use std::fs;
mod common;
use common::Environment;
use common::Error;
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
enum Change<'a> {
AddUser(&'a str),
RetireUser(&'a str),
}
fn assert_changes<'a>(json: &[u8], expected: &[Change]) {
let json = String::from_utf8_lossy(json);
let output: serde_json::Value = serde_json::from_str(&json)
.expect(&format!("valid json: got {} bytes:\n{}",
json.len(), json));
let changes = &output["changes"];
let changes = if let serde_json::Value::Array(changes) = changes {
changes
} else {
panic!("Expected an array of changes, got: {:?}",
changes);
};
let mut got = Vec::new();
for change in changes {
let change = if let serde_json::Value::Object(change) = change {
change
} else {
panic!("Expected a map, got: {:?}", change);
};
if let Some(user) = change.get("AddUser") {
if let serde_json::Value::String(user) = user {
got.push(Change::AddUser(user));
} else {
panic!("Unknown change: {:?}", change);
}
} else if let Some(user) = change.get("RetireUser") {
if let serde_json::Value::String(user) = user {
got.push(Change::RetireUser(user));
} else {
panic!("Unknown change: {:?}", change);
}
} else if let Some(_) = change.get("AddRight") {
} else if let Some(_) = change.get("RemoveRight") {
} else if let Some(_) = change.get("AddCert") {
} else if let Some(_) = change.get("RemoveCert") {
} else {
panic!("Unknown change: {:?}", change);
}
}
got.sort();
let mut expected = expected.to_vec();
expected.sort();
eprintln!("Expected changes: {:?}", expected);
eprintln!(" Got changes: {:?}", got);
assert_eq!(expected, got);
}
#[test]
fn policy_diff() -> anyhow::Result<()> {
let e = Environment::new()?;
let p = e.git_state();
let (alice, alice_pgp) = e.gen("alice", None, None);
let alice_fpr = &alice.fingerprint().to_string();
let (bob, bob_pgp) = e.gen("bob", None, None);
let bob_fpr = &bob.fingerprint().to_string();
let (_carol, carol_pgp) = e.gen("carol", None, None);
let (_dave, dave_pgp) = e.gen("dave", None, None);
fs::write(p.join("0"), "0.")?;
e.git(&["add", "0"])?;
e.git(&[
"commit",
"-m", "Initial commit. No policy yet"
])?;
let c0 = e.git_current_commit()?;
e.sq_git(&[
"policy",
"authorize",
"alice",
"--cert-file", &alice_pgp,
"--project-maintainer"
])?;
e.git(&["add", "openpgp-policy.toml"])?;
e.git(&[
"commit",
"-m", "Add Alice to the policy file.",
&format!("-S{}", alice_fpr),
])?;
let root = e.git_current_commit()?;
e.git(&["log"])?;
e.sq_git(&["log", "--trust-root", &root])?;
fs::write(p.join("2"), "2.")?;
e.git(&["add", "2"])?;
e.git(&[
"commit",
"-m", "Alice adds a commit.",
&format!("-S{}", alice_fpr),
])?;
let c2 = e.git_current_commit()?;
e.git(&["log"])?;
e.sq_git(&["log", "--trust-root", &root])?;
e.sq_git(&[
"policy",
"authorize",
"bob",
"--cert-file", &bob_pgp,
"--committer"
])?;
e.git(&["add", "openpgp-policy.toml"])?;
e.git(&[
"commit",
"-m", "Alice authorizes Bob.",
&format!("-S{}", alice_fpr),
])?;
let c3 = e.git_current_commit()?;
e.git(&["log"])?;
e.sq_git(&["log", "--trust-root", &root])?;
fs::write(p.join("4"), "4.")?;
e.git(&["add", "4"])?;
e.git(&[
"commit",
"-m", "Bob adds a commit.",
&format!("-S{}", bob_fpr),
])?;
let c4 = e.git_current_commit()?;
e.git(&["log"])?;
e.sq_git(&["log", "--trust-root", &root])?;
let check = |args: &[&str], expected: &[Change]| {
let mut a = vec!["policy", "diff"];
a.extend(args);
let output = match e.sq_git(&a) {
Ok(output) => {
assert!(expected.is_empty());
output
}
Err(err) => {
match err {
Error::CliError(_, output) => {
output
}
err => {
panic!("Unexpected failure: {}", err);
}
}
}
};
assert_changes(&output.stdout, expected)
};
check(&[], &[]);
check(&[&root, &root], &[]);
check(&[&c0, &c0], &[]);
check(&[&c0, &root],
&[ Change::AddUser("alice") ]);
check(&[&root, &c0],
&[ Change::RetireUser("alice") ]);
check(&[&root, &c2], &[]);
check(&[&c2, &root], &[]);
check(&[&c2, &c3],
&[ Change::AddUser("bob") ]);
check(&[&c3, &c2],
&[ Change::RetireUser("bob") ]);
check(&[&c3, &c4], &[]);
check(&[&c4, &c3], &[]);
check(&["--old-commit", &c0, "--new-commit", &c4],
&[Change::AddUser("alice"), Change::AddUser("bob")]);
check(&["--new-commit", &c4, "--old-commit", &c0],
&[Change::AddUser("alice"), Change::AddUser("bob")]);
check(&[&c0, &c4], &[Change::AddUser("alice"), Change::AddUser("bob")]);
check(&[&c4, &c0], &[Change::RetireUser("alice"), Change::RetireUser("bob")]);
check(&[&c0, "HEAD"], &[Change::AddUser("alice"), Change::AddUser("bob")]);
check(&["HEAD", &c0], &[Change::RetireUser("alice"), Change::RetireUser("bob")]);
let policy_toml = p.join("openpgp-policy.toml");
std::fs::remove_file(&policy_toml).expect("can remove");
let policy_toml = &policy_toml.display().to_string()[..];
e.sq_git(&[
"policy",
"authorize",
"carol",
"--cert-file", &carol_pgp,
"--project-maintainer" ])?;
check(&[&c4, policy_toml],
&[
Change::RetireUser("alice"),
Change::RetireUser("bob"),
Change::AddUser("carol")
]);
check(&[&c4],
&[
Change::RetireUser("alice"),
Change::RetireUser("bob"),
Change::AddUser("carol")
]);
check(&[],
&[
Change::RetireUser("alice"),
Change::RetireUser("bob"),
Change::AddUser("carol")
]);
check(&[policy_toml, policy_toml], &[]);
check(&[policy_toml], &[]);
check(&["--old-commit", &c4, "--new-file", policy_toml],
&[
Change::RetireUser("alice"),
Change::RetireUser("bob"),
Change::AddUser("carol")
]);
check(&["--new-file", policy_toml, "--old-commit", &c4],
&[
Change::RetireUser("alice"),
Change::RetireUser("bob"),
Change::AddUser("carol")
]);
let carol_policy_toml = p.join("carol-policy.toml");
std::fs::rename(&policy_toml, &carol_policy_toml).expect("can rename");
let carol_policy_toml = &carol_policy_toml.display().to_string()[..];
e.sq_git(&[
"policy",
"authorize",
"dave",
"--cert-file", &dave_pgp,
"--project-maintainer" ])?;
check(&["--old-file", policy_toml, "--new-file", carol_policy_toml],
&[
Change::RetireUser("dave"),
Change::AddUser("carol")
]);
check(&["--new-file", policy_toml, "--old-file", carol_policy_toml],
&[
Change::AddUser("dave"),
Change::RetireUser("carol")
]);
Ok(())
}
#[test]
fn non_existant() -> anyhow::Result<()> {
let e = Environment::new()?;
let p = e.git_state();
let non_existent = p.join("non_existent");
assert!(! non_existent.exists());
let non_existent = &non_existent.display().to_string();
assert!(e.sq_git(&[ non_existent, non_existent ]).is_err());
assert!(e.sq_git(&[
"--old-commit", non_existent, "--new-commit", non_existent
]).is_err());
assert!(e.sq_git(&[
"--old-file", non_existent, "--new-file", non_existent
]).is_err());
Ok(())
}