Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_o3db_sync/tests/scan.rs

6.9 KiB, 9 runs

created by r1870400018:10314, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Ozone scan integration test.
2//!
3//! Populates a fresh database with a mix of key prefixes, then
4//! exercises [`Database::scan`] through several option shapes
5//! (all, prefix, limit, overwrite) and verifies the expected
6//! entries come back.
7//!
8//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
9//! Anthropic Claude
10
11use oxedyne_fe2o3_core::{
12 prelude::*,
13 alt::Override,
14};
15use oxedyne_fe2o3_crypto::enc::EncryptionScheme;
16use oxedyne_fe2o3_hash::{
17 csum::ChecksumScheme,
18 hash::HashScheme,
19};
20use oxedyne_fe2o3_iop_db::api::{
21 Database,
22 RestSchemesOverride,
23 ScanOpts,
24};
25use oxedyne_fe2o3_jdat::prelude::*;
26use oxedyne_fe2o3_o3db_sync::{
27 data::core::RestSchemesInput,
28 test::setup,
29};
30
31use std::{
32 collections::HashSet,
33 path::Path,
34 thread,
35 time::Duration,
36};
37
38pub fn test_scan(_filter: &'static str) -> Outcome<()> {
39
40 let db_root = res!(Path::new("./test_db_scan").canonicalize().or_else(|_| {
41 ok!(std::fs::create_dir_all("./test_db_scan"));
42 Path::new("./test_db_scan").canonicalize()
43 }));
44
45 // Fixed key so the test is deterministic.
46 let enckey = [0x42u8; 32];
47 let aes_gcm = res!(EncryptionScheme::new_aes_256_gcm_with_key(&enckey[..]));
48 let crc32 = ChecksumScheme::new_crc32();
49 let schms2: RestSchemesOverride<EncryptionScheme, HashScheme> =
50 RestSchemesOverride::default()
51 .set_encrypter(Override::Default(aes_gcm.clone()));
52 let schms2 = Some(&schms2);
53 let user = setup::Uid::default();
54
55 let schms_input = RestSchemesInput::new(
56 Some(aes_gcm.clone()),
57 None::<HashScheme>,
58 None::<HashScheme>,
59 Some(crc32.clone()),
60 );
61
62 let mut cfg = res!(setup::default_cfg());
63 cfg.num_zones = 3;
64 cfg.num_cbots_per_zone = 2;
65 cfg.num_igbots_per_zone = 2;
66 cfg.data_file_max_bytes = 200_000;
67 // Keep every zone inside the per-test db root so this test
68 // cannot inherit or contaminate state from sibling tests
69 // (basic.rs uses "../test_db_zone_container" for zone 1).
70 cfg.zone_overrides = mapdat!{
71 1u16 => mapdat!{ "dir" => "", "max_size" => 10_000_000u64 },
72 2u16 => mapdat!{ "dir" => "", "max_size" => 10_000_000u64 },
73 3u16 => mapdat!{ "dir" => "", "max_size" => 10_000_000u64 },
74 }.get_map().unwrap();
75
76 test!(sync_log::stream(), "+---------------------------------------------+");
77 test!(sync_log::stream(), "| SCAN TEST |");
78 test!(sync_log::stream(), "+---------------------------------------------+");
79
80 let mut db = res!(setup::start_db(
81 db_root.clone(),
82 Some(cfg.clone()),
83 schms_input.clone(),
84 None,
85 true, // gc on
86 true, // wipe
87 ));
88
89 thread::sleep(Duration::from_secs(1));
90
91 // 1. Populate with a mix of prefixes.
92 // - 10 under "user:"
93 // - 5 under "sess:"
94 // - 3 under "cfg:"
95 test!(sync_log::stream(), "Populating 18 keys across three prefixes.");
96 for i in 0..10 {
97 res!(db.insert(
98 dat!(fmt!("user:{:03}", i)),
99 dat!(fmt!("profile_{}", i)),
100 user,
101 schms2,
102 ));
103 }
104 for i in 0..5 {
105 res!(db.insert(
106 dat!(fmt!("sess:{:03}", i)),
107 dat!(fmt!("token_{}", i)),
108 user,
109 schms2,
110 ));
111 }
112 for i in 0..3 {
113 res!(db.insert(
114 dat!(fmt!("cfg:{:03}", i)),
115 dat!(fmt!("setting_{}", i)),
116 user,
117 schms2,
118 ));
119 }
120
121 thread::sleep(Duration::from_millis(500));
122
123 // 2. Scan everything. 18 entries expected.
124 test!(sync_log::stream(), "Scan all: expecting 18 entries.");
125 let all = res!(db.scan(&ScanOpts::all(), schms2));
126 if all.len() != 18 {
127 return Err(err!(
128 "Expected 18 entries from scan-all, got {}.", all.len();
129 Test, Mismatch));
130 }
131 let all_keys: HashSet<String> = all.iter()
132 .filter_map(|(k, _, _)| match k {
133 Dat::Str(s) => Some(s.clone()),
134 _ => None,
135 })
136 .collect();
137 if all_keys.len() != 18 {
138 return Err(err!(
139 "Expected 18 distinct Str keys, got {}.", all_keys.len();
140 Test, Mismatch));
141 }
142
143 // 3. Prefix scan "user:". 10 expected.
144 test!(sync_log::stream(), "Prefix scan 'user:' expecting 10 entries.");
145 let user_only = res!(db.scan(
146 &ScanOpts::with_str_prefix("user:"),
147 schms2,
148 ));
149 if user_only.len() != 10 {
150 return Err(err!(
151 "Expected 10 'user:' entries, got {}.", user_only.len();
152 Test, Mismatch));
153 }
154 for (k, _, _) in &user_only {
155 match k {
156 Dat::Str(s) => {
157 if !s.starts_with("user:") {
158 return Err(err!(
159 "Prefix scan returned non-matching key {:?}.", k;
160 Test, Mismatch));
161 }
162 },
163 other => return Err(err!(
164 "Expected Dat::Str key, got {:?}.", other;
165 Test, Mismatch)),
166 }
167 }
168
169 // 4. Prefix scan "sess:". 5 expected.
170 test!(sync_log::stream(), "Prefix scan 'sess:' expecting 5 entries.");
171 let sess_only = res!(db.scan(
172 &ScanOpts::with_str_prefix("sess:"),
173 schms2,
174 ));
175 if sess_only.len() != 5 {
176 return Err(err!(
177 "Expected 5 'sess:' entries, got {}.", sess_only.len();
178 Test, Mismatch));
179 }
180
181 // 5. Prefix scan "nope:". 0 expected.
182 test!(sync_log::stream(), "Prefix scan 'nope:' expecting 0 entries.");
183 let nope_only = res!(db.scan(
184 &ScanOpts::with_str_prefix("nope:"),
185 schms2,
186 ));
187 if nope_only.len() != 0 {
188 return Err(err!(
189 "Expected 0 'nope:' entries, got {}.", nope_only.len();
190 Test, Mismatch));
191 }
192
193 // 6. Limit. 5 expected from a scan-all capped at 5.
194 test!(sync_log::stream(), "Scan all with limit=5 expecting 5 entries.");
195 let limited = res!(db.scan(
196 &ScanOpts::all().limit(5),
197 schms2,
198 ));
199 if limited.len() != 5 {
200 return Err(err!(
201 "Expected 5 entries under limit=5, got {}.", limited.len();
202 Test, Mismatch));
203 }
204
205 // 7. Overwrite. Re-insert "user:000" and expect scan still to
206 // return 18 entries total -- one per distinct key -- not 19.
207 test!(sync_log::stream(), "Overwrite 'user:000' and rescan; still expecting 18 entries.");
208 res!(db.insert(
209 dat!("user:000"),
210 dat!("profile_0_v2"),
211 user,
212 schms2,
213 ));
214 thread::sleep(Duration::from_millis(500));
215 let after_overwrite = res!(db.scan(&ScanOpts::all(), schms2));
216 if after_overwrite.len() != 18 {
217 return Err(err!(
218 "Expected 18 entries after overwrite, got {}.", after_overwrite.len();
219 Test, Mismatch));
220 }
221
222 test!(sync_log::stream(), "Scan test passed.");
223 thread::sleep(Duration::from_secs(1));
224 res!(db.shutdown());
225 Ok(())
226}