//! L7 tests for template & sync DTO (de)serialisation contract: camelCase on //! the wire, `TemplateDto`/`TemplateListDto` shapes, `AgentDriftDto`/ //! `AgentDriftListDto`, `SyncResultDto`, request DTO deserialisation, and //! `parse_template_id` error behaviour. No Tauri runtime required. use app_tauri_lib::dto::{ parse_template_id, AgentDriftDto, AgentDriftListDto, CreateAgentFromTemplateRequestDto, CreateTemplateRequestDto, SyncResultDto, TemplateDto, TemplateListDto, UpdateTemplateRequestDto, }; use application::{ AgentDrift, CreateTemplateOutput, DetectAgentDriftOutput, ListTemplatesOutput, SyncAgentWithTemplateOutput, UpdateTemplateOutput, }; use domain::ids::{AgentId, ProfileId, TemplateId}; use domain::markdown::MarkdownDoc; use domain::template::{AgentTemplate, TemplateVersion}; use serde_json::json; use uuid::Uuid; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- fn make_template(template_uuid: u128, profile_uuid: u128) -> AgentTemplate { AgentTemplate::new( TemplateId::from_uuid(Uuid::from_u128(template_uuid)), "My Template", MarkdownDoc::new("# Hello".to_owned()), ProfileId::from_uuid(Uuid::from_u128(profile_uuid)), ) .expect("valid template") } // --------------------------------------------------------------------------- // TemplateDto serialisation // --------------------------------------------------------------------------- #[test] fn template_dto_serialises_camelcase() { let tmpl = make_template(1, 2); let dto = TemplateDto(tmpl.clone()); let v = serde_json::to_value(&dto).unwrap(); assert_eq!(v["id"], tmpl.id.to_string()); assert_eq!(v["name"], "My Template"); // contentMd is the camelCase field on AgentTemplate; MarkdownDoc is transparent → plain string assert_eq!(v["contentMd"], "# Hello"); // version is a transparent number assert_eq!(v["version"], 1u64); assert_eq!( v["defaultProfileId"], ProfileId::from_uuid(Uuid::from_u128(2)).to_string() ); // no snake_case leak assert!(v.get("content_md").is_none(), "no snake_case leak for contentMd"); assert!(v.get("default_profile_id").is_none(), "no snake_case leak for defaultProfileId"); } #[test] fn template_dto_version_is_number() { let tmpl = make_template(3, 4); let dto = TemplateDto(tmpl); let v = serde_json::to_value(&dto).unwrap(); assert!(v["version"].is_number(), "version should be a JSON number"); assert_eq!(v["version"].as_u64().unwrap(), 1); } // --------------------------------------------------------------------------- // TemplateListDto // --------------------------------------------------------------------------- #[test] fn template_list_dto_is_transparent_array() { let out = ListTemplatesOutput { templates: vec![make_template(1, 2), make_template(3, 4)], }; let dto = TemplateListDto::from(out); let v = serde_json::to_value(&dto).unwrap(); let arr = v.as_array().expect("transparent array"); assert_eq!(arr.len(), 2); assert_eq!(arr[0]["name"], "My Template"); assert_eq!(arr[1]["name"], "My Template"); } #[test] fn template_list_dto_empty() { let out = ListTemplatesOutput { templates: vec![] }; let dto = TemplateListDto::from(out); let v = serde_json::to_value(&dto).unwrap(); assert!(v.as_array().unwrap().is_empty()); } // --------------------------------------------------------------------------- // From / From // --------------------------------------------------------------------------- #[test] fn create_template_output_maps_to_template_dto() { let tmpl = make_template(5, 6); let out = CreateTemplateOutput { template: tmpl.clone() }; let dto = TemplateDto::from(out); assert_eq!(dto.0.id, tmpl.id); } #[test] fn update_template_output_maps_to_template_dto() { let tmpl = make_template(7, 8); let bumped = tmpl.with_updated_content(MarkdownDoc::new("# Updated".to_owned())); let out = UpdateTemplateOutput { template: bumped.clone() }; let dto = TemplateDto::from(out); assert_eq!(dto.0.version, TemplateVersion(2)); assert_eq!(dto.0.id, bumped.id); } // --------------------------------------------------------------------------- // AgentDriftDto / AgentDriftListDto serialisation // --------------------------------------------------------------------------- #[test] fn agent_drift_dto_serialises_camelcase() { let agent_id = AgentId::from_uuid(Uuid::from_u128(10)); let drift = AgentDrift { agent_id, from: TemplateVersion(1), to: TemplateVersion(3), }; let dto = AgentDriftDto::from(drift); let v = serde_json::to_value(&dto).unwrap(); assert_eq!(v["agentId"], agent_id.to_string()); assert_eq!(v["from"], 1u64); assert_eq!(v["to"], 3u64); // no snake_case leak assert!(v.get("agent_id").is_none(), "no snake_case leak for agentId"); } #[test] fn agent_drift_list_dto_is_transparent_array() { let agent_id = AgentId::from_uuid(Uuid::from_u128(11)); let out = DetectAgentDriftOutput { drifts: vec![AgentDrift { agent_id, from: TemplateVersion(2), to: TemplateVersion(5), }], }; let dto = AgentDriftListDto::from(out); let v = serde_json::to_value(&dto).unwrap(); let arr = v.as_array().expect("transparent array"); assert_eq!(arr.len(), 1); assert_eq!(arr[0]["from"], 2u64); assert_eq!(arr[0]["to"], 5u64); } #[test] fn agent_drift_list_dto_empty() { let out = DetectAgentDriftOutput { drifts: vec![] }; let dto = AgentDriftListDto::from(out); let v = serde_json::to_value(&dto).unwrap(); assert!(v.as_array().unwrap().is_empty()); } // --------------------------------------------------------------------------- // SyncResultDto // --------------------------------------------------------------------------- #[test] fn sync_result_dto_synced_with_version() { let out = SyncAgentWithTemplateOutput { synced: true, version: Some(TemplateVersion(4)), }; let dto = SyncResultDto::from(out); let v = serde_json::to_value(&dto).unwrap(); assert_eq!(v["synced"], true); assert_eq!(v["version"], 4u64); } #[test] fn sync_result_dto_not_synced_version_null() { let out = SyncAgentWithTemplateOutput { synced: false, version: None, }; let dto = SyncResultDto::from(out); let v = serde_json::to_value(&dto).unwrap(); assert_eq!(v["synced"], false); assert!(v["version"].is_null(), "version should be null when None"); } // --------------------------------------------------------------------------- // Request DTO deserialisation // --------------------------------------------------------------------------- #[test] fn create_template_request_deserialises_camelcase() { let profile_id = Uuid::from_u128(20).to_string(); let raw = json!({ "name": "Backend Template", "content": "# My context", "defaultProfileId": profile_id }); let dto: CreateTemplateRequestDto = serde_json::from_value(raw).unwrap(); assert_eq!(dto.name, "Backend Template"); assert_eq!(dto.content, "# My context"); assert_eq!(dto.default_profile_id, profile_id); } #[test] fn update_template_request_deserialises_camelcase() { let template_id = Uuid::from_u128(30).to_string(); let raw = json!({ "templateId": template_id, "content": "# Updated content" }); let dto: UpdateTemplateRequestDto = serde_json::from_value(raw).unwrap(); assert_eq!(dto.template_id, template_id); assert_eq!(dto.content, "# Updated content"); } #[test] fn create_agent_from_template_request_deserialises_camelcase() { let project_id = Uuid::from_u128(40).to_string(); let template_id = Uuid::from_u128(41).to_string(); let raw = json!({ "projectId": project_id, "templateId": template_id, "name": "My Agent", "synchronized": true }); let dto: CreateAgentFromTemplateRequestDto = serde_json::from_value(raw).unwrap(); assert_eq!(dto.project_id, project_id); assert_eq!(dto.template_id, template_id); assert_eq!(dto.name.as_deref(), Some("My Agent")); assert!(dto.synchronized); } #[test] fn create_agent_from_template_request_name_defaults_to_none() { let raw = json!({ "projectId": Uuid::nil().to_string(), "templateId": Uuid::nil().to_string(), "synchronized": false }); let dto: CreateAgentFromTemplateRequestDto = serde_json::from_value(raw).unwrap(); assert!(dto.name.is_none()); } // --------------------------------------------------------------------------- // parse_template_id // --------------------------------------------------------------------------- #[test] fn parse_template_id_accepts_uuid() { let id = Uuid::from_u128(99); assert_eq!( parse_template_id(&id.to_string()).unwrap(), TemplateId::from_uuid(id) ); } #[test] fn parse_template_id_rejects_garbage() { let err = parse_template_id("INVALID").expect_err("garbage rejected"); assert_eq!(err.code, "INVALID"); } #[test] fn parse_template_id_rejects_empty() { let err = parse_template_id("").expect_err("empty string rejected"); assert_eq!(err.code, "INVALID"); }