1#![allow(
23 clippy::expect_used,
24 clippy::unwrap_used,
25 clippy::panic,
26 clippy::items_after_statements,
27 clippy::no_effect_underscore_binding,
28 clippy::float_cmp
29)]
30
31#[cfg(test)]
32mod tests {
33 use crate::types::{Compression, Metadata, ModelType, SaveOptions};
34 use std::collections::HashMap;
35 use std::path::PathBuf;
36
37 use serde::{Deserialize, Serialize};
38
39 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42 struct GoldenModel {
43 name: String,
44 weights: Vec<f32>,
45 bias: f32,
46 }
47
48 fn golden_model() -> GoldenModel {
49 GoldenModel {
50 name: "golden_v1".to_string(),
51 weights: vec![1.0, 2.0, 0.5, -0.5, 4.0, -2.0, 0.25, 8.0],
52 bias: 0.125,
53 }
54 }
55
56 fn golden_options() -> SaveOptions {
60 let metadata = Metadata {
61 created_at: "1700000000".to_string(),
62 aprender_version: "0.0.0-golden".to_string(),
63 model_name: Some("golden-v1".to_string()),
64 description: None,
65 training: None,
66 hyperparameters: HashMap::new(),
67 metrics: HashMap::new(),
68 custom: HashMap::new(),
69 distillation: None,
70 distillation_info: None,
71 license: None,
72 model_card: None,
73 };
74 SaveOptions {
75 compression: Compression::None,
76 metadata,
77 quality_score: Some(85),
78 }
79 }
80
81 fn fixtures() -> PathBuf {
82 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
83 .join("tests")
84 .join("fixtures")
85 }
86
87 fn golden_v1_bytes() -> Vec<u8> {
88 std::fs::read(fixtures().join("golden_v1.apr")).expect("read golden_v1.apr fixture")
89 }
90
91 #[test]
95 fn test_falsify_aprf_byte_identity_golden_roundtrip() {
96 let dir = std::env::temp_dir();
97 let path = dir.join("aprf_byte_identity_probe.apr");
98 crate::save(
99 &golden_model(),
100 ModelType::LinearRegression,
101 &path,
102 golden_options(),
103 )
104 .expect("save golden model with pinned options");
105
106 let produced = std::fs::read(&path).expect("read produced bytes");
107 let _ = std::fs::remove_file(&path);
108
109 let golden = golden_v1_bytes();
110 assert_eq!(
111 produced.len(),
112 golden.len(),
113 "byte length drifted (extraction changed the on-disk encoding)"
114 );
115 assert_eq!(
116 produced, golden,
117 "extracted save() output is NOT byte-identical to golden_v1.apr — \
118 the serializer order, padding, header layout, or CRC drifted"
119 );
120
121 let back: GoldenModel = crate::load_from_bytes(&golden, ModelType::LinearRegression)
123 .expect("load golden bytes");
124 assert_eq!(back, golden_model());
125 }
126
127 #[test]
133 fn test_falsify_aprf_sovereign_deps_no_ml_gpu() {
134 use cargo_metadata::MetadataCommand;
135
136 const FORBIDDEN: &[&str] = &[
137 "trueno",
138 "aprender-compute",
139 "aprender-gpu",
140 "aprender-core",
141 "wgpu",
142 "naga",
143 "cudarc",
144 "cust",
145 "candle-core",
146 "candle-nn",
147 "tch",
148 "torch-sys",
149 ];
150
151 let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml");
152 let metadata = MetadataCommand::new()
153 .manifest_path(&manifest)
154 .features(cargo_metadata::CargoOpt::AllFeatures)
155 .exec()
156 .expect("cargo metadata for apr-format");
157
158 let resolve = metadata.resolve.expect("resolve graph present");
160 let id2name: HashMap<_, _> = metadata
161 .packages
162 .iter()
163 .map(|p| (p.id.clone(), p.name.clone()))
164 .collect();
165 let root = metadata
166 .packages
167 .iter()
168 .find(|p| p.name == "apr-format")
169 .map(|p| p.id.clone())
170 .expect("apr-format package present");
171 let nodes: HashMap<_, _> = resolve.nodes.iter().map(|n| (n.id.clone(), n)).collect();
172
173 let mut seen = std::collections::HashSet::new();
174 let mut stack = vec![root];
175 while let Some(id) = stack.pop() {
176 if !seen.insert(id.clone()) {
177 continue;
178 }
179 if let Some(node) = nodes.get(&id) {
180 for dep in &node.deps {
181 stack.push(dep.pkg.clone());
182 }
183 }
184 }
185
186 let names: std::collections::HashSet<&str> = seen
187 .iter()
188 .filter_map(|id| id2name.get(id).map(String::as_str))
189 .collect();
190
191 let leaked: Vec<&str> = FORBIDDEN
192 .iter()
193 .copied()
194 .filter(|f| names.contains(f))
195 .collect();
196
197 assert!(
198 leaked.is_empty(),
199 "apr-format leaf is NO LONGER sovereign — forbidden ML/GPU/framework \
200 crate(s) leaked into its dependency closure: {leaked:?}"
201 );
202 }
203
204 #[test]
207 fn test_falsify_aprf_crc_integrity_matches_legacy() {
208 assert_eq!(crate::crc32(b"123456789"), 0xCBF4_3926);
210 assert_eq!(crate::crc32(&[]), 0x0000_0000);
211 assert_eq!(crate::crc32(&[0x00]), 0xD202_EF8D);
212
213 let bytes = golden_v1_bytes();
215 let stored = u32::from_le_bytes([
216 bytes[bytes.len() - 4],
217 bytes[bytes.len() - 3],
218 bytes[bytes.len() - 2],
219 bytes[bytes.len() - 1],
220 ]);
221 let computed = crate::crc32(&bytes[..bytes.len() - 4]);
222 assert_eq!(
223 stored, computed,
224 "leaf crc32 diverged from the legacy table/fold — existing .apr files \
225 would fail integrity"
226 );
227
228 let mut tampered = bytes.clone();
230 tampered[crate::HEADER_SIZE + 1] ^= 0xFF;
231 let recomputed = crate::crc32(&tampered[..tampered.len() - 4]);
232 assert_ne!(recomputed, stored, "crc32 failed to detect a flipped byte");
233 }
234
235 #[test]
238 fn test_falsify_aprf_metadata_fidelity_roundtrip() {
239 use crate::types::{Header, LicenseInfo, LicenseTier, TrainingInfo, HEADER_SIZE};
240
241 let mut hyper = HashMap::new();
242 hyper.insert("lr".to_string(), serde_json::json!(0.001));
243 let mut metrics = HashMap::new();
244 metrics.insert("acc".to_string(), serde_json::json!(0.97));
245 let mut custom = HashMap::new();
246 custom.insert("note".to_string(), serde_json::json!("hello"));
247
248 let metadata = Metadata {
249 created_at: "1234567890".to_string(),
250 aprender_version: "9.9.9-test".to_string(),
251 model_name: Some("fidelity".to_string()),
252 description: Some("round-trip every field".to_string()),
253 training: Some(TrainingInfo {
254 samples: Some(42),
255 duration_ms: Some(1000),
256 source: Some("unit-test".to_string()),
257 }),
258 hyperparameters: hyper,
259 metrics,
260 custom,
261 distillation: Some("teacher-hash".to_string()),
262 distillation_info: None,
263 license: Some(LicenseInfo {
264 uuid: "uuid-1".to_string(),
265 hash: "hash-1".to_string(),
266 expiry: None,
267 seats: Some(3),
268 licensee: Some("ACME".to_string()),
269 tier: LicenseTier::Enterprise,
270 }),
271 model_card: None,
272 };
273
274 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
275 struct M {
276 v: Vec<f32>,
277 }
278 let model = M { v: vec![1.0, 2.0] };
279
280 let dir = std::env::temp_dir();
281 let path = dir.join("aprf_metadata_fidelity.apr");
282 let options = SaveOptions {
283 compression: Compression::None,
284 metadata: metadata.clone(),
285 quality_score: None,
286 };
287 crate::save(&model, ModelType::LinearRegression, &path, options).expect("save");
288
289 let raw = std::fs::read(&path).expect("read");
291 let header = Header::from_bytes(&raw[..HEADER_SIZE]).expect("hdr");
292 assert!(header.flags.is_licensed(), "LICENSED flag not set");
293
294 let info = crate::inspect(&path).expect("inspect");
296 let _ = std::fs::remove_file(&path);
297 let m = info.metadata;
298 assert_eq!(m.created_at, metadata.created_at);
299 assert_eq!(m.aprender_version, metadata.aprender_version);
300 assert_eq!(m.model_name, metadata.model_name);
301 assert_eq!(m.description, metadata.description);
302 assert_eq!(m.distillation, metadata.distillation);
303 assert_eq!(m.hyperparameters, metadata.hyperparameters);
304 assert_eq!(m.metrics, metadata.metrics);
305 assert_eq!(m.custom, metadata.custom);
306 let (got, want) = (m.license.expect("lic"), metadata.license.expect("lic"));
307 assert_eq!(got.uuid, want.uuid);
308 assert_eq!(got.hash, want.hash);
309 assert_eq!(got.seats, want.seats);
310 assert_eq!(got.licensee, want.licensee);
311 assert_eq!(got.tier, want.tier);
312 let tr = m.training.expect("training");
313 assert_eq!(tr.samples, Some(42));
314 }
315
316 #[test]
322 fn test_falsify_aprf_api_compat_reexport_resolves() {
323 let crc: u32 = crate::crc32(b"abc");
326 assert_eq!(crc, crate::crc32(b"abc"));
327 let bits: u16 = crate::f32_to_f16(1.0);
328 assert_eq!(crate::f16_to_f32(bits), 1.0);
329
330 let hdr = crate::Header::new(ModelType::LinearRegression);
331 assert_eq!(hdr.magic, crate::MAGIC);
332 assert_eq!(crate::HEADER_SIZE, 32);
333
334 let _info_ty: Option<crate::ModelInfo> = None;
335 let _opts = crate::SaveOptions::default();
336 let v2 = crate::v2::AprV2Header::new();
337 assert_eq!(v2.magic, crate::v2::MAGIC_V2);
338 let card = crate::ModelCard::new("m", "1.0.0");
339 assert_eq!(card.version, "1.0.0");
340
341 let dir = std::env::temp_dir();
343 let path = dir.join("aprf_api_compat.apr");
344 crate::save(
345 &vec![1.0_f32, 2.0],
346 ModelType::LinearRegression,
347 &path,
348 crate::SaveOptions::default(),
349 )
350 .expect("save via re-export");
351 let back: Vec<f32> =
352 crate::load(&path, ModelType::LinearRegression).expect("load via re-export");
353 let _ = std::fs::remove_file(&path);
354 assert_eq!(back, vec![1.0, 2.0]);
355 }
356
357 #[test]
360 fn test_falsify_aprf_quality_gate_preserved() {
361 #[derive(Serialize)]
362 struct M {
363 v: Vec<f32>,
364 }
365 let model = M { v: vec![1.0] };
366 let dir = std::env::temp_dir();
367
368 let bad = SaveOptions {
370 quality_score: Some(0),
371 ..Default::default()
372 };
373 let refused = crate::save(
374 &model,
375 ModelType::LinearRegression,
376 dir.join("aprf_qgate_bad.apr"),
377 bad,
378 );
379 assert!(
380 matches!(refused, Err(crate::AprFormatError::ValidationError { .. })),
381 "Jidoka gate lost: save(Some(0)) was NOT refused"
382 );
383
384 let good = SaveOptions {
386 quality_score: Some(85),
387 ..Default::default()
388 };
389 let path = dir.join("aprf_qgate_good.apr");
390 let accepted = crate::save(&model, ModelType::LinearRegression, &path, good);
391 assert!(accepted.is_ok(), "known-good save(Some(85)) was refused");
392 let _ = std::fs::remove_file(&path);
393 }
394}