//! DTO contract tests for local model-server IPC. use app_tauri_lib::dto::{ save_model_server_input, ModelServerConfigDto, ModelServerKindDto, ModelSourceDto, SaveModelServerRequestDto, StopPolicyDto, }; use domain::model_server::{ ExecutablePath, LlamaCppOptions, LocalModelRef, LocalModelServerConfig, LocalModelServerKind, ModelPath, ModelServerEndpoint, ModelSource, }; use domain::{LocalModelServerId, StopPolicy}; use serde_json::json; use uuid::Uuid; fn sid(n: u128) -> LocalModelServerId { LocalModelServerId::from_uuid(Uuid::from_u128(n)) } fn domain_config(id: LocalModelServerId, model_id: &str) -> LocalModelServerConfig { LocalModelServerConfig::new( id, LocalModelServerKind::LlamaCpp, "Local Qwen", ModelServerEndpoint::new("http://localhost:8080", 8080).unwrap(), LocalModelRef::new( model_id, "Qwen", Some(ModelSource::LocalPath { path: ModelPath::new("/models/qwen.gguf").unwrap(), }), "qwen3-coder", ) .unwrap(), Some(ExecutablePath::new("/usr/bin/llama-server").unwrap()), LlamaCppOptions::new("127.0.0.1", Some(35), Some(4096), true).unwrap(), vec!["--threads".to_owned(), "8".to_owned()], true, StopPolicy::StopOnAppExit, ) .unwrap() } #[test] fn model_server_dto_serialises_flat_camelcase_wire_shape() { let dto = ModelServerConfigDto::from_domain(domain_config(sid(35), "internal-model-id")); let value = serde_json::to_value(dto).unwrap(); assert_eq!(value["id"], sid(35).to_string()); assert_eq!(value["kind"], "llamaCpp"); assert_eq!(value["baseURL"], "http://localhost:8080/v1"); assert_eq!(value["modelSource"]["type"], "localPath"); assert_eq!(value["modelSource"]["path"], "/models/qwen.gguf"); assert!(value.get("modelPath").is_none()); assert_eq!(value["servedModelName"], "qwen3-coder"); assert_eq!(value["binaryPath"], "/usr/bin/llama-server"); assert_eq!(value["host"], "127.0.0.1"); assert_eq!(value["gpuLayers"], 35); assert_eq!(value["contextSize"], 4096); assert_eq!(value["jinja"], true); assert_eq!(value["stopPolicy"], "stopOnAppExit"); assert!(value.get("warmupDeadlineSecs").is_none()); assert!(value.get("model").is_none(), "domain model must not leak"); assert!( value.get("endpoint").is_none(), "domain endpoint must not leak" ); } #[test] fn model_server_dto_deserialises_flat_wire_shape_and_generates_model_id() { let raw = json!({ "id": sid(36).to_string(), "kind": "llamaCpp", "name": "Local Qwen", "baseURL": "http://localhost:8081", "port": 8081, "modelPath": "/models/qwen.gguf", "servedModelName": "qwen3-coder", "binaryPath": "/usr/bin/llama-server", "gpuLayers": 35, "contextSize": 4096, "args": ["--threads", "8"], "autoStart": true, "stopPolicy": "stopOnAppExit" }); let request = SaveModelServerRequestDto { config: serde_json::from_value(raw).unwrap(), }; let input = save_model_server_input(request, None).unwrap(); assert_eq!(input.config.id, sid(36)); assert_eq!(input.config.kind, LocalModelServerKind::LlamaCpp); assert_eq!(input.config.endpoint.base_url, "http://localhost:8081/v1"); assert_eq!( input.config.model.source, Some(ModelSource::LocalPath { path: ModelPath::new("/models/qwen.gguf").unwrap() }) ); assert_eq!(input.config.options.gpu_layers, Some(35)); assert_eq!(input.config.options.context_size, Some(4096)); assert_eq!(input.config.options.host, "127.0.0.1"); assert_eq!(input.config.warmup_deadline_secs, None); assert!(!input.config.options.jinja); assert_eq!(input.config.model.served_name, "qwen3-coder"); assert_eq!(input.config.model.label, "qwen3-coder"); assert!(!input.config.model.id.is_empty()); } #[test] fn model_server_dto_preserves_existing_internal_model_id_on_upsert() { let existing = domain_config(sid(37), "stable-internal-id"); let dto = ModelServerConfigDto { id: sid(37).to_string(), kind: ModelServerKindDto::LlamaCpp, name: "Updated Qwen".to_owned(), base_url: "http://localhost:8082/v1".to_owned(), port: 8082, model_source: Some(ModelSourceDto::LocalPath { path: "/models/qwen-updated.gguf".to_owned(), }), model_path: None, served_model_name: "qwen3-coder-updated".to_owned(), binary_path: Some("/usr/bin/llama-server".to_owned()), host: "127.0.0.1".to_owned(), gpu_layers: None, context_size: None, jinja: false, args: Vec::new(), auto_start: true, stop_policy: StopPolicyDto::StopOnAppExit, warmup_deadline_secs: Some(900), }; let config = dto.into_domain(Some(&existing)).unwrap(); assert_eq!(config.model.id, "stable-internal-id"); assert_eq!(config.model.served_name, "qwen3-coder-updated"); assert_eq!(config.warmup_deadline_secs, Some(900)); } #[test] fn model_server_dto_rejects_invalid_warmup_deadline_secs() { let raw = json!({ "id": sid(40).to_string(), "kind": "llamaCpp", "name": "Local Qwen", "baseURL": "http://localhost:8085", "port": 8085, "modelPath": "/models/qwen.gguf", "servedModelName": "qwen3-coder", "host": "127.0.0.1", "jinja": false, "args": [], "autoStart": true, "stopPolicy": "stopOnAppExit", "warmupDeadlineSecs": 29 }); let request = SaveModelServerRequestDto { config: serde_json::from_value(raw).unwrap(), }; assert_eq!( save_model_server_input(request, None).unwrap_err().code, "INVALID" ); } #[test] fn model_server_dto_roundtrips_v2_huggingface_source() { let raw = json!({ "id": sid(38).to_string(), "kind": "llamaCpp", "name": "HF Qwen", "baseURL": "http://localhost:8083", "port": 8083, "modelSource": { "type": "huggingFace", "repo": "Qwen/Qwen3-Coder:Q4_K_M" }, "servedModelName": "qwen3-coder", "binaryPath": "/usr/bin/llama-server", "host": "127.0.0.1", "jinja": false, "args": [], "autoStart": true, "stopPolicy": "stopOnAppExit" }); let request = SaveModelServerRequestDto { config: serde_json::from_value(raw).unwrap(), }; let input = save_model_server_input(request, None).unwrap(); assert_eq!( input.config.model.source, Some(ModelSource::HuggingFace { repo: domain::model_server::HfModelRef::new("Qwen/Qwen3-Coder:Q4_K_M").unwrap() }) ); let value = serde_json::to_value(ModelServerConfigDto::from_domain(input.config)).unwrap(); assert_eq!(value["modelSource"]["type"], "huggingFace"); assert_eq!(value["modelSource"]["repo"], "Qwen/Qwen3-Coder:Q4_K_M"); assert!(value.get("modelPath").is_none()); } #[test] fn model_server_dto_rejects_conflicting_model_path_alias() { let raw = json!({ "id": sid(39).to_string(), "kind": "llamaCpp", "name": "Local Qwen", "baseURL": "http://localhost:8084", "port": 8084, "modelSource": { "type": "localPath", "path": "/models/a.gguf" }, "modelPath": "/models/b.gguf", "servedModelName": "qwen3-coder", "host": "127.0.0.1", "jinja": false, "args": [], "autoStart": true, "stopPolicy": "stopOnAppExit" }); let request = SaveModelServerRequestDto { config: serde_json::from_value(raw).unwrap(), }; assert_eq!( save_model_server_input(request, None).unwrap_err().code, "INVALID" ); }