Parcourir la source

feat(desktop-pet): API serde 模型层与解析测试

liaoxg il y a 1 jour
Parent
commit
c94b0a2587

+ 1 - 0
cfc-desktop-pet/src-tauri/src/api/mod.rs

@@ -0,0 +1 @@
+pub mod models;

+ 88 - 0
cfc-desktop-pet/src-tauri/src/api/models.rs

@@ -0,0 +1,88 @@
+use serde::Deserialize;
+
+/// CFC 统一响应包装 Result<T>(code=200 视为成功)
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ApiResult<T> {
+    pub code: i32,
+    pub message: Option<String>,
+    pub data: Option<T>,
+}
+
+impl<T> ApiResult<T> {
+    pub fn is_success(&self) -> bool {
+        self.code == 200
+    }
+}
+
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CfcTask {
+    pub id: i64,
+    pub title: Option<String>,
+    pub status: Option<String>, // PENDING / IN_PROGRESS / COMPLETED / REJECTED
+    pub due_date: Option<String>,
+}
+
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DimensionValue {
+    pub dimension: Option<String>,
+    pub value: i32,
+    pub color: Option<String>,
+}
+
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct EnergyOverview {
+    #[serde(default)]
+    pub dimensions: Vec<DimensionValue>,
+}
+
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DailyCheckin {
+    pub checked: bool,
+    #[serde(default)]
+    pub items: Vec<String>,
+}
+
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct MascotInfo {
+    pub mascot_id: Option<String>,
+    pub name: Option<String>,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn parses_task_with_camelcase_fields() {
+        let json = r#"{"id":3,"title":"喝水","status":"PENDING","dueDate":"2026-08-15"}"#;
+        let task: CfcTask = serde_json::from_str(json).unwrap();
+        assert_eq!(task.id, 3);
+        assert_eq!(task.status.as_deref(), Some("PENDING"));
+        assert_eq!(task.due_date.as_deref(), Some("2026-08-15"));
+    }
+
+    #[test]
+    fn parses_energy_overview_and_defaults_to_empty() {
+        let json = r##"{"dimensions":[{"dimension":"身","value":65,"color":"#FF8C42"}]}"##;
+        let e: EnergyOverview = serde_json::from_str(json).unwrap();
+        assert_eq!(e.dimensions.len(), 1);
+        assert_eq!(e.dimensions[0].value, 65);
+
+        let empty: EnergyOverview = serde_json::from_str(r#"{}"#).unwrap();
+        assert!(empty.dimensions.is_empty());
+    }
+
+    #[test]
+    fn parses_api_result_envelope() {
+        let json = r#"{"code":200,"message":"ok","data":{"mascotId":"xibao","name":"浠宝"}}"#;
+        let r: ApiResult<MascotInfo> = serde_json::from_str(json).unwrap();
+        assert!(r.is_success());
+        assert_eq!(r.data.unwrap().mascot_id.as_deref(), Some("xibao"));
+    }
+}

+ 2 - 0
cfc-desktop-pet/src-tauri/src/main.rs

@@ -1,5 +1,7 @@
 #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
 
+mod api;
+
 fn main() {
     tauri::Builder::default()
         .run(tauri::generate_context!())