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::error::HeddleError;
94 use crate::object::{TreeEntry, TreePageLimits, TreeStreamError};
95
96 let blob = ContentHash::compute(b"compliance-stream");
97 let tree = Tree::from_entries(vec![
98 TreeEntry::file("a.txt", blob, false).expect("a"),
99 TreeEntry::file("b.txt", blob, true).expect("b"),
100 ]);
101 let hash = store.put_tree(&tree).expect("put_tree failed");
102 let mut reader = store
103 .open_tree(&hash, None)
104 .expect("open_tree")
105 .expect("tree body");
106 let page = reader
107 .next_page(TreePageLimits::new(1, usize::MAX).expect("limits"))
108 .expect("page")
109 .expect("first page");
110 assert_eq!(page.entries.len(), 1);
111 assert_eq!(page.entries[0].name(), "a.txt");
112 let rest = reader
113 .next_page(TreePageLimits::new(8, usize::MAX).expect("limits"))
114 .expect("page")
115 .expect("rest");
116 assert_eq!(rest.entries[0].name(), "b.txt");
117 reader.finish_and_verify().expect("verify");
118
119 match store.open_tree(&hash, Some(&page.resume_cursor)) {
120 Ok(Some(mut resumed)) => {
121 let rest = resumed
122 .next_page(TreePageLimits::new(8, usize::MAX).expect("limits"))
123 .expect("page")
124 .expect("rest");
125 assert_eq!(rest.entries[0].name(), "b.txt");
126 resumed.finish_and_verify().expect("verify");
127 }
128 Err(HeddleError::TreeStream(TreeStreamError::UnverifiedRange)) => {}
129 other => panic!("resume must succeed on verified placement or fail closed: {other:?}"),
130 }
131}
132
133fn tree_missing_returns_none<S: ObjectStore>(store: &S) {
134 let hash = ContentHash::compute(b"compliance-nonexistent-tree");
135 let result = store
136 .get_tree(&hash)
137 .expect("get_tree error on missing key");
138 assert!(
139 result.is_none(),
140 "get_tree should return None for unknown hash"
141 );
142}
143
144fn state_round_trip<S: ObjectStore>(store: &S) {
147 let tree = Tree::new();
148 let tree_hash = store
149 .put_tree(&tree)
150 .expect("put_tree in state test failed");
151 let state = State::new(tree_hash, vec![], attribution());
152 let id = state.id();
153
154 store.put_state(&state).expect("put_state failed");
155
156 let got = store
157 .get_state(&id)
158 .expect("get_state failed")
159 .expect("state missing after put");
160
161 assert_eq!(got.id(), id, "state id changed after round-trip");
162 assert_eq!(got.tree, tree_hash, "tree hash changed after round-trip");
163}
164
165fn state_has<S: ObjectStore>(store: &S) {
166 let tree = Tree::new();
167 let tree_hash = store
168 .put_tree(&tree)
169 .expect("put_tree in state test failed");
170 let state = State::new(tree_hash, vec![], attribution());
171 let id = state.id();
172 store.put_state(&state).expect("put_state failed");
173 assert!(
174 store.has_state(&id).expect("has_state failed"),
175 "has_state returned false immediately after put"
176 );
177}
178
179fn state_list<S: ObjectStore>(store: &S) {
180 let tree = Tree::new();
181 let tree_hash = store
182 .put_tree(&tree)
183 .expect("put_tree in state test failed");
184 let state = State::new(tree_hash, vec![], attribution());
185 let id = state.id();
186 store.put_state(&state).expect("put_state failed");
187 let ids = store.list_states().expect("list_states failed");
188 assert!(
189 ids.contains(&id),
190 "list_states does not contain id after put"
191 );
192}