返回 CodeWhale
tests.rs
根目录 / crates / config / src / cloud_facts / tests.rs
1 use std::collections::BTreeMap;
2
3 use base64::Engine as _;
4 use base64::engine::general_purpose::STANDARD as BASE64;
5 use ring::signature::KeyPair as _;
6 use serde_json::{Value, json};
7
8 use super::catalog_patch::apply_model_patches;
9 use super::keys::{KeyStatus, TrustedKey};
10 use super::overlay;
11 use super::provenance::{CloudFactsState, CloudFactsStatus, FactsOrigin};
12 use super::scope::{base_url_allowed, scoped_view};
13 use super::types::ModelOp;
14 use super::verify::{FactsRejection, parse_rfc3339_utc, signing_message, verify_envelope};
15 use crate::catalog::{CatalogCompiler, CatalogOffering, CatalogSource};
16
17 /// Cross-language fixture signed by `web/scripts/facts-publish.mjs` with the
18 /// TEST-ONLY key (`docs/cloud-facts/fixtures/test-only-signing-key.pem`).
19 const FIXTURE_V7: &str =
20 include_str!("../../../../docs/cloud-facts/fixtures/envelope-stable-v7.json");
21 const FIXTURE_FUTURE_V8: &str =
22 include_str!("../../../../docs/cloud-facts/fixtures/envelope-future-only-v8.json");
23 const TEST_ONLY_PUB_B64: &str = "8+FLDW4OorUETUVks0hpQAi5Lj4wg3kjKjfYFzLbJ7U=";
24 const NOW: u64 = 1_790_000_000; // 2026-09-21T...Z
25
26 fn test_only_key(status: KeyStatus) -> TrustedKey {
27 let raw = BASE64.decode(TEST_ONLY_PUB_B64).expect("pub b64");
28 let mut public_key = [0u8; 32];
29 public_key.copy_from_slice(&raw);
30 TrustedKey {
31 key_id: "cwf-test-only",
32 public_key,
33 status,
34 }
35 }
36
37 fn v(s: &str) -> semver::Version {
38 semver::Version::parse(s).expect("semver")
39 }
40
41 /// Ephemeral signer for in-Rust variants (tamper, rotation, schema bumps).
42 struct Signer {
43 key_id: &'static str,
44 pair: ring::signature::Ed25519KeyPair,
45 }
46
47 impl Signer {
48 fn new(key_id: &'static str) -> Self {
49 let rng = ring::rand::SystemRandom::new();
50 let pkcs8 = ring::signature::Ed25519KeyPair::generate_pkcs8(&rng).expect("pkcs8");
51 let pair = ring::signature::Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).expect("pair");
52 Self { key_id, pair }
53 }
54
55 fn trusted(&self, status: KeyStatus) -> TrustedKey {
56 let mut public_key = [0u8; 32];
57 public_key.copy_from_slice(self.pair.public_key().as_ref());
58 TrustedKey {
59 key_id: self.key_id,
60 public_key,
61 status,
62 }
63 }
64
65 fn sign(&self, payload: &[u8]) -> String {
66 BASE64.encode(self.pair.sign(&signing_message(self.key_id, payload)))
67 }
68
69 fn envelope(&self, payload: &Value) -> Value {
70 let bytes = serde_json::to_vec(payload).expect("payload json");
71 use sha2::Digest as _;
72 let digest = sha2::Sha256::digest(&bytes)
73 .iter()
74 .map(|byte| format!("{byte:02x}"))
75 .collect::<String>();
76 json!({
77 "envelope": 1,
78 "channel": payload["channel"],
79 "facts_version": payload["facts_version"],
80 "schema_version": payload["schema_version"],
81 "key_id": self.key_id,
82 "alg": "ed25519",
83 "applies_to": payload["applies_to"],
84 "published_at": payload["published_at"],
85 "sha256": digest,
86 "payload_b64": BASE64.encode(&bytes),
87 "sig_b64": self.sign(&bytes),
88 "sigs": [],
89 })
90 }
91 }
92
93 fn payload(channel: &str, version: u64, applies_to: &str) -> Value {
94 json!({
95 "schema_version": 1,
96 "channel": channel,
97 "facts_version": version,
98 "published_at": "2026-08-30T00:00:00Z",
99 "applies_to": applies_to,
100 "models": [],
101 "provider_defaults": {},
102 "release": null,
103 "announcements": [],
104 })
105 }
106
107 fn verify(bytes: &[u8], keys: &[TrustedKey]) -> Result<super::VerifiedFacts, FactsRejection> {
108 verify_envelope(bytes, "stable", &v("0.9.11"), None, keys, NOW)
109 }
110
111 #[test]
112 fn node_signed_fixture_verifies_under_the_test_only_key() {
113 let keys = [test_only_key(KeyStatus::Active)];
114 let verified = verify(FIXTURE_V7.as_bytes(), &keys).expect("fixture verifies");
115 assert_eq!(verified.key_id, "cwf-test-only");
116 assert_eq!(verified.facts.facts_version, 7);
117 assert_eq!(verified.facts.channel, "stable");
118 assert_eq!(verified.facts.models.len(), 6);
119 assert!(!verified.stale);
120 // sha256 in the envelope matches the recomputed digest of the signed bytes.
121 let env: Value = serde_json::from_str(FIXTURE_V7).expect("json");
122 assert_eq!(env["sha256"].as_str(), Some(verified.sha256.as_str()));
123 }
124
125 #[test]
126 fn fixture_is_rejected_with_no_trusted_keys() {
127 let err = verify(FIXTURE_V7.as_bytes(), &[]).expect_err("no keys");
128 assert!(matches!(err, FactsRejection::UnknownKey { .. }), "{err:?}");
129 }
130
131 #[test]
132 fn retired_key_is_rejected_distinctly() {
133 let keys = [test_only_key(KeyStatus::Retired)];
134 let err = verify(FIXTURE_V7.as_bytes(), &keys).expect_err("retired");
135 assert_eq!(
136 err,
137 FactsRejection::RetiredKey {
138 key_id: "cwf-test-only".into()
139 }
140 );
141 }
142
143 #[test]
144 fn flipping_payload_signature_or_key_id_bytes_breaks_verification() {
145 let keys = [test_only_key(KeyStatus::Active)];
146 let mut env: Value = serde_json::from_str(FIXTURE_V7).expect("json");
147
148 // Payload tamper (decode → flip → encode keeps the envelope well-formed).
149 let mut bytes = BASE64.decode(env["payload_b64"].as_str().unwrap()).unwrap();
150 bytes[10] ^= 0x01;
151 let mut tampered = env.clone();
152 tampered["payload_b64"] = Value::String(BASE64.encode(&bytes));
153 let err = verify(&serde_json::to_vec(&tampered).unwrap(), &keys).unwrap_err();
154 assert_eq!(err, FactsRejection::BadSignature);
155
156 // Signature tamper.
157 let mut sig = BASE64.decode(env["sig_b64"].as_str().unwrap()).unwrap();
158 sig[3] ^= 0x80;
159 let mut tampered = env.clone();
160 tampered["sig_b64"] = Value::String(BASE64.encode(&sig));
161 let err = verify(&serde_json::to_vec(&tampered).unwrap(), &keys).unwrap_err();
162 assert_eq!(err, FactsRejection::BadSignature);
163
164 // Re-labelling under another pinned key id must fail: key_id is signed.
165 let other = TrustedKey {
166 key_id: "cwf-other",
167 ..test_only_key(KeyStatus::Active)
168 };
169 env["key_id"] = Value::String("cwf-other".into());
170 let err = verify(&serde_json::to_vec(&env).unwrap(), &[other]).unwrap_err();
171 assert_eq!(err, FactsRejection::BadSignature);
172 }
173
174 #[test]
175 fn envelope_shape_errors_come_before_crypto() {
176 let keys = [test_only_key(KeyStatus::Active)];
177 let base: Value = serde_json::from_str(FIXTURE_V7).expect("json");
178
179 let mut e = base.clone();
180 e["alg"] = json!("rsa");
181 assert!(matches!(
182 verify(&serde_json::to_vec(&e).unwrap(), &keys).unwrap_err(),
183 FactsRejection::BadEnvelope(_)
184 ));
185
186 let mut e = base.clone();
187 e["envelope"] = json!(2);
188 assert!(matches!(
189 verify(&serde_json::to_vec(&e).unwrap(), &keys).unwrap_err(),
190 FactsRejection::BadEnvelope(_)
191 ));
192
193 let big = vec![b' '; super::MAX_ENVELOPE_BYTES + 1];
194 assert!(matches!(
195 verify(&big, &keys).unwrap_err(),
196 FactsRejection::TooLarge { .. }
197 ));
198
199 // Oversized payload_b64 is refused before base64 decode / crypto.
200 let mut e = base.clone();
201 e["payload_b64"] = Value::String("A".repeat(super::MAX_PAYLOAD_BYTES * 4 / 3 + 64));
202 assert!(matches!(
203 verify(&serde_json::to_vec(&e).unwrap(), &keys).unwrap_err(),
204 FactsRejection::TooLarge { .. }
205 ));
206 }
207
208 #[test]
209 fn outer_inner_mismatch_wrong_channel_and_schema_too_new_are_rejected() {
210 let signer = Signer::new("cwf-unit");
211 let keys = [signer.trusted(KeyStatus::Active)];
212
213 let mut env = signer.envelope(&payload("stable", 3, "*"));
214 env["facts_version"] = json!(4);
215 let err = verify(&serde_json::to_vec(&env).unwrap(), &keys).unwrap_err();
216 assert!(matches!(err, FactsRejection::Mismatch(_)), "{err:?}");
217
218 let env = signer.envelope(&payload("beta", 3, "*"));
219 let err = verify(&serde_json::to_vec(&env).unwrap(), &keys).unwrap_err();
220 assert_eq!(
221 err,
222 FactsRejection::WrongChannel {
223 expected: "stable".into(),
224 got: "beta".into()
225 }
226 );
227
228 let mut p = payload("stable", 3, "*");
229 p["schema_version"] = json!(2);
230 let env = signer.envelope(&p);
231 let err = verify(&serde_json::to_vec(&env).unwrap(), &keys).unwrap_err();
232 assert_eq!(err, FactsRejection::SchemaTooNew { schema_version: 2 });
233 }
234
235 #[test]
236 fn applies_to_scopes_the_whole_payload_by_binary_version() {
237 let keys = [test_only_key(KeyStatus::Active)];
238 let err = verify(FIXTURE_FUTURE_V8.as_bytes(), &keys).unwrap_err();
239 assert_eq!(
240 err,
241 FactsRejection::NotApplicable {
242 applies_to: ">=99.0.0".into()
243 }
244 );
245 // Same envelope is accepted by a binary inside the range.
246 let ok = verify_envelope(
247 FIXTURE_FUTURE_V8.as_bytes(),
248 "stable",
249 &v("99.1.0"),
250 None,
251 &keys,
252 NOW,
253 );
254 assert!(ok.is_ok(), "{ok:?}");
255
256 let signer = Signer::new("cwf-unit");
257 let keys = [signer.trusted(KeyStatus::Active)];
258 let env = signer.envelope(&payload("stable", 1, "not a range"));
259 let err = verify(&serde_json::to_vec(&env).unwrap(), &keys).unwrap_err();
260 assert!(matches!(err, FactsRejection::BadVersionReq(_)));
261
262 // Prerelease binaries follow semver: a plain range does not match them.
263 let env = signer.envelope(&payload("stable", 1, ">=0.9.0, <1.0.0"));
264 let bytes = serde_json::to_vec(&env).unwrap();
265 assert!(verify_envelope(&bytes, "stable", &v("0.9.12-beta.1"), None, &keys, NOW).is_err());
266 assert!(verify_envelope(&bytes, "stable", &v("0.9.12"), None, &keys, NOW).is_ok());
267 }
268
269 #[test]
270 fn rollback_protection_accepts_equal_and_higher_only() {
271 let signer = Signer::new("cwf-unit");
272 let keys = [signer.trusted(KeyStatus::Active)];
273 let env = serde_json::to_vec(&signer.envelope(&payload("stable", 5, "*"))).unwrap();
274 assert!(verify_envelope(&env, "stable", &v("0.9.11"), Some(4), &keys, NOW).is_ok());
275 assert!(verify_envelope(&env, "stable", &v("0.9.11"), Some(5), &keys, NOW).is_ok());
276 let err = verify_envelope(&env, "stable", &v("0.9.11"), Some(6), &keys, NOW).unwrap_err();
277 assert_eq!(
278 err,
279 FactsRejection::Rollback {
280 got: 5,
281 highest_seen: 6
282 }
283 );
284 }
285
286 #[test]
287 fn not_after_only_downgrades_to_stale_after_grace() {
288 let signer = Signer::new("cwf-unit");
289 let keys = [signer.trusted(KeyStatus::Active)];
290 let mut p = payload("stable", 5, "*");
291 p["not_after"] = json!("2026-09-01T00:00:00Z");
292 let env = serde_json::to_vec(&signer.envelope(&p)).unwrap();
293 let not_after = parse_rfc3339_utc("2026-09-01T00:00:00Z").unwrap();
294 let fresh = verify_envelope(&env, "stable", &v("0.9.11"), None, &keys, not_after - 1).unwrap();
295 assert!(!fresh.stale);
296 let in_grace =
297 verify_envelope(&env, "stable", &v("0.9.11"), None, &keys, not_after + 3600).unwrap();
298 assert!(!in_grace.stale);
299 let stale = verify_envelope(
300 &env,
301 "stable",
302 &v("0.9.11"),
303 None,
304 &keys,
305 not_after + super::verify::NOT_AFTER_GRACE_SECS + 1,
306 )
307 .unwrap();
308 assert!(stale.stale, "past grace must be stale, not rejected");
309 }
310
311 #[test]
312 fn rotation_accepts_any_pinned_active_signature() {
313 let old = Signer::new("cwf-old");
314 let new = Signer::new("cwf-new");
315 let p = payload("stable", 9, "*");
316 let bytes = serde_json::to_vec(&p).unwrap();
317 // Primary signature by the old key, extra by the new key.
318 let mut env = old.envelope(&p);
319 env["sigs"] = json!([{ "key_id": "cwf-new", "sig_b64": new.sign(&bytes) }]);
320 let bytes = serde_json::to_vec(&env).unwrap();
321 // A client that only pins the new key still accepts it.
322 let ok = verify(&bytes, &[new.trusted(KeyStatus::Active)]).unwrap();
323 assert_eq!(ok.key_id, "cwf-new");
324 // A client that retired the old key and pins the new one accepts it too.
325 let ok = verify(
326 &bytes,
327 &[
328 old.trusted(KeyStatus::Retired),
329 new.trusted(KeyStatus::Active),
330 ],
331 )
332 .unwrap();
333 assert_eq!(ok.key_id, "cwf-new");
334 // A client pinning only the old key (still active) accepts via primary.
335 let ok = verify(&bytes, &[old.trusted(KeyStatus::Active)]).unwrap();
336 assert_eq!(ok.key_id, "cwf-old");
337 }
338
339 #[test]
340 fn unknown_payload_fields_are_preserved_not_acted_on() {
341 let signer = Signer::new("cwf-unit");
342 let keys = [signer.trusted(KeyStatus::Active)];
343 let mut p = payload("stable", 5, "*");
344 p["policy"] = json!({ "deny": ["everything"] });
345 let env = serde_json::to_vec(&signer.envelope(&p)).unwrap();
346 let ok = verify(&env, &keys).unwrap();
347 assert!(ok.facts.unknown.contains_key("policy"));
348 let scoped = scoped_view(&ok, &v("0.9.11"), NOW);
349 assert!(scoped.models.is_empty() && scoped.provider_defaults.is_empty());
350 }
351
352 #[test]
353 fn scoped_view_filters_items_and_enforces_the_base_url_allowlist() {
354 let keys = [test_only_key(KeyStatus::Active)];
355 let verified = verify(FIXTURE_V7.as_bytes(), &keys).unwrap();
356 let scoped = scoped_view(&verified, &v("0.9.11"), NOW);
357
358 let ids: Vec<&str> = scoped.models.iter().map(|m| m.id.as_str()).collect();
359 assert!(
360 !ids.contains(&"future-only"),
361 "per-item applies_to must filter: {ids:?}"
362 );
363 assert_eq!(scoped.models.len(), 5);
364
365 assert_eq!(
366 scoped.provider_defaults["deepseek"]
367 .default_model
368 .as_deref(),
369 Some("deepseek-v4-pro")
370 );
371 assert_eq!(
372 scoped.provider_defaults["deepseek"].base_url.as_deref(),
373 Some("https://api.deepseek.com/beta")
374 );
375 assert!(
376 !scoped.provider_defaults.contains_key("openai"),
377 "off-family base_url must be dropped entirely"
378 );
379 assert!(
380 !scoped.provider_defaults.contains_key("ollama"),
381 "local providers accept no cloud base_url"
382 );
383 assert!(
384 scoped
385 .dropped
386 .iter()
387 .any(|d| d.contains("official HTTPS endpoint"))
388 );
389
390 let announcements: Vec<&str> = scoped.announcements.iter().map(|a| a.id.as_str()).collect();
391 assert_eq!(announcements, vec!["fixture-live"]);
392 assert_eq!(scoped.release.as_ref().unwrap().yanked, vec!["0.9.10"]);
393 }
394
395 #[test]
396 fn base_url_allowlist_is_https_and_official_host_family_only() {
397 assert!(base_url_allowed(
398 "deepseek",
399 "https://api.deepseek.com/beta"
400 ));
401 assert!(!base_url_allowed(
402 "deepseek",
403 "https://eu.api.deepseek.com/v1"
404 ));
405 assert!(!base_url_allowed(
406 "deepseek",
407 "https://api.deepseek.com/other"
408 ));
409 assert!(!base_url_allowed(
410 "deepseek",
411 "https://evil.example\\api.deepseek.com/v1"
412 ));
413 assert!(!base_url_allowed(
414 "deepseek",
415 "http://api.deepseek.com/beta"
416 ));
417 assert!(!base_url_allowed(
418 "deepseek",
419 "https://api.deepseek.com.evil.example/"
420 ));
421 assert!(!base_url_allowed(
422 "deepseek",
423 "https://user@api.deepseek.com/"
424 ));
425 assert!(!base_url_allowed("openai", "https://evil.example/v1"));
426 assert!(!base_url_allowed("ollama", "https://localhost:11434/v1"));
427 assert!(!base_url_allowed("no-such-provider", "https://x.example/"));
428 }
429
430 #[test]
431 fn catalog_layer_15_patches_sit_between_models_dev_and_provider_live() {
432 let keys = [test_only_key(KeyStatus::Active)];
433 let verified = verify(FIXTURE_V7.as_bytes(), &keys).unwrap();
434 let scoped = scoped_view(&verified, &v("0.9.11"), NOW);
435
436 let row = |id: &str, source: CatalogSource| CatalogOffering {
437 provider: "deepseek".into(),
438 wire_model_id: id.into(),
439 endpoint_key: "chat".into(),
440 limit: Some(crate::models_dev::ModelsDevLimit {
441 context: Some(1000),
442 input: None,
443 output: Some(10),
444 }),
445 reasoning: Some(false),
446 source,
447 ..CatalogOffering::default()
448 };
449 let snapshot = CatalogCompiler::new()
450 .with_bundled(vec![
451 row("deepseek-v4-pro", CatalogSource::Bundled),
452 row("deepseek-chat", CatalogSource::Bundled),
453 row("deepseek-reasoner", CatalogSource::Bundled),
454 ])
455 .with_cloud_facts(&scoped, NOW)
456 .with_provider_live(vec![row(
457 "deepseek-reasoner",
458 CatalogSource::Live {
459 base_url_fingerprint: "fp".into(),
460 fetched_at: NOW,
461 },
462 )])
463 .compile();
464
465 let find = |id: &str| {
466 snapshot
467 .offerings
468 .iter()
469 .find(|o| o.wire_model_id == id)
470 .cloned()
471 };
472 // Upsert patched only the fields it set; untouched fields survive.
473 let pro = find("deepseek-v4-pro").expect("patched row");
474 assert_eq!(pro.limit.as_ref().unwrap().context, Some(262_144));
475 assert_eq!(pro.limit.as_ref().unwrap().output, Some(32_768));
476 assert_eq!(pro.reasoning, Some(false), "unpatched field must survive");
477 assert!(matches!(
478 pro.source,
479 CatalogSource::CloudFacts {
480 facts_version: 7,
481 ..
482 }
483 ));
484 // New row materialized because it carried a context window.
485 let new_row = find("fixture-new-model").expect("new row");
486 assert_eq!(new_row.reasoning, Some(true));
487 // Patch without context for a missing row is skipped.
488 assert!(find("fixture-needs-context").is_none());
489 // Deprecate annotates, never removes.
490 let chat = find("deepseek-chat").expect("deprecated row stays");
491 assert!(
492 chat.reasoning_options
493 .iter()
494 .any(|v| v["cloud_facts"]["op"] == "deprecated")
495 );
496 // Hide removed the bundled row, but the provider-live row above layer 15
497 // re-adds it: cloud can never hide a gateway's own live row.
498 let reasoner = find("deepseek-reasoner").expect("provider-live row wins");
499 assert!(matches!(reasoner.source, CatalogSource::Live { .. }));
500
501 // Direct map application: hide on a higher-layer row is a receipt, not a removal.
502 let mut rows: BTreeMap<(String, String), CatalogOffering> = BTreeMap::new();
503 rows.insert(
504 ("deepseek".into(), "deepseek-reasoner".into()),
505 row("deepseek-reasoner", CatalogSource::UserOverride),
506 );
507 let skipped = apply_model_patches(&mut rows, &scoped, NOW);
508 assert!(rows.contains_key(&("deepseek".into(), "deepseek-reasoner".into())));
509 assert!(skipped.iter().any(|s| s.id == "deepseek-reasoner"));
510 assert_eq!(scoped.models[0].op, ModelOp::Upsert);
511 }
512
513 #[test]
514 fn overlay_supplies_provider_defaults_only_from_the_scoped_view() {
515 let keys = [test_only_key(KeyStatus::Active)];
516 let verified = verify(FIXTURE_V7.as_bytes(), &keys).unwrap();
517 let scoped = scoped_view(&verified, &v("0.9.11"), NOW);
518
519 overlay::clear();
520 assert!(overlay::cloud_default_model("deepseek").is_none());
521 let ticket = overlay::configure(true, "config-default-test").unwrap();
522 assert!(overlay::publish(
523 &ticket,
524 Some(scoped),
525 CloudFactsStatus::default()
526 ));
527 let (model, source) = overlay::cloud_default_model("deepseek").expect("cloud default");
528 assert_eq!(model, "deepseek-v4-pro");
529 assert_eq!(
530 source,
531 overlay::DefaultSource::CloudFacts { facts_version: 7 }
532 );
533 assert!(overlay::cloud_default_base_url("openai").is_none());
534 overlay::clear();
535 assert!(overlay::cloud_default_model("deepseek").is_none());
536 }
537
538 #[test]
539 fn status_labels_cover_every_state() {
540 let now = NOW;
541 let label = |state: CloudFactsState| {
542 CloudFactsStatus {
543 state,
544 ..CloudFactsStatus::default()
545 }
546 .label(now)
547 };
548 assert_eq!(label(CloudFactsState::Off), "off (bundled)");
549 assert!(label(CloudFactsState::Inert).contains("inert"));
550 assert!(label(CloudFactsState::BundledOnly).contains("bundled"));
551 let verified = label(CloudFactsState::Verified {
552 channel: "stable".into(),
553 facts_version: 42,
554 key_id: "cwf-2026-08".into(),
555 fetched_at: now - 12 * 60,
556 origin: FactsOrigin::Network,
557 stale: false,
558 patches: 3,
559 defaults: 1,
560 announcements: 0,
561 });
562 assert_eq!(
563 verified,
564 "stable v42 · verified cwf-2026-08 · fetched 12m ago (network) · 3 patches, 1 default, 0 notices"
565 );
566 assert!(
567 label(CloudFactsState::Rejected {
568 reason: "bad signature".into(),
569 at: now
570 })
571 .contains("bundled in use")
572 );
573 assert!(
574 label(CloudFactsState::NotApplicable {
575 applies_to: ">=1.0".into()
576 })
577 .contains(">=1.0")
578 );
579 assert!(
580 label(CloudFactsState::Failed {
581 last_error: "HTTP 503".into(),
582 at: now - 3 * 86_400,
583 keeping: Some(42)
584 })
585 .contains("keeping v42")
586 );
587 }
588
589 #[test]
590 fn rfc3339_utc_parser_matches_known_epochs() {
591 assert_eq!(parse_rfc3339_utc("1970-01-01T00:00:00Z"), Some(0));
592 assert_eq!(
593 parse_rfc3339_utc("2026-08-30T00:00:00Z"),
594 Some(1_788_048_000)
595 );
596 assert_eq!(
597 parse_rfc3339_utc("2026-08-30T00:00:00.123Z"),
598 Some(1_788_048_000)
599 );
600 assert_eq!(parse_rfc3339_utc("2026-08-30T00:00:00+02:00"), None);
601 assert_eq!(parse_rfc3339_utc("garbage"), None);
602 }
603
604 #[test]
605 fn pinned_keys_are_well_formed() {
606 for key in super::TRUSTED_KEYS {
607 assert!(key.key_id.starts_with("cwf-"));
608 assert_ne!(key.public_key, [0u8; 32]);
609 }
610 }
611
612 #[test]
613 fn signed_envelope_rejects_metadata_digest_future_and_malformed_times() {
614 let signer = Signer::new("cwf-contract");
615 let keys = [signer.trusted(KeyStatus::Active)];
616 let valid = signer.envelope(&payload("stable", 5, "*"));
617 for field in ["schema_version", "applies_to", "published_at", "sha256"] {
618 let mut bad = valid.clone();
619 bad.as_object_mut().unwrap().remove(field);
620 assert!(
621 verify(&serde_json::to_vec(&bad).unwrap(), &keys).is_err(),
622 "{field}"
623 );
624 }
625 for (field, value) in [
626 ("published_at", json!("2026-08-31T00:00:00Z")),
627 ("sha256", json!("0".repeat(64))),
628 ] {
629 let mut bad = valid.clone();
630 bad[field] = value;
631 assert!(matches!(
632 verify(&serde_json::to_vec(&bad).unwrap(), &keys),
633 Err(FactsRejection::Mismatch(_))
634 ));
635 }
636 for (field, value) in [
637 ("schema_version", json!(0)),
638 ("facts_version", json!(0)),
639 ("applies_to", json!("")),
640 ("published_at", json!("2099-01-01T00:00:00Z")),
641 ("not_after", json!("2026-02-30T00:00:00Z")),
642 ("not_after", json!("2026-01-01T00:00:00Z")),
643 ] {
644 let mut bad = payload("stable", 5, "*");
645 bad[field] = value;
646 assert!(
647 verify(&serde_json::to_vec(&signer.envelope(&bad)).unwrap(), &keys).is_err(),
648 "{field}"
649 );
650 }
651 let mut excessive = valid.clone();
652 excessive["sigs"] = json!(vec![
653 json!({"key_id":"cwf-contract", "sig_b64":valid["sig_b64"]});
654 8
655 ]);
656 assert!(matches!(
657 verify(&serde_json::to_vec(&excessive).unwrap(), &keys),
658 Err(FactsRejection::BadEnvelope(_))
659 ));
660 for invalid in [
661 "2026-02-30T00:00:00Z",
662 "2026-13-01T00:00:00Z",
663 "2026-01-01T25:00:00Z",
664 ] {
665 assert!(parse_rfc3339_utc(invalid).is_none());
666 }
667 }
668
669 #[test]
670 fn capability_patch_preserves_cost_authority_and_price_block_is_atomic() {
671 use super::scope::ScopedFacts;
672 use super::types::{ModelFact, PricingFact};
673 use crate::models_dev::ModelsDevCost;
674 let key = ("openai".to_string(), "fixture".to_string());
675 let row = CatalogOffering {
676 provider: key.0.clone(),
677 wire_model_id: key.1.clone(),
678 source: CatalogSource::ModelsDevLive { fetched_at: 123 },
679 cost: Some(ModelsDevCost {
680 input: Some(1.0),
681 output: Some(2.0),
682 cache_read: Some(0.1),
683 cache_write: Some(3.0),
684 }),
685 ..Default::default()
686 };
687 let mut rows = BTreeMap::from([(key.clone(), row)]);
688 let mut facts = ScopedFacts {
689 facts_version: 7,
690 key_id: "cwf-test-only".into(),
691 models: vec![ModelFact {
692 provider: key.0.clone(),
693 id: key.1.clone(),
694 context_window: Some(9000),
695 ..Default::default()
696 }],
697 ..Default::default()
698 };
699 apply_model_patches(&mut rows, &facts, NOW);
700 assert!(matches!(
701 rows[&key].source,
702 CatalogSource::CloudFacts { .. }
703 ));
704 assert_eq!(
705 rows[&key].pricing_source(),
706 &CatalogSource::ModelsDevLive { fetched_at: 123 }
707 );
708 assert_eq!(rows[&key].cost.as_ref().unwrap().cache_write, Some(3.0));
709 facts.models[0].pricing = Some(PricingFact {
710 input_per_m: Some(4.0),
711 ..Default::default()
712 });
713 apply_model_patches(&mut rows, &facts, NOW);
714 assert!(matches!(
715 rows[&key].pricing_source(),
716 CatalogSource::CloudFacts { .. }
717 ));
718 assert_eq!(
719 rows[&key].cost,
720 Some(ModelsDevCost {
721 input: Some(4.0),
722 output: None,
723 cache_read: None,
724 cache_write: None
725 })
726 );
727 facts.valid_until = Some(0);
728 facts.models[0].pricing.as_mut().unwrap().input_per_m = Some(999.0);
729 apply_model_patches(&mut rows, &facts, NOW);
730 assert_eq!(rows[&key].cost.as_ref().unwrap().input, Some(4.0));
731 }
732
733 #[test]
734 fn overlay_tickets_disable_expiry_and_source_changes_keep_channel_rollback_floors() {
735 use super::scope::ScopedFacts;
736 let facts = |channel: &str, version| ScopedFacts {
737 channel: channel.into(),
738 facts_version: version,
739 ..Default::default()
740 };
741 let first = overlay::configure(true, "gate-test-first").unwrap();
742 assert!(overlay::publish(
743 &first,
744 Some(facts("gate-stable", 7)),
745 CloudFactsStatus::default()
746 ));
747 let generation = overlay::snapshot().generation;
748 let second = overlay::configure(true, "gate-test-second").unwrap();
749 assert!(overlay::snapshot().generation > generation);
750 let mut wrote = false;
751 assert!(!overlay::publish_with(
752 &first,
753 Some(facts("gate-stable", 8)),
754 CloudFactsStatus::default(),
755 || wrote = true
756 ));
757 assert!(!wrote);
758 assert!(!overlay::publish(
759 &second,
760 Some(facts("gate-stable", 6)),
761 CloudFactsStatus::default()
762 ));
763 assert!(overlay::publish(
764 &second,
765 Some(facts("gate-beta", 2)),
766 CloudFactsStatus::default()
767 ));
768 overlay::clear();
769 assert!(overlay::overlay().is_none());
770 assert!(!overlay::is_current(&second));
771 let third = overlay::configure(true, "gate-test-first").unwrap();
772 assert_eq!(overlay::highest_seen("gate-stable"), Some(7));
773 assert_eq!(overlay::highest_seen("gate-beta"), Some(2));
774 assert!(!overlay::publish(
775 &third,
776 Some(facts("gate-stable", 6)),
777 CloudFactsStatus::default()
778 ));
779 let mut expired = facts("gate-stable", 8);
780 expired.valid_until = Some(0);
781 assert!(overlay::publish(
782 &third,
783 Some(expired),
784 CloudFactsStatus::default()
785 ));
786 let generation = overlay::snapshot().generation;
787 assert!(overlay::overlay().is_none());
788 assert_eq!(overlay::snapshot().generation, generation);
789 assert_eq!(overlay::highest_seen("gate-stable"), Some(8));
790 overlay::clear();
791 }
792
793 #[test]
794 fn cloud_endpoint_contract_rejects_dynamic_origins_and_prefix_expansion() {
795 assert!(base_url_allowed(
796 "codewhale",
797 crate::DEFAULT_CODEWHALE_BASE_URL
798 ));
799 assert!(!base_url_allowed("codewhale", "https://private.example/v1"));
800 assert!(base_url_allowed(
801 "moonshot",
802 "https://api.kimi.com/coding/v1"
803 ));
804 for url in [
805 "https://api.kimi.com/coding/private",
806 "https://api.kimi.com/coding/v1?key=fixture",
807 "https://api.kimi.com/coding/v1#fixture",
808 "https://fixture@api.kimi.com/coding/v1",
809 "https://api.kimi.com/coding/v1\\private",
810 ] {
811 assert!(!base_url_allowed("moonshot", url), "{url}");
812 }
813 assert!(!base_url_allowed(
814 "xiaomimimo",
815 "https://api.xiaomimimo.com/v1/private"
816 ));
817 }
818
819 #[test]
820 fn legacy_live_and_operator_layers_cannot_be_overwritten_by_cloud_patches() {
821 use super::{ModelFact, PricingFact, ScopedFacts};
822 let key = ("openai".to_string(), "layer-fixture".to_string());
823 let facts = ScopedFacts {
824 facts_version: 1,
825 models: vec![ModelFact {
826 provider: key.0.clone(),
827 id: key.1.clone(),
828 context_window: Some(9000),
829 pricing: Some(PricingFact {
830 input_per_m: Some(999.0),
831 ..Default::default()
832 }),
833 ..Default::default()
834 }],
835 ..Default::default()
836 };
837 for source in [
838 CatalogSource::Live {
839 base_url_fingerprint: "fixture".into(),
840 fetched_at: NOW,
841 },
842 CatalogSource::ConfigOverride,
843 CatalogSource::UserOverride,
844 ] {
845 let row = CatalogOffering {
846 provider: key.0.clone(),
847 wire_model_id: key.1.clone(),
848 source,
849 ..Default::default()
850 };
851 let mut rows = BTreeMap::from([(key.clone(), row.clone())]);
852 assert_eq!(apply_model_patches(&mut rows, &facts, NOW).len(), 1);
853 assert_eq!(rows[&key], row);
854 }
855 let row = CatalogOffering {
856 provider: key.0.clone(),
857 wire_model_id: key.1.clone(),
858 source: CatalogSource::Live {
859 base_url_fingerprint: "fixture".into(),
860 fetched_at: NOW,
861 },
862 ..Default::default()
863 };
864 let snapshot = CatalogCompiler::new()
865 .with_live(vec![row.clone()])
866 .with_cloud_facts(&facts, NOW)
867 .compile();
868 assert_eq!(snapshot.offerings, vec![row]);
869 }
870
871 /// The complement of the layer test above: a correction must actually *reach* a
872 /// live Models.dev row, because that layer describes most of the models a user
873 /// sees. Both sides go through [`crate::catalog::CatalogCompiler::with_live`],
874 /// which routes a row by its own source, and the enriched rows come from the
875 /// real producer — so this fails if the refresh ever goes back to stamping
876 /// `Live`, which put every enriched row above the layer meant to correct it and
877 /// left a published rate change reaching nobody without a reinstall.
878 #[test]
879 fn signed_facts_correct_a_live_models_dev_row_but_never_the_provider_roster() {
880 use super::{ModelFact, PricingFact, ScopedFacts};
881 use crate::catalog::live_offerings_from_models_dev;
882 use crate::models_dev::ModelsDevCatalog;
883
884 let raw = r#"{
885 "models": {},
886 "providers": {
887 "deepseek": {
888 "id": "deepseek",
889 "models": {
890 "deepseek-chat": {
891 "id": "deepseek-chat",
892 "modalities": { "input": ["text"], "output": ["text"] },
893 "limit": { "context": 65536, "output": 4096 },
894 "cost": { "input": 2.0, "output": 8.0 }
895 }
896 }
897 }
898 }
899 }"#;
900 let catalog = ModelsDevCatalog::parse_json(raw).expect("fixture parses");
901 let enriched = live_offerings_from_models_dev(&catalog, NOW);
902 assert_eq!(enriched.len(), 1, "one text-chat row");
903
904 let facts = ScopedFacts {
905 facts_version: 3,
906 key_id: "cwf-test-only".into(),
907 models: vec![ModelFact {
908 provider: "deepseek".into(),
909 id: "deepseek-chat".into(),
910 context_window: Some(131_072),
911 pricing: Some(PricingFact {
912 input_per_m: Some(0.5),
913 output_per_m: Some(1.5),
914 ..Default::default()
915 }),
916 ..Default::default()
917 }],
918 ..Default::default()
919 };
920
921 let corrected = CatalogCompiler::new()
922 .with_live(enriched.clone())
923 .with_cloud_facts(&facts, NOW)
924 .compile();
925 let row = corrected
926 .offerings
927 .iter()
928 .find(|row| row.wire_model_id == "deepseek-chat")
929 .expect("the row survives its own correction");
930 let limit = row.limit.as_ref().expect("limits are kept");
931 assert_eq!(
932 limit.context,
933 Some(131_072),
934 "the stale window is corrected"
935 );
936 assert_eq!(
937 limit.output,
938 Some(4_096),
939 "and only what the payload states is replaced"
940 );
941 assert_eq!(row.cost.as_ref().and_then(|cost| cost.input), Some(0.5));
942 assert_eq!(row.cost.as_ref().and_then(|cost| cost.output), Some(1.5));
943 assert!(matches!(
944 row.pricing_source(),
945 CatalogSource::CloudFacts { .. }
946 ));
947
948 // The same payload, same wire, once the provider's own roster owns the id:
949 // an authenticated endpoint outranks a signed correction, field for field.
950 let roster = CatalogOffering {
951 source: CatalogSource::Live {
952 base_url_fingerprint: "deepseek-endpoint".into(),
953 fetched_at: NOW,
954 },
955 ..enriched[0].clone()
956 };
957 let untouched = CatalogCompiler::new()
958 .with_live(vec![roster.clone()])
959 .with_cloud_facts(&facts, NOW)
960 .compile();
961 assert_eq!(untouched.offerings, vec![roster]);
962 }
963
964 #[test]
965 fn unsigned_metadata_cannot_amplify_rejection_messages() {
966 let signer = Signer::new("cwf-bounds");
967 let keys = [signer.trusted(KeyStatus::Active)];
968 for field in ["alg", "channel", "applies_to", "published_at", "sha256"] {
969 let mut envelope = signer.envelope(&payload("stable", 1, "*"));
970 envelope[field] = json!("x".repeat(16_384));
971 let error = verify(&serde_json::to_vec(&envelope).unwrap(), &keys).unwrap_err();
972 assert!(error.to_string().len() < 256, "{field}");
973 }
974 }
975
976 /// The unlisted assertion is the only thing that may override a provider
977 /// roster's own omission, so the scoped view is where it is bounded: an
978 /// `upsert` in a payload that expires. The rest of the patch survives either
979 /// way — only the assertion is withdrawn, with a receipt.
980 #[test]
981 fn unlisted_assertion_needs_an_upsert_in_an_expiring_payload() {
982 let signer = Signer::new("cwf-unlisted");
983 let keys = [signer.trusted(KeyStatus::Active)];
984 let models = json!([
985 {"provider": "deepseek", "id": "preview-a", "allow_unlisted": true},
986 {"provider": "deepseek", "id": "preview-b", "op": "hide", "allow_unlisted": true},
987 {"provider": "deepseek", "id": "listed-c", "context_window": 1000},
988 ]);
989
990 let mut bounded = payload("stable", 11, "*");
991 bounded["models"] = models.clone();
992 bounded["not_after"] = json!("2026-12-31T00:00:00Z");
993 let verified = verify(
994 &serde_json::to_vec(&signer.envelope(&bounded)).unwrap(),
995 &keys,
996 )
997 .unwrap();
998 let scoped = scoped_view(&verified, &v("0.9.11"), NOW);
999 assert!(scoped.valid_until.is_some());
1000 assert_eq!(scoped.models.len(), 3, "patches themselves are not dropped");
1001 assert!(super::catalog_patch::is_unlisted_attested(
1002 &scoped,
1003 "deepseek",
1004 "preview-a"
1005 ));
1006 assert!(
1007 !super::catalog_patch::is_unlisted_attested(&scoped, "deepseek", "preview-b"),
1008 "a hide cannot carry an existence assertion"
1009 );
1010 assert!(!super::catalog_patch::is_unlisted_attested(
1011 &scoped, "deepseek", "listed-c"
1012 ));
1013 assert!(
1014 !super::catalog_patch::is_unlisted_attested(&scoped, "deepseek", "preview-a-x"),
1015 "the assertion names one exact id, never a prefix"
1016 );
1017
1018 let mut unbounded = payload("stable", 12, "*");
1019 unbounded["models"] = models;
1020 let verified = verify(
1021 &serde_json::to_vec(&signer.envelope(&unbounded)).unwrap(),
1022 &keys,
1023 )
1024 .unwrap();
1025 let scoped = scoped_view(&verified, &v("0.9.11"), NOW);
1026 assert!(scoped.valid_until.is_none());
1027 assert_eq!(scoped.models.len(), 3);
1028 assert!(
1029 !super::catalog_patch::is_unlisted_attested(&scoped, "deepseek", "preview-a"),
1030 "an assertion that cannot expire is not honored"
1031 );
1032 assert_eq!(
1033 scoped
1034 .dropped
1035 .iter()
1036 .filter(|receipt| receipt.contains("allow_unlisted"))
1037 .count(),
1038 2
1039 );
1040 }
1041
1042 /// An attested id needs no invented context window to be listed, and gets no
1043 /// invented anything else either.
1044 #[test]
1045 fn attested_id_only_upsert_creates_a_row_with_everything_unknown() {
1046 use super::{ModelFact, ScopedFacts};
1047 let bare = |allow_unlisted| ModelFact {
1048 provider: "deepseek".into(),
1049 id: "preview-a".into(),
1050 allow_unlisted,
1051 ..Default::default()
1052 };
1053 let key = ("deepseek".to_string(), "preview-a".to_string());
1054
1055 let mut rows = BTreeMap::new();
1056 let skipped = apply_model_patches(
1057 &mut rows,
1058 &ScopedFacts {
1059 models: vec![bare(false)],
1060 ..Default::default()
1061 },
1062 NOW,
1063 );
1064 assert!(rows.is_empty(), "an id alone still creates nothing");
1065 assert_eq!(skipped.len(), 1);
1066 assert!(
1067 skipped[0]
1068 .reason
1069 .contains("context_window or allow_unlisted")
1070 );
1071
1072 let mut rows = BTreeMap::new();
1073 assert!(
1074 apply_model_patches(
1075 &mut rows,
1076 &ScopedFacts {
1077 facts_version: 3,
1078 key_id: "cwf-unlisted".into(),
1079 valid_until: Some(crate::catalog::now_unix() + 3600),
1080 models: vec![bare(true)],
1081 ..Default::default()
1082 },
1083 NOW,
1084 )
1085 .is_empty()
1086 );
1087 let row = &rows[&key];
1088 assert_eq!(row.wire_model_id, "preview-a");
1089 assert_eq!(row.limit, None, "no limits were stated, so none are known");
1090 assert_eq!(row.cost, None);
1091 assert_eq!(row.cost_source, None);
1092 assert_eq!(row.reasoning, None);
1093 assert_eq!(row.tool_call, None);
1094 assert_eq!(row.modalities, None);
1095 assert_eq!(row.attachment, None);
1096 assert!(matches!(row.source, CatalogSource::CloudFacts { .. }));
1097 }
1098
1099 /// A roster that answers with ids alone has said nothing about limits — not
1100 /// that they are unknown. Completion fills only that silence.
1101 #[test]
1102 fn provider_live_rows_are_completed_only_where_the_provider_is_silent() {
1103 use super::catalog_patch::complete_provider_live_row;
1104 use super::{ModelFact, PricingFact, ScopedFacts};
1105 use crate::models_dev::{ModelsDevCost, ModelsDevLimit};
1106 let live = CatalogSource::Live {
1107 base_url_fingerprint: "fixture".into(),
1108 fetched_at: NOW,
1109 };
1110 let facts = ScopedFacts {
1111 facts_version: 4,
1112 key_id: "cwf-unlisted".into(),
1113 models: vec![ModelFact {
1114 provider: "deepseek".into(),
1115 id: "roster-model".into(),
1116 context_window: Some(131_072),
1117 max_output: Some(8_192),
1118 reasoning: Some(true),
1119 pricing: Some(PricingFact {
1120 input_per_m: Some(1.0),
1121 output_per_m: Some(2.0),
1122 ..Default::default()
1123 }),
1124 ..Default::default()
1125 }],
1126 ..Default::default()
1127 };
1128 let id_only = CatalogOffering {
1129 provider: "deepseek".into(),
1130 wire_model_id: "roster-model".into(),
1131 source: live.clone(),
1132 ..Default::default()
1133 };
1134
1135 let mut row = id_only.clone();
1136 assert!(complete_provider_live_row(&mut row, &facts));
1137 assert_eq!(row.limit.as_ref().unwrap().context, Some(131_072));
1138 assert_eq!(row.limit.as_ref().unwrap().output, Some(8_192));
1139 assert_eq!(row.reasoning, Some(true));
1140 assert_eq!(row.source, live, "the row is still the provider's");
1141 assert_eq!(row.cost, None, "a signed price is not billable here");
1142 assert_eq!(row.cost_source, None);
1143
1144 // Anything the provider actually stated wins, field by field.
1145 let mut row = CatalogOffering {
1146 limit: Some(ModelsDevLimit {
1147 context: Some(64_000),
1148 ..Default::default()
1149 }),
1150 reasoning: Some(false),
1151 cost: Some(ModelsDevCost {
1152 input: Some(9.0),
1153 ..Default::default()
1154 }),
1155 ..id_only.clone()
1156 };
1157 assert!(complete_provider_live_row(&mut row, &facts));
1158 assert_eq!(row.limit.as_ref().unwrap().context, Some(64_000));
1159 assert_eq!(row.limit.as_ref().unwrap().output, Some(8_192));
1160 assert_eq!(row.reasoning, Some(false));
1161 assert_eq!(row.cost.as_ref().unwrap().input, Some(9.0));
1162
1163 // Only provider-live rows are completed, and only by current facts.
1164 for source in [
1165 CatalogSource::Bundled,
1166 CatalogSource::ConfigOverride,
1167 CatalogSource::UserOverride,
1168 CatalogSource::ModelsDevLive { fetched_at: NOW },
1169 ] {
1170 let mut row = CatalogOffering {
1171 source,
1172 ..id_only.clone()
1173 };
1174 let before = row.clone();
1175 assert!(!complete_provider_live_row(&mut row, &facts));
1176 assert_eq!(row, before);
1177 }
1178 let mut row = id_only.clone();
1179 let expired = ScopedFacts {
1180 valid_until: Some(0),
1181 ..facts.clone()
1182 };
1183 assert!(!complete_provider_live_row(&mut row, &expired));
1184 assert_eq!(row, id_only);
1185 }
1186
1187 /// Producer/consumer parity for the additive field: an older payload without it
1188 /// keeps roster dominance, and a newer one round-trips through the same wire
1189 /// shape the publisher signs.
1190 #[test]
1191 fn allow_unlisted_defaults_to_false_and_round_trips() {
1192 use super::ModelFact;
1193 let without: ModelFact =
1194 serde_json::from_value(json!({"provider": "deepseek", "id": "preview-a"})).unwrap();
1195 assert!(!without.allow_unlisted);
1196 assert_eq!(
1197 serde_json::to_value(&without)
1198 .unwrap()
1199 .get("allow_unlisted"),
1200 None,
1201 "an unset assertion must not appear in the signed bytes"
1202 );
1203 let with: ModelFact = serde_json::from_value(
1204 json!({"provider": "deepseek", "id": "preview-a", "allow_unlisted": true}),
1205 )
1206 .unwrap();
1207 assert!(with.allow_unlisted);
1208 assert_eq!(
1209 serde_json::to_value(&with).unwrap()["allow_unlisted"],
1210 json!(true)
1211 );
1212 }
1213
1214 #[test]
1215 fn cloud_route_identity_preserves_legacy_wire_endpoint_boundaries() {
1216 let chat = crate::default_base_url_for_provider(crate::ProviderKind::Minimax);
1217 let anthropic = crate::default_base_url_for_provider(crate::ProviderKind::MinimaxAnthropic);
1218 assert!(base_url_allowed("minimax", chat));
1219 assert!(base_url_allowed("minimax-anthropic", anthropic));
1220 assert!(!base_url_allowed("minimax-anthropic", chat));
1221 assert!(!base_url_allowed("minimax", anthropic));
1222 assert!(
1223 !base_url_allowed("minimax_anthropic", anthropic),
1224 "signed identities must use their exact canonical spelling"
1225 );
1226 let signer = Signer::new("cwf-wire-identity");
1227 let mut p = payload("stable", 1, "*");
1228 p["models"] =
1229 json!([{"provider":"minimax-anthropic", "id":"MiniMax-M3", "context_window":1000}]);
1230 p["provider_defaults"] =
1231 json!({"minimax-anthropic":{"default_model":"MiniMax-M3", "base_url":anthropic}});
1232 let verified = verify(
1233 &serde_json::to_vec(&signer.envelope(&p)).unwrap(),
1234 &[signer.trusted(KeyStatus::Active)],
1235 )
1236 .unwrap();
1237 let scoped = scoped_view(&verified, &v("0.9.11"), NOW);
1238 assert_eq!(scoped.models[0].provider, "minimax-anthropic");
1239 assert_eq!(
1240 scoped.provider_defaults["minimax-anthropic"]
1241 .base_url
1242 .as_deref(),
1243 Some(anthropic)
1244 );
1245 assert!(!scoped.provider_defaults.contains_key("minimax"));
1246 }
1247
1247 lines RUST