//! Tests for the declarative [`EmbedderProfile`] / [`EmbedderStrategy`] (LOT C, //! étage 2 vectoriel): serde camelCase round-trip, the validated `new` //! constructor, and the dependency-free `none()` default. use domain::{DomainError, EmbedderProfile, EmbedderStrategy}; fn roundtrip(value: &T) -> T where T: serde::Serialize + serde::de::DeserializeOwned + PartialEq + std::fmt::Debug, { let json = serde_json::to_string(value).expect("serialize"); let back: T = serde_json::from_str(&json).expect("deserialize"); assert_eq!(&back, value, "round-trip mismatch via {json}"); back } // --------------------------------------------------------------------------- // EmbedderStrategy: camelCase wire form (`localOnnx` / `localServer` / `api` / // `none`). // --------------------------------------------------------------------------- #[test] fn strategy_serializes_camel_case() { assert_eq!( serde_json::to_string(&EmbedderStrategy::LocalOnnx).unwrap(), "\"localOnnx\"" ); assert_eq!( serde_json::to_string(&EmbedderStrategy::LocalServer).unwrap(), "\"localServer\"" ); assert_eq!( serde_json::to_string(&EmbedderStrategy::Api).unwrap(), "\"api\"" ); assert_eq!( serde_json::to_string(&EmbedderStrategy::None).unwrap(), "\"none\"" ); } #[test] fn strategy_deserializes_camel_case() { assert_eq!( serde_json::from_str::("\"localOnnx\"").unwrap(), EmbedderStrategy::LocalOnnx ); assert_eq!( serde_json::from_str::("\"localServer\"").unwrap(), EmbedderStrategy::LocalServer ); assert_eq!( serde_json::from_str::("\"api\"").unwrap(), EmbedderStrategy::Api ); assert_eq!( serde_json::from_str::("\"none\"").unwrap(), EmbedderStrategy::None ); } #[test] fn strategy_roundtrips() { for s in [ EmbedderStrategy::LocalOnnx, EmbedderStrategy::LocalServer, EmbedderStrategy::Api, EmbedderStrategy::None, ] { roundtrip(&s); } } // --------------------------------------------------------------------------- // EmbedderProfile: camelCase fields and optional-field skipping. // --------------------------------------------------------------------------- #[test] fn profile_roundtrips_with_all_fields() { let p = EmbedderProfile::new( "local-onnx-minilm", "Local MiniLM", EmbedderStrategy::LocalOnnx, Some("all-MiniLM-L6-v2".to_owned()), Some("http://localhost:1234".to_owned()), Some("OPENAI_API_KEY".to_owned()), 384, ) .unwrap(); let back = roundtrip(&p); assert_eq!(back.dimension, 384); assert_eq!(back.strategy, EmbedderStrategy::LocalOnnx); } #[test] fn profile_uses_camel_case_keys_and_skips_none_options() { let p = EmbedderProfile::new( "id-1", "Name", EmbedderStrategy::Api, None, Some("https://api.example/embed".to_owned()), Some("MY_KEY".to_owned()), 16, ) .unwrap(); let json = serde_json::to_string(&p).unwrap(); assert!(json.contains("\"apiKeyEnv\":\"MY_KEY\""), "camelCase apiKeyEnv: {json}"); assert!(json.contains("\"strategy\":\"api\""), "camelCase strategy: {json}"); // `model` is None ⇒ skipped from the wire form. assert!(!json.contains("\"model\""), "None model must be skipped: {json}"); } #[test] fn profile_deserializes_from_camel_case_document() { let json = r#"{ "id": "srv", "name": "Server", "strategy": "localServer", "endpoint": "http://127.0.0.1:9000", "dimension": 512 }"#; let p: EmbedderProfile = serde_json::from_str(json).unwrap(); assert_eq!(p.id, "srv"); assert_eq!(p.strategy, EmbedderStrategy::LocalServer); assert_eq!(p.endpoint.as_deref(), Some("http://127.0.0.1:9000")); assert_eq!(p.dimension, 512); assert!(p.model.is_none()); assert!(p.api_key_env.is_none()); } // --------------------------------------------------------------------------- // none(): the dependency-free default. // --------------------------------------------------------------------------- #[test] fn none_profile_has_none_strategy() { let p = EmbedderProfile::none(); assert_eq!(p.strategy, EmbedderStrategy::None); assert_eq!(p.id, "none"); assert!(p.dimension > 0, "even the none profile keeps a non-zero dimension"); // And it round-trips like any other profile. roundtrip(&p); } // --------------------------------------------------------------------------- // new(): validation. // --------------------------------------------------------------------------- #[test] fn new_rejects_empty_id() { assert!(matches!( EmbedderProfile::new("", "Name", EmbedderStrategy::None, None, None, None, 8), Err(DomainError::EmptyField { field: "embedder.id" }) )); } #[test] fn new_rejects_empty_name() { assert!(matches!( EmbedderProfile::new("id", "", EmbedderStrategy::None, None, None, None, 8), Err(DomainError::EmptyField { field: "embedder.name" }) )); } #[test] fn new_rejects_zero_dimension() { assert!(matches!( EmbedderProfile::new("id", "Name", EmbedderStrategy::None, None, None, None, 0), Err(DomainError::EmptyField { field: "embedder.dimension" }) )); } #[test] fn new_accepts_valid_minimal_profile() { let p = EmbedderProfile::new("id", "Name", EmbedderStrategy::None, None, None, None, 1).unwrap(); assert_eq!(p.dimension, 1); assert_eq!(p.strategy, EmbedderStrategy::None); }