git-ents.gitmain
⌘K
foforge
members.rs101 lines · 3.8 KB · rusthistorycomment on this file
1//! Integration coverage for `git ents members` against a real local
2//! composition root (`roots.local`) — the bootstrap enrollment, then the
3//! full add/revoke/unrevoke/check lifecycle atop it.
4//!
5//! rstest table-driven: the spec enumerates member-state transitions
6//! (`model.member-revocation`) as a small closed set of cases, exactly the
7//! shape the engineering conventions call out for table tests rather than
8//! property tests.
9#![allow(
10 clippy::expect_used,
11 clippy::indexing_slicing,
12 reason = "integration test"
13)]
14
15mod common;
16
17use ents_model::MemberState;
18use git_ents::commands::members;
19use git_ents::root::LocalRoot;
20use rstest::rstest;
21
22/// The bootstrap window (`gate.bootstrap`) admits the very first member
23/// with no prior enrollment — [`git_ents::lib`]'s own doctest exercises
24/// this too; this test additionally confirms `git ents members list` then
25/// reads it back through the real composition root.
26// @relation(roots.local, model.member-identity, scope=function, role=Verifies)
27#[test]
28fn bootstrap_enrolls_the_first_member() {
29 let fixture = common::Fixture::new(1);
30 let root = LocalRoot::open(fixture.path()).expect("opens");
31
32 members::add(&root, "jdc", None, Some(fixture.key_path.clone())).expect("bootstrap admits it");
33
34 let listed = members::list(&root.refs, &root.objects).expect("lists");
35 assert_eq!(listed.len(), 1);
36 assert_eq!(listed[0].0, "jdc");
37 assert_eq!(listed[0].1.state, MemberState::Active);
38}
39
40/// Enroll an admin, then use that same key to add a second member — the
41/// ordinary (non-bootstrap) admin-registered path.
42// @relation(roots.local, model.member-identity, scope=function, role=Verifies)
43#[test]
44fn admin_enrolls_a_second_member() {
45 let fixture = common::Fixture::new(2);
46 let root = LocalRoot::open(fixture.path()).expect("opens");
47 members::add(&root, "admin", None, Some(fixture.key_path.clone())).expect("bootstrap");
48
49 members::add(
50 &root,
51 "bob",
52 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBogus bob".to_owned()),
53 Some(fixture.key_path.clone()),
54 )
55 .expect("admin-registered add");
56
57 let listed = members::list(&root.refs, &root.objects).expect("lists");
58 assert_eq!(listed.len(), 2);
59 assert!(listed.iter().any(|(name, _)| name == "bob"));
60}
61
62#[rstest]
63#[case::revoke_then_check(true)]
64#[case::unrevoke_then_check(false)]
65// @relation(model.member-revocation, scope=function, role=Verifies)
66fn revoke_and_unrevoke_round_trip(#[case] end_revoked: bool) {
67 let fixture = common::Fixture::new(3);
68 let root = LocalRoot::open(fixture.path()).expect("opens");
69 members::add(&root, "jdc", None, Some(fixture.key_path.clone())).expect("bootstrap");
70
71 members::set_revoked(&root, "jdc", true, Some(fixture.key_path.clone())).expect("revoke");
72 if !end_revoked {
73 members::set_revoked(&root, "jdc", false, Some(fixture.key_path.clone()))
74 .expect("unrevoke");
75 }
76
77 let (_, state) = members::check(&root, Some(fixture.key_path.clone()))
78 .expect("reads")
79 .expect("still a member record (revocation records state, never deletes)");
80 let expected = if end_revoked {
81 MemberState::Revoked
82 } else {
83 MemberState::Active
84 };
85 assert_eq!(state, expected);
86}
87
88/// `git ents members remove` deletes the ref entirely, distinct from
89/// revocation (`model.member-revocation`'s "never deletes" contrast).
90// @relation(model.member-revocation, scope=function, role=Verifies)
91#[test]
92fn remove_deletes_the_member_ref_entirely() {
93 let fixture = common::Fixture::new(4);
94 let root = LocalRoot::open(fixture.path()).expect("opens");
95 members::add(&root, "jdc", None, Some(fixture.key_path.clone())).expect("bootstrap");
96
97 members::remove(&root, "jdc", Some(fixture.key_path.clone())).expect("removes");
98
99 let listed = members::list(&root.refs, &root.objects).expect("lists");
100 assert!(listed.is_empty());
101}