gewe_http/contact/
wecom.rs

1use crate::client::GeweHttpClient;
2use gewe_core::{
3    AddWecomContactRequest, GetWecomContactDetailRequest, GetWecomContactDetailResponse, GeweError,
4    SearchWecomRequest, SearchWecomResponse, SyncWecomContactsRequest, SyncWecomContactsResponse,
5};
6use tracing::instrument;
7
8impl GeweHttpClient {
9    #[instrument(skip(self))]
10    pub async fn search_wecom_contact(
11        &self,
12        req: SearchWecomRequest<'_>,
13    ) -> Result<SearchWecomResponse, GeweError> {
14        let env = self
15            .post_api::<_, SearchWecomResponse>("gewe/v2/api/im/search", &req)
16            .await?;
17        env.data.ok_or(GeweError::MissingData)
18    }
19
20    #[instrument(skip(self))]
21    pub async fn sync_wecom_contacts(
22        &self,
23        req: SyncWecomContactsRequest<'_>,
24    ) -> Result<SyncWecomContactsResponse, GeweError> {
25        let env = self
26            .post_api::<_, SyncWecomContactsResponse>("gewe/v2/api/im/sync", &req)
27            .await?;
28        env.data.ok_or(GeweError::MissingData)
29    }
30
31    #[instrument(skip(self))]
32    pub async fn add_wecom_contact(
33        &self,
34        req: AddWecomContactRequest<'_>,
35    ) -> Result<(), GeweError> {
36        let _ = self.post_api::<_, ()>("gewe/v2/api/im/add", &req).await?;
37        Ok(())
38    }
39
40    #[instrument(skip(self))]
41    pub async fn get_wecom_contact_detail(
42        &self,
43        req: GetWecomContactDetailRequest<'_>,
44    ) -> Result<GetWecomContactDetailResponse, GeweError> {
45        let env = self
46            .post_api::<_, GetWecomContactDetailResponse>("gewe/v2/api/im/detail", &req)
47            .await?;
48        env.data.ok_or(GeweError::MissingData)
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use gewe_core::{
55        AddWecomContactRequest, GetWecomContactDetailRequest, SearchWecomRequest,
56        SyncWecomContactsRequest,
57    };
58
59    #[test]
60    fn test_search_wecom_request_serialization() {
61        let req = SearchWecomRequest {
62            app_id: "test_app",
63            scene: 1,
64            content: "search_keyword",
65        };
66
67        let json = serde_json::to_string(&req).unwrap();
68        assert!(json.contains("appId"));
69        assert!(json.contains("test_app"));
70        assert!(json.contains("scene"));
71        assert!(json.contains("content"));
72        assert!(json.contains("search_keyword"));
73    }
74
75    #[test]
76    fn test_search_wecom_request_field_names() {
77        let req = SearchWecomRequest {
78            app_id: "app123",
79            scene: 2,
80            content: "test",
81        };
82
83        let json = serde_json::to_string(&req).unwrap();
84        let value: serde_json::Value = serde_json::from_str(&json).unwrap();
85
86        // 验证字段名是 camelCase
87        assert!(value.get("appId").is_some());
88        assert!(value.get("scene").is_some());
89        assert!(value.get("content").is_some());
90    }
91
92    #[test]
93    fn test_sync_wecom_contacts_request_serialization() {
94        let req = SyncWecomContactsRequest {
95            app_id: "test_app_id",
96        };
97
98        let json = serde_json::to_string(&req).unwrap();
99        assert!(json.contains("appId"));
100        assert!(json.contains("test_app_id"));
101    }
102
103    #[test]
104    fn test_add_wecom_contact_request_serialization() {
105        let req = AddWecomContactRequest {
106            app_id: "my_app",
107            v3: "v3_value",
108            v4: "v4_value",
109        };
110
111        let json = serde_json::to_string(&req).unwrap();
112        assert!(json.contains("appId"));
113        assert!(json.contains("my_app"));
114        assert!(json.contains("v3"));
115        assert!(json.contains("v3_value"));
116        assert!(json.contains("v4"));
117        assert!(json.contains("v4_value"));
118    }
119
120    #[test]
121    fn test_get_wecom_contact_detail_request_serialization() {
122        let req = GetWecomContactDetailRequest {
123            app_id: "app_test",
124            to_user_name: "wecom_user_123",
125        };
126
127        let json = serde_json::to_string(&req).unwrap();
128        assert!(json.contains("appId"));
129        assert!(json.contains("app_test"));
130        assert!(json.contains("toUserName"));
131        assert!(json.contains("wecom_user_123"));
132    }
133
134    #[test]
135    fn test_get_wecom_contact_detail_request_field_names() {
136        let req = GetWecomContactDetailRequest {
137            app_id: "test",
138            to_user_name: "user",
139        };
140
141        let json = serde_json::to_string(&req).unwrap();
142        let value: serde_json::Value = serde_json::from_str(&json).unwrap();
143
144        // 验证字段名是 camelCase
145        assert!(value.get("appId").is_some());
146        assert!(value.get("toUserName").is_some());
147    }
148
149    #[test]
150    fn test_search_wecom_request_with_special_chars() {
151        let req = SearchWecomRequest {
152            app_id: "测试应用",
153            scene: 1,
154            content: "搜索关键词",
155        };
156
157        let json = serde_json::to_string(&req).unwrap();
158        // 验证 Unicode 字符能正确序列化
159        assert!(json.contains("测试应用") || json.contains("\\u"));
160        assert!(json.contains("搜索关键词") || json.contains("\\u"));
161    }
162
163    #[test]
164    fn test_search_wecom_request_clone() {
165        let req = SearchWecomRequest {
166            app_id: "app",
167            scene: 3,
168            content: "content",
169        };
170
171        let cloned = req.clone();
172        assert_eq!(req.app_id, cloned.app_id);
173        assert_eq!(req.scene, cloned.scene);
174        assert_eq!(req.content, cloned.content);
175    }
176
177    #[test]
178    fn test_sync_wecom_contacts_request_clone() {
179        let req = SyncWecomContactsRequest { app_id: "app123" };
180
181        let cloned = req.clone();
182        assert_eq!(req.app_id, cloned.app_id);
183    }
184}