//! L5 tests for the profile/first-run DTO (de)serialisation contract: camelCase //! on the wire, embedded [`AgentProfile`] shape preserved, and `parse_profile_id` //! error behaviour. use app_tauri_lib::dto::{ parse_delete_profile, parse_profile_id, ConfigureProfilesRequestDto, DetectProfilesRequestDto, DetectProfilesResponseDto, FirstRunStateDto, ProfileListDto, SaveProfileRequestDto, }; use application::{ ConfigureProfilesInput, DetectProfilesInput, DetectProfilesOutput, FirstRunStateOutput, ProfileAvailability, SaveProfileInput, }; use domain::ids::ProfileId; use domain::profile::{AgentProfile, ContextInjection}; use serde_json::json; use uuid::Uuid; fn profile(id: u128, name: &str, command: &str) -> AgentProfile { AgentProfile::new( ProfileId::from_uuid(Uuid::from_u128(id)), name, command, Vec::new(), ContextInjection::convention_file("CLAUDE.md").unwrap(), Some(format!("{command} --version")), "{projectRoot}", ) .unwrap() } #[test] fn profile_list_dto_serialises_camelcase_profiles() { let dto: ProfileListDto = vec![profile(1, "Claude", "claude")].into(); 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]["command"], "claude"); assert_eq!(arr[0]["cwdTemplate"], "{projectRoot}"); assert_eq!(arr[0]["contextInjection"]["strategy"], "conventionFile"); } #[test] fn detect_request_deserialises_candidates() { let raw = json!({ "candidates": [{ "id": Uuid::from_u128(1).to_string(), "name": "Claude", "command": "claude", "args": [], "contextInjection": { "strategy": "stdin" }, "detect": "claude --version", "cwdTemplate": "{projectRoot}" }] }); let dto: DetectProfilesRequestDto = serde_json::from_value(raw).unwrap(); let input: DetectProfilesInput = dto.into(); assert_eq!(input.candidates.len(), 1); assert_eq!(input.candidates[0].command, "claude"); } #[test] fn detect_response_serialises_available_flag_camelcase() { let out = DetectProfilesOutput { results: vec![ProfileAvailability { profile: profile(1, "Claude", "claude"), available: true, }], }; let dto: DetectProfilesResponseDto = out.into(); let v = serde_json::to_value(&dto).unwrap(); let arr = v.as_array().unwrap(); assert_eq!(arr[0]["available"], true); assert_eq!(arr[0]["profile"]["command"], "claude"); } #[test] fn save_request_deserialises_profile() { let raw = json!({ "profile": { "id": Uuid::from_u128(2).to_string(), "name": "Codex", "command": "codex", "args": ["--foo"], "contextInjection": { "strategy": "conventionFile", "target": "AGENTS.md" }, "detect": null, "cwdTemplate": "" } }); let dto: SaveProfileRequestDto = serde_json::from_value(raw).unwrap(); let input: SaveProfileInput = dto.into(); assert_eq!(input.profile.command, "codex"); assert_eq!(input.profile.args, vec!["--foo"]); assert!(input.profile.detect.is_none()); } #[test] fn configure_request_deserialises_profiles() { let raw = json!({ "profiles": [] }); let dto: ConfigureProfilesRequestDto = serde_json::from_value(raw).unwrap(); let input: ConfigureProfilesInput = dto.into(); assert!(input.profiles.is_empty()); } #[test] fn first_run_state_dto_serialises_camelcase() { let out = FirstRunStateOutput { is_first_run: true, reference_profiles: vec![profile(1, "Claude", "claude")], }; let dto: FirstRunStateDto = out.into(); let v = serde_json::to_value(&dto).unwrap(); assert_eq!(v["isFirstRun"], true); assert!(v.get("is_first_run").is_none(), "no snake_case leak"); assert_eq!(v["referenceProfiles"][0]["command"], "claude"); } #[test] fn parse_profile_id_accepts_uuid_and_rejects_garbage() { let id = Uuid::from_u128(7); assert_eq!( parse_profile_id(&id.to_string()).unwrap(), ProfileId::from_uuid(id) ); let err = parse_profile_id("not-a-uuid").expect_err("garbage rejected"); assert_eq!(err.code, "INVALID"); } #[test] fn parse_delete_profile_builds_input() { let id = Uuid::from_u128(9); let input = parse_delete_profile(&id.to_string()).unwrap(); assert_eq!(input.id, ProfileId::from_uuid(id)); }