aa_core/storage/
conformance.rs1use super::{
13 AgentId, AuditEntry, AuditSink, CredentialStore, LifecycleStore, PolicyStore, RateLimitCounter, SessionRecord,
14 SessionStore, StorageError,
15};
16
17pub async fn assert_policy_store_conformance(store: &dyn PolicyStore, present: &AgentId, absent: &AgentId) {
29 store
30 .get_policy(present)
31 .await
32 .expect("get_policy(present) should resolve to a policy");
33
34 match store.get_policy(absent).await {
35 Err(StorageError::NotFound(_)) => {}
36 other => panic!("get_policy(absent) should return NotFound, got {other:?}"),
37 }
38
39 store
40 .invalidate(present)
41 .await
42 .expect("invalidate(present) should succeed");
43
44 store
45 .invalidate(absent)
46 .await
47 .expect("invalidate(absent) should be idempotent");
48}
49
50pub async fn assert_audit_sink_conformance(sink: &dyn AuditSink, event: AuditEntry) {
56 sink.emit(event).await.expect("emit should persist the entry");
57}
58
59pub async fn assert_session_store_conformance(store: &dyn SessionStore, record: SessionRecord) {
70 let id = record.session_id;
71
72 store.save(record.clone()).await.expect("save(new) should succeed");
73
74 let loaded = store.load(&id).await.expect("load(present) should return the record");
75 assert_eq!(loaded, record, "loaded record should equal the saved record");
76
77 store.delete(&id).await.expect("delete(present) should succeed");
78
79 match store.load(&id).await {
80 Err(StorageError::NotFound(_)) => {}
81 other => panic!("load(deleted) should return NotFound, got {other:?}"),
82 }
83
84 store.delete(&id).await.expect("delete(absent) should be idempotent");
85}
86
87pub async fn assert_credential_store_conformance(store: &dyn CredentialStore, key: &str, value: Vec<u8>) {
98 store
99 .put_secret(key, value.clone())
100 .await
101 .expect("put_secret(new) should succeed");
102
103 let got = store
104 .get_secret(key)
105 .await
106 .expect("get_secret(present) should return the value");
107 assert_eq!(got, value, "round-tripped secret bytes should match");
108
109 store
110 .delete_secret(key)
111 .await
112 .expect("delete_secret(present) should succeed");
113
114 match store.get_secret(key).await {
115 Err(StorageError::NotFound(_)) => {}
116 other => panic!("get_secret(deleted) should return NotFound, got {other:?}"),
117 }
118
119 store
120 .delete_secret(key)
121 .await
122 .expect("delete_secret(absent) should be idempotent");
123}
124
125pub async fn assert_rate_limit_counter_conformance(counter: &dyn RateLimitCounter, key: &str) {
138 const WINDOW_SECS: u64 = 3600;
139
140 assert_eq!(
141 counter.current(key).await.expect("current(fresh) should succeed"),
142 0,
143 "a key that was never incremented reads 0"
144 );
145
146 assert_eq!(
147 counter
148 .increment(key, 5, WINDOW_SECS)
149 .await
150 .expect("increment should succeed"),
151 5,
152 "first increment returns the amount added"
153 );
154
155 assert_eq!(
156 counter
157 .increment(key, 3, WINDOW_SECS)
158 .await
159 .expect("increment should succeed"),
160 8,
161 "second increment accumulates within the window"
162 );
163
164 assert_eq!(
165 counter.current(key).await.expect("current should succeed"),
166 8,
167 "current reflects the accumulated total"
168 );
169
170 counter.reset(key).await.expect("reset should succeed");
171
172 assert_eq!(
173 counter.current(key).await.expect("current after reset should succeed"),
174 0,
175 "reset returns the counter to 0"
176 );
177
178 counter.reset(key).await.expect("reset(absent) should be idempotent");
179}
180
181pub async fn assert_lifecycle_store_conformance(store: &dyn LifecycleStore, present: &AgentId, absent: &AgentId) {
194 store.register(present).await.expect("register should succeed");
195
196 store
197 .heartbeat(present)
198 .await
199 .expect("heartbeat(registered) should succeed");
200
201 match store.heartbeat(absent).await {
202 Err(StorageError::NotFound(_)) => {}
203 other => panic!("heartbeat(unregistered) should return NotFound, got {other:?}"),
204 }
205
206 store
207 .deregister(present)
208 .await
209 .expect("deregister(registered) should succeed");
210
211 match store.heartbeat(present).await {
212 Err(StorageError::NotFound(_)) => {}
213 other => panic!("heartbeat(deregistered) should return NotFound, got {other:?}"),
214 }
215
216 store
217 .deregister(present)
218 .await
219 .expect("deregister(present) should be idempotent");
220
221 store
222 .deregister(absent)
223 .await
224 .expect("deregister(absent) should be idempotent");
225}