objects/store/
store_compliance.rs1use crate::{
9 object::{Attribution, Blob, ContentHash, Principal, State, Tree},
10 store::ObjectStore,
11};
12
13fn attribution() -> Attribution {
14 Attribution::human(Principal::new("Compliance Test", "test@example.com"))
15}
16
17pub fn run_compliance_tests<S: ObjectStore>(store: &S) {
22 blob_round_trip(store);
23 blob_missing_returns_none(store);
24 blob_has(store);
25 blob_list(store);
26 tree_round_trip(store);
27 tree_missing_returns_none(store);
28 tree_streaming_round_trip(store);
29 state_round_trip(store);
30 state_has(store);
31 state_list(store);
32}
33
34fn blob_round_trip<S: ObjectStore>(store: &S) {
37 let blob = Blob::from("compliance: blob round-trip");
38 let hash = store.put_blob(&blob).expect("put_blob failed");
39 let got = store
40 .get_blob(&hash)
41 .expect("get_blob failed")
42 .expect("blob missing after put");
43 assert_eq!(
44 got.content(),
45 blob.content(),
46 "blob content changed after round-trip"
47 );
48}
49
50fn blob_missing_returns_none<S: ObjectStore>(store: &S) {
51 let hash = ContentHash::compute(b"compliance-nonexistent-blob");
52 let result = store
53 .get_blob(&hash)
54 .expect("get_blob error on missing key");
55 assert!(
56 result.is_none(),
57 "get_blob should return None for unknown hash"
58 );
59}
60
61fn blob_has<S: ObjectStore>(store: &S) {
62 let blob = Blob::from("compliance: has_blob");
63 let hash = store.put_blob(&blob).expect("put_blob failed");
64 assert!(
65 store.has_blob(&hash).expect("has_blob failed"),
66 "has_blob returned false immediately after put"
67 );
68}
69
70fn blob_list<S: ObjectStore>(store: &S) {
71 let blob = Blob::from("compliance: list_blobs");
72 let hash = store.put_blob(&blob).expect("put_blob failed");
73 let list = store.list_blobs().expect("list_blobs failed");
74 assert!(
75 list.contains(&hash),
76 "list_blobs does not contain hash after put"
77 );
78}
79
80fn tree_round_trip<S: ObjectStore>(store: &S) {
83 let tree = Tree::new();
84 let hash = store.put_tree(&tree).expect("put_tree failed");
85 let got = store
86 .get_tree(&hash)
87 .expect("get_tree failed")
88 .expect("tree missing after put");
89 assert_eq!(got.hash(), hash, "tree hash changed after round-trip");
90}
91
92fn tree_streaming_round_trip<S: ObjectStore>(store: &S) {
93 use crate::{
94 error::HeddleError,
95 object::{TreeEntry, TreePageLimits, TreeStreamError},
96 };
97
98 let blob = ContentHash::compute(b"compliance-stream");
99 let tree = Tree::from_entries(vec![
100 TreeEntry::file("a.txt", blob, false).expect("a"),
101 TreeEntry::file("b.txt", blob, true).expect("b"),
102 ]);
103 let hash = store.put_tree(&tree).expect("put_tree failed");
104 let mut reader = store
105 .open_tree(&hash, None)
106 .expect("open_tree")
107 .expect("tree body");
108 let page = reader
109 .next_page(TreePageLimits::new(1, usize::MAX).expect("limits"))
110 .expect("page")
111 .expect("first page");
112 assert_eq!(page.entries.len(), 1);
113 assert_eq!(page.entries[0].name(), "a.txt");
114 let rest = reader
115 .next_page(TreePageLimits::new(8, usize::MAX).expect("limits"))
116 .expect("page")
117 .expect("rest");
118 assert_eq!(rest.entries[0].name(), "b.txt");
119 reader.finish_and_verify().expect("verify");
120
121 match store.open_tree(&hash, Some(&page.resume_cursor)) {
122 Ok(Some(mut resumed)) => {
123 let rest = resumed
124 .next_page(TreePageLimits::new(8, usize::MAX).expect("limits"))
125 .expect("page")
126 .expect("rest");
127 assert_eq!(rest.entries[0].name(), "b.txt");
128 resumed.finish_and_verify().expect("verify");
129 }
130 Err(HeddleError::TreeStream(TreeStreamError::UnverifiedRange)) => {}
131 other => panic!("resume must succeed on verified placement or fail closed: {other:?}"),
132 }
133}
134
135fn tree_missing_returns_none<S: ObjectStore>(store: &S) {
136 let hash = ContentHash::compute(b"compliance-nonexistent-tree");
137 let result = store
138 .get_tree(&hash)
139 .expect("get_tree error on missing key");
140 assert!(
141 result.is_none(),
142 "get_tree should return None for unknown hash"
143 );
144}
145
146fn state_round_trip<S: ObjectStore>(store: &S) {
149 let tree = Tree::new();
150 let tree_hash = store
151 .put_tree(&tree)
152 .expect("put_tree in state test failed");
153 let state = State::new(tree_hash, vec![], attribution());
154 let id = state.id();
155
156 store.put_state(&state).expect("put_state failed");
157
158 let got = store
159 .get_state(&id)
160 .expect("get_state failed")
161 .expect("state missing after put");
162
163 assert_eq!(got.id(), id, "state id changed after round-trip");
164 assert_eq!(got.tree, tree_hash, "tree hash changed after round-trip");
165}
166
167fn state_has<S: ObjectStore>(store: &S) {
168 let tree = Tree::new();
169 let tree_hash = store
170 .put_tree(&tree)
171 .expect("put_tree in state test failed");
172 let state = State::new(tree_hash, vec![], attribution());
173 let id = state.id();
174 store.put_state(&state).expect("put_state failed");
175 assert!(
176 store.has_state(&id).expect("has_state failed"),
177 "has_state returned false immediately after put"
178 );
179}
180
181fn state_list<S: ObjectStore>(store: &S) {
182 let tree = Tree::new();
183 let tree_hash = store
184 .put_tree(&tree)
185 .expect("put_tree in state test failed");
186 let state = State::new(tree_hash, vec![], attribution());
187 let id = state.id();
188 store.put_state(&state).expect("put_state failed");
189 let ids = store.list_states().expect("list_states failed");
190 assert!(
191 ids.contains(&id),
192 "list_states does not contain id after put"
193 );
194}