open_lark/service/cloud_docs/drive/v1/
media.rs

1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    core::{
6        api_req::ApiRequest,
7        api_resp::{ApiResponseTrait, BaseResponse, BinaryResponse, ResponseFormat},
8        config::Config,
9        constants::AccessTokenType,
10        endpoints::cloud_docs::*,
11        http::Transport,
12        req_option::RequestOption,
13        validation::{validate_file_name, validate_upload_file, ValidateBuilder, ValidationResult},
14        SDKResult,
15    },
16    impl_executable_builder_owned,
17};
18use log;
19
20/// 素材服务
21pub struct MediaService {
22    config: Config,
23}
24
25impl MediaService {
26    pub fn new(config: Config) -> Self {
27        Self { config }
28    }
29
30    /// 创建上传素材Builder
31    pub fn upload_all_builder(&self) -> UploadMediaRequestBuilder {
32        UploadMediaRequestBuilder::default()
33    }
34
35    /// 使用Builder上传素材(带验证)
36    pub async fn upload_all_with_builder(
37        &self,
38        builder_result: SDKResult<UploadMediaRequest>,
39        option: Option<RequestOption>,
40    ) -> SDKResult<BaseResponse<UploadMediaRespData>> {
41        let request = builder_result?;
42        self.upload_all(request, option).await
43    }
44
45    /// 上传素材
46    ///
47    /// 该接口用于上传素材文件。
48    ///
49    /// <https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/media/upload_all>
50    pub async fn upload_all(
51        &self,
52        request: UploadMediaRequest,
53        option: Option<RequestOption>,
54    ) -> SDKResult<BaseResponse<UploadMediaRespData>> {
55        let mut api_req = request.api_req;
56        api_req.http_method = Method::POST;
57        api_req.api_path = DRIVE_V1_MEDIAS_UPLOAD_ALL.to_string();
58        api_req.supported_access_token_types = vec![AccessTokenType::User, AccessTokenType::Tenant];
59
60        let api_resp = Transport::request(api_req, &self.config, option).await?;
61        Ok(api_resp)
62    }
63
64    /// 分片上传素材-预上传
65    ///
66    /// 该接口用于分片上传的预上传步骤。
67    ///
68    /// <https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/media/upload_prepare>
69    pub async fn upload_prepare(
70        &self,
71        request: UploadPrepareRequest,
72        option: Option<RequestOption>,
73    ) -> SDKResult<BaseResponse<UploadPrepareRespData>> {
74        let api_req = ApiRequest {
75            http_method: Method::POST,
76            api_path: DRIVE_V1_MEDIAS_UPLOAD_PREPARE.to_string(),
77            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
78            body: serde_json::to_vec(&request)?,
79            ..Default::default()
80        };
81
82        let api_resp = Transport::request(api_req, &self.config, option).await?;
83        Ok(api_resp)
84    }
85
86    /// 分片上传素材-上传分片
87    ///
88    /// 该接口用于上传文件分片。
89    ///
90    /// <https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/media/upload_part>
91    pub async fn upload_part(
92        &self,
93        request: UploadPartRequest,
94        option: Option<RequestOption>,
95    ) -> SDKResult<BaseResponse<UploadPartRespData>> {
96        let mut api_req = request.api_req;
97        api_req.http_method = Method::POST;
98        api_req.api_path = DRIVE_V1_MEDIAS_UPLOAD_PART.to_string();
99        api_req.supported_access_token_types = vec![AccessTokenType::User, AccessTokenType::Tenant];
100
101        let api_resp = Transport::request(api_req, &self.config, option).await?;
102        Ok(api_resp)
103    }
104
105    /// 分片上传素材-完成上传
106    ///
107    /// 该接口用于完成分片上传。
108    ///
109    /// <https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/media/upload_finish>
110    pub async fn upload_finish(
111        &self,
112        request: UploadFinishRequest,
113        option: Option<RequestOption>,
114    ) -> SDKResult<BaseResponse<UploadFinishRespData>> {
115        let api_req = ApiRequest {
116            http_method: Method::POST,
117            api_path: DRIVE_V1_MEDIAS_UPLOAD_FINISH.to_string(),
118            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
119            body: serde_json::to_vec(&request)?,
120            ..Default::default()
121        };
122
123        let api_resp = Transport::request(api_req, &self.config, option).await?;
124        Ok(api_resp)
125    }
126
127    /// 下载素材
128    ///
129    /// 该接口用于下载素材文件。
130    ///
131    /// <https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/media/download>
132    pub async fn download(
133        &self,
134        request: DownloadMediaRequest,
135        option: Option<RequestOption>,
136    ) -> SDKResult<BaseResponse<BinaryResponse>> {
137        let api_req = ApiRequest {
138            http_method: Method::GET,
139            api_path: DRIVE_V1_MEDIAS_DOWNLOAD.replace("{}", &request.file_token),
140            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
141            ..Default::default()
142        };
143
144        let api_resp = Transport::request(api_req, &self.config, option).await?;
145        Ok(api_resp)
146    }
147
148    /// 获取素材临时下载链接
149    ///
150    /// 该接口用于获取素材的临时下载链接。
151    ///
152    /// <https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/media/batch_get_tmp_download_url>
153    pub async fn batch_get_tmp_download_url(
154        &self,
155        request: BatchGetTmpDownloadUrlRequest,
156        option: Option<RequestOption>,
157    ) -> SDKResult<BaseResponse<BatchGetTmpDownloadUrlRespData>> {
158        let mut api_req = ApiRequest {
159            http_method: Method::GET,
160            api_path: DRIVE_V1_MEDIAS_BATCH_GET_TMP_DOWNLOAD_URL.to_string(),
161            supported_access_token_types: vec![AccessTokenType::User, AccessTokenType::Tenant],
162            ..Default::default()
163        };
164
165        // 添加查询参数
166        let file_tokens = request.file_tokens.join(",");
167        api_req.query_params.insert("file_tokens", file_tokens);
168
169        let api_resp = Transport::request(api_req, &self.config, option).await?;
170        Ok(api_resp)
171    }
172}
173
174// === 数据结构定义 ===
175
176/// 上传素材请求参数
177#[derive(Debug, Clone, Default, Serialize, Deserialize)]
178pub struct UploadMediaRequest {
179    /// 请求体
180    #[serde(skip)]
181    pub api_req: ApiRequest,
182    /// 素材名称
183    file_name: String,
184    /// 父文件夹token
185    parent_token: String,
186    /// 文件大小
187    size: i32,
188    /// 校验和(可选)
189    checksum: Option<String>,
190}
191
192impl UploadMediaRequest {
193    pub fn builder() -> UploadMediaRequestBuilder {
194        UploadMediaRequestBuilder::default()
195    }
196}
197
198/// 上传素材请求构建器
199#[derive(Default)]
200pub struct UploadMediaRequestBuilder {
201    request: UploadMediaRequest,
202}
203
204impl UploadMediaRequestBuilder {
205    pub fn file_name(mut self, file_name: impl ToString) -> Self {
206        self.request.file_name = file_name.to_string();
207        self
208    }
209
210    pub fn parent_token(mut self, parent_token: impl ToString) -> Self {
211        self.request.parent_token = parent_token.to_string();
212        self
213    }
214
215    pub fn size(mut self, size: i32) -> Self {
216        self.request.size = size;
217        self
218    }
219
220    pub fn checksum(mut self, checksum: impl ToString) -> Self {
221        self.request.checksum = Some(checksum.to_string());
222        self
223    }
224
225    pub fn file(mut self, file: Vec<u8>) -> Self {
226        self.request.api_req.file = file;
227        self
228    }
229
230    pub fn build(mut self) -> UploadMediaRequest {
231        // 验证必填字段
232        if self.request.file_name.is_empty() {
233            log::error!("file_name is required for media upload");
234            return UploadMediaRequest {
235                api_req: ApiRequest {
236                    body: Vec::new(),
237                    ..Default::default()
238                },
239                ..self.request
240            };
241        }
242
243        if self.request.parent_token.is_empty() {
244            log::error!("parent_token is required for media upload");
245            return UploadMediaRequest {
246                api_req: ApiRequest {
247                    body: Vec::new(),
248                    ..Default::default()
249                },
250                ..self.request
251            };
252        }
253
254        if self.request.size <= 0 {
255            log::error!("file size must be greater than 0");
256            return UploadMediaRequest {
257                api_req: ApiRequest {
258                    body: Vec::new(),
259                    ..Default::default()
260                },
261                ..self.request
262            };
263        }
264
265        // 验证文件名
266        let (_, name_result) = validate_file_name(&self.request.file_name);
267        if !name_result.is_valid() {
268            log::error!(
269                "Invalid file_name: {}",
270                name_result.error().unwrap_or("unknown error")
271            );
272            return UploadMediaRequest {
273                api_req: ApiRequest {
274                    body: Vec::new(),
275                    ..Default::default()
276                },
277                ..self.request
278            };
279        }
280
281        // 验证文件数据(如果有)
282        if !self.request.api_req.file.is_empty() {
283            let upload_result =
284                validate_upload_file(&self.request.api_req.file, &self.request.file_name, false);
285            if !upload_result.is_valid() {
286                log::error!(
287                    "File validation failed: {}",
288                    upload_result.error().unwrap_or("unknown error")
289                );
290                return UploadMediaRequest {
291                    api_req: ApiRequest {
292                        body: Vec::new(),
293                        ..Default::default()
294                    },
295                    ..self.request
296                };
297            }
298        }
299
300        self.request.api_req.body = match serde_json::to_vec(&self.request) {
301            Ok(body) => body,
302            Err(e) => {
303                log::error!("Failed to serialize upload media request: {}", e);
304                return UploadMediaRequest {
305                    api_req: ApiRequest {
306                        body: Vec::new(),
307                        ..Default::default()
308                    },
309                    ..self.request
310                };
311            }
312        };
313        self.request
314    }
315}
316
317impl ValidateBuilder for UploadMediaRequestBuilder {
318    fn validate(&self) -> ValidationResult {
319        // 验证必填字段
320        if self.request.file_name.is_empty() {
321            return ValidationResult::Invalid("file_name is required".to_string());
322        }
323
324        if self.request.parent_token.is_empty() {
325            return ValidationResult::Invalid("parent_token is required".to_string());
326        }
327
328        if self.request.size <= 0 {
329            return ValidationResult::Invalid("file size must be greater than 0".to_string());
330        }
331
332        // 验证文件名
333        let (_, name_result) = validate_file_name(&self.request.file_name);
334        if !name_result.is_valid() {
335            return name_result;
336        }
337
338        // 验证文件数据(如果有)
339        if !self.request.api_req.file.is_empty() {
340            validate_upload_file(&self.request.api_req.file, &self.request.file_name, false)
341        } else {
342            ValidationResult::Valid
343        }
344    }
345}
346
347/// 上传素材响应数据
348#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct UploadMediaRespData {
350    /// 素材token
351    pub file_token: String,
352}
353
354impl ApiResponseTrait for UploadMediaRespData {
355    fn data_format() -> ResponseFormat {
356        ResponseFormat::Data
357    }
358}
359
360/// 分片上传预上传请求参数
361#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct UploadPrepareRequest {
363    /// 文件名称
364    pub file_name: String,
365    /// 父文件夹token
366    pub parent_token: String,
367    /// 文件大小
368    pub size: i64,
369    /// 分片大小(可选)
370    pub block_size: Option<i32>,
371    /// 文件校验和(可选)
372    pub checksum: Option<String>,
373}
374
375impl UploadPrepareRequest {
376    pub fn new(file_name: impl Into<String>, parent_token: impl Into<String>, size: i64) -> Self {
377        Self {
378            file_name: file_name.into(),
379            parent_token: parent_token.into(),
380            size,
381            block_size: None,
382            checksum: None,
383        }
384    }
385}
386
387/// 分片上传预上传响应数据
388#[derive(Debug, Clone, Serialize, Deserialize)]
389pub struct UploadPrepareRespData {
390    /// 上传事务ID
391    pub upload_id: String,
392    /// 分片大小
393    pub block_size: i32,
394    /// 分片数量
395    pub block_num: i32,
396}
397
398impl ApiResponseTrait for UploadPrepareRespData {
399    fn data_format() -> ResponseFormat {
400        ResponseFormat::Data
401    }
402}
403
404/// 上传分片请求参数
405#[derive(Debug, Clone, Default, Serialize, Deserialize)]
406pub struct UploadPartRequest {
407    /// 请求体
408    #[serde(skip)]
409    pub api_req: ApiRequest,
410    /// 上传事务ID
411    upload_id: String,
412    /// 分片序号
413    seq: i32,
414    /// 分片大小
415    size: i32,
416    /// 分片校验和(可选)
417    checksum: Option<String>,
418}
419
420impl UploadPartRequest {
421    pub fn builder() -> UploadPartRequestBuilder {
422        UploadPartRequestBuilder::default()
423    }
424}
425
426/// 上传分片请求构建器
427#[derive(Default)]
428pub struct UploadPartRequestBuilder {
429    request: UploadPartRequest,
430}
431
432impl UploadPartRequestBuilder {
433    pub fn upload_id(mut self, upload_id: impl ToString) -> Self {
434        self.request.upload_id = upload_id.to_string();
435        self
436    }
437
438    pub fn seq(mut self, seq: i32) -> Self {
439        self.request.seq = seq;
440        self
441    }
442
443    pub fn size(mut self, size: i32) -> Self {
444        self.request.size = size;
445        self
446    }
447
448    pub fn checksum(mut self, checksum: impl ToString) -> Self {
449        self.request.checksum = Some(checksum.to_string());
450        self
451    }
452
453    pub fn file_chunk(mut self, chunk: Vec<u8>) -> Self {
454        self.request.api_req.file = chunk;
455        self
456    }
457
458    pub fn build(mut self) -> UploadPartRequest {
459        self.request.api_req.body = serde_json::to_vec(&self.request).unwrap();
460        self.request
461    }
462}
463
464/// 上传分片响应数据
465#[derive(Debug, Clone, Serialize, Deserialize)]
466pub struct UploadPartRespData {
467    /// 分片ETag
468    pub etag: String,
469}
470
471impl ApiResponseTrait for UploadPartRespData {
472    fn data_format() -> ResponseFormat {
473        ResponseFormat::Data
474    }
475}
476
477/// 完成上传请求参数
478#[derive(Debug, Clone, Serialize, Deserialize)]
479pub struct UploadFinishRequest {
480    /// 上传事务ID
481    pub upload_id: String,
482    /// 分片信息列表
483    pub block_infos: Vec<BlockInfo>,
484}
485
486#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
487pub struct BlockInfo {
488    /// 分片ETag
489    pub etag: String,
490    /// 分片序号
491    pub seq: i32,
492}
493
494impl UploadFinishRequest {
495    pub fn new(upload_id: impl Into<String>, block_infos: Vec<BlockInfo>) -> Self {
496        Self {
497            upload_id: upload_id.into(),
498            block_infos,
499        }
500    }
501}
502
503/// 完成上传响应数据
504#[derive(Debug, Clone, Serialize, Deserialize)]
505pub struct UploadFinishRespData {
506    /// 素材token
507    pub file_token: String,
508}
509
510impl ApiResponseTrait for UploadFinishRespData {
511    fn data_format() -> ResponseFormat {
512        ResponseFormat::Data
513    }
514}
515
516/// 下载素材请求参数
517#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct DownloadMediaRequest {
519    /// 素材token
520    pub file_token: String,
521}
522
523impl DownloadMediaRequest {
524    pub fn new(file_token: impl Into<String>) -> Self {
525        Self {
526            file_token: file_token.into(),
527        }
528    }
529}
530
531/// 批量获取临时下载链接请求参数
532#[derive(Debug, Clone, Serialize, Deserialize)]
533pub struct BatchGetTmpDownloadUrlRequest {
534    /// 素材token列表
535    pub file_tokens: Vec<String>,
536}
537
538impl BatchGetTmpDownloadUrlRequest {
539    pub fn new(file_tokens: Vec<String>) -> Self {
540        Self { file_tokens }
541    }
542}
543
544/// 批量获取临时下载链接响应数据
545#[derive(Debug, Clone, Serialize, Deserialize)]
546pub struct BatchGetTmpDownloadUrlRespData {
547    /// 临时下载链接信息
548    pub tmp_download_urls: Vec<TmpDownloadUrl>,
549}
550
551#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
552pub struct TmpDownloadUrl {
553    /// 素材token
554    pub file_token: String,
555    /// 临时下载链接
556    pub tmp_download_url: String,
557}
558
559impl ApiResponseTrait for BatchGetTmpDownloadUrlRespData {
560    fn data_format() -> ResponseFormat {
561        ResponseFormat::Data
562    }
563}
564
565// === 宏实现 ===
566
567impl_executable_builder_owned!(
568    UploadMediaRequestBuilder,
569    MediaService,
570    UploadMediaRequest,
571    BaseResponse<UploadMediaRespData>,
572    upload_all
573);
574
575impl_executable_builder_owned!(
576    UploadPartRequestBuilder,
577    MediaService,
578    UploadPartRequest,
579    BaseResponse<UploadPartRespData>,
580    upload_part
581);
582
583#[cfg(test)]
584mod tests {
585    use super::*;
586    use rstest::*;
587
588    fn mock_config() -> Config {
589        Config::builder()
590            .app_id("test_app_id")
591            .app_secret("test_app_secret")
592            .build()
593    }
594
595    // === MediaService Tests ===
596
597    #[test]
598    fn test_media_service_new() {
599        let config = mock_config();
600        let service = MediaService::new(config.clone());
601        assert_eq!(service.config.app_id, config.app_id);
602    }
603
604    #[test]
605    fn test_upload_all_builder() {
606        let service = MediaService::new(mock_config());
607        let builder = service.upload_all_builder();
608        assert_eq!(builder.request.file_name, "");
609        assert_eq!(builder.request.parent_token, "");
610        assert_eq!(builder.request.size, 0);
611    }
612
613    // === UploadMediaRequest Tests ===
614
615    #[test]
616    fn test_upload_media_request_builder() {
617        let request = UploadMediaRequest::builder()
618            .file_name("test.pdf")
619            .parent_token("parent123")
620            .size(1024)
621            .build();
622        assert_eq!(request.file_name, "test.pdf");
623        assert_eq!(request.parent_token, "parent123");
624        assert_eq!(request.size, 1024);
625        assert!(request.checksum.is_none());
626    }
627
628    // === UploadMediaRequestBuilder Tests ===
629
630    #[test]
631    fn test_upload_media_builder_basic() {
632        let builder = UploadMediaRequestBuilder::default()
633            .file_name("test_file.txt")
634            .parent_token("parent_token_123")
635            .size(1024);
636
637        assert_eq!(builder.request.file_name, "test_file.txt");
638        assert_eq!(builder.request.parent_token, "parent_token_123");
639        assert_eq!(builder.request.size, 1024);
640    }
641
642    #[test]
643    fn test_upload_media_builder_with_checksum() {
644        let builder = UploadMediaRequestBuilder::default()
645            .file_name("image.png")
646            .parent_token("images_folder")
647            .size(512000)
648            .checksum("md5:hash123");
649
650        assert_eq!(builder.request.file_name, "image.png");
651        assert_eq!(builder.request.checksum, Some("md5:hash123".to_string()));
652    }
653
654    #[test]
655    fn test_upload_media_builder_with_file_data() {
656        let file_data = vec![0x89, 0x50, 0x4E, 0x47]; // PNG header
657        let builder = UploadMediaRequestBuilder::default()
658            .file_name("test.png")
659            .parent_token("parent")
660            .size(4)
661            .file(file_data.clone());
662
663        assert_eq!(builder.request.api_req.file, file_data);
664    }
665
666    #[test]
667    fn test_upload_media_builder_build_valid() {
668        let file_data = b"Hello World".to_vec();
669        let request = UploadMediaRequestBuilder::default()
670            .file_name("hello.txt")
671            .parent_token("parent123")
672            .size(11)
673            .file(file_data)
674            .build();
675
676        assert_eq!(request.file_name, "hello.txt");
677        assert_eq!(request.parent_token, "parent123");
678        assert_eq!(request.size, 11);
679        assert!(!request.api_req.body.is_empty());
680    }
681
682    #[test]
683    fn test_upload_media_builder_build_missing_filename() {
684        let request = UploadMediaRequestBuilder::default()
685            .parent_token("parent123")
686            .size(100)
687            .build();
688
689        // Should return invalid request due to missing filename
690        assert!(request.api_req.body.is_empty());
691    }
692
693    #[test]
694    fn test_upload_media_builder_build_missing_parent_token() {
695        let request = UploadMediaRequestBuilder::default()
696            .file_name("test.txt")
697            .size(100)
698            .build();
699
700        // Should return invalid request due to missing parent_token
701        assert!(request.api_req.body.is_empty());
702    }
703
704    #[test]
705    fn test_upload_media_builder_build_invalid_size() {
706        let request = UploadMediaRequestBuilder::default()
707            .file_name("test.txt")
708            .parent_token("parent123")
709            .size(0) // Invalid size
710            .build();
711
712        // Should return invalid request due to invalid size
713        assert!(request.api_req.body.is_empty());
714    }
715
716    #[test]
717    fn test_upload_media_builder_build_negative_size() {
718        let request = UploadMediaRequestBuilder::default()
719            .file_name("test.txt")
720            .parent_token("parent123")
721            .size(-1) // Negative size
722            .build();
723
724        // Should return invalid request due to negative size
725        assert!(request.api_req.body.is_empty());
726    }
727
728    // === UploadPrepareRequest Tests ===
729
730    #[test]
731    fn test_upload_prepare_request_new() {
732        let request = UploadPrepareRequest::new("large_file.zip", "uploads", 10485760);
733        assert_eq!(request.file_name, "large_file.zip");
734        assert_eq!(request.parent_token, "uploads");
735        assert_eq!(request.size, 10485760);
736        assert!(request.block_size.is_none());
737    }
738
739    #[test]
740    fn test_upload_prepare_request_with_block_size() {
741        let mut request = UploadPrepareRequest::new("video.mp4", "videos", 104857600);
742        request.block_size = Some(1048576); // 1MB blocks
743        assert_eq!(request.file_name, "video.mp4");
744        assert_eq!(request.block_size, Some(1048576));
745    }
746
747    // === UploadPartRequest Tests ===
748
749    #[test]
750    fn test_upload_part_request_builder() {
751        let request = UploadPartRequest::builder()
752            .upload_id("upload123")
753            .seq(1)
754            .build();
755        assert_eq!(request.upload_id, "upload123");
756        assert_eq!(request.seq, 1);
757        assert_eq!(request.api_req.file.len(), 0);
758    }
759
760    #[test]
761    fn test_upload_part_request_builder_with_file() {
762        let _file_chunk = [0x01, 0x02, 0x03, 0x04];
763        let request = UploadPartRequest::builder()
764            .upload_id("upload456")
765            .seq(2)
766            .size(4)
767            .checksum("crc32:abc123")
768            .build();
769
770        assert_eq!(request.upload_id, "upload456");
771        assert_eq!(request.seq, 2);
772        assert_eq!(request.size, 4);
773        assert_eq!(request.checksum, Some("crc32:abc123".to_string()));
774    }
775
776    #[test]
777    fn test_upload_part_request_builder_minimal() {
778        let request = UploadPartRequestBuilder::default()
779            .upload_id("minimal")
780            .seq(1)
781            .build();
782
783        assert_eq!(request.upload_id, "minimal");
784        assert_eq!(request.seq, 1);
785        assert_eq!(request.size, 0);
786        assert!(request.checksum.is_none());
787    }
788
789    // === UploadFinishRequest Tests ===
790
791    #[test]
792    fn test_upload_finish_request_new() {
793        let block_infos = vec![
794            BlockInfo {
795                etag: "hash1".to_string(),
796                seq: 1,
797            },
798            BlockInfo {
799                etag: "hash2".to_string(),
800                seq: 2,
801            },
802        ];
803        let request = UploadFinishRequest::new("upload789", block_infos.clone());
804        assert_eq!(request.upload_id, "upload789");
805        assert_eq!(request.block_infos, block_infos);
806    }
807
808    // === DownloadMediaRequest Tests ===
809
810    #[test]
811    fn test_download_media_request_new() {
812        let request = DownloadMediaRequest::new("media_token_abc");
813        assert_eq!(request.file_token, "media_token_abc");
814    }
815
816    // === BatchGetTmpDownloadUrlRequest Tests ===
817
818    #[test]
819    fn test_batch_get_tmp_download_url_request_new() {
820        let request = BatchGetTmpDownloadUrlRequest::new(vec!["temp_token_xyz".to_string()]);
821        assert_eq!(request.file_tokens, vec!["temp_token_xyz".to_string()]);
822    }
823
824    // === Response Data Structure Tests ===
825
826    #[test]
827    fn test_upload_media_resp_data() {
828        let data = UploadMediaRespData {
829            file_token: "uploaded_file_token".to_string(),
830        };
831        assert_eq!(data.file_token, "uploaded_file_token");
832    }
833
834    #[test]
835    fn test_upload_prepare_resp_data() {
836        let data = UploadPrepareRespData {
837            upload_id: "prepared_upload_123".to_string(),
838            block_size: 1048576,
839            block_num: 10,
840        };
841        assert_eq!(data.upload_id, "prepared_upload_123");
842        assert_eq!(data.block_size, 1048576);
843        assert_eq!(data.block_num, 10);
844    }
845
846    #[test]
847    fn test_upload_part_resp_data() {
848        let data = UploadPartRespData {
849            etag: "part_etag_456".to_string(),
850        };
851        assert_eq!(data.etag, "part_etag_456");
852    }
853
854    #[test]
855    fn test_upload_finish_resp_data() {
856        let data = UploadFinishRespData {
857            file_token: "finished_file_token".to_string(),
858        };
859        assert_eq!(data.file_token, "finished_file_token");
860    }
861
862    #[test]
863    fn test_batch_get_tmp_download_url_resp_data() {
864        let data = BatchGetTmpDownloadUrlRespData {
865            tmp_download_urls: vec![TmpDownloadUrl {
866                file_token: "token123".to_string(),
867                tmp_download_url: "https://temp.example.com/download/abc123".to_string(),
868            }],
869        };
870        assert_eq!(
871            data.tmp_download_urls[0].tmp_download_url,
872            "https://temp.example.com/download/abc123"
873        );
874    }
875
876    // === Serialization Tests ===
877
878    #[rstest]
879    #[case("upload_media_request")]
880    #[case("upload_prepare_request")]
881    #[case("upload_part_request")]
882    #[case("upload_finish_request")]
883    #[case("download_media_request")]
884    #[case("batch_get_tmp_download_url_request")]
885    fn test_request_serialization_roundtrip(#[case] request_type: &str) {
886        match request_type {
887            "upload_media_request" => {
888                let original = UploadMediaRequest::builder()
889                    .file_name("test.txt")
890                    .parent_token("parent123")
891                    .size(100)
892                    .checksum("sha256:test")
893                    .build();
894                let json = serde_json::to_string(&original).unwrap();
895                let deserialized: UploadMediaRequest = serde_json::from_str(&json).unwrap();
896                assert_eq!(original.file_name, deserialized.file_name);
897                assert_eq!(original.parent_token, deserialized.parent_token);
898                assert_eq!(original.size, deserialized.size);
899                assert_eq!(original.checksum, deserialized.checksum);
900            }
901            "upload_prepare_request" => {
902                let mut original = UploadPrepareRequest::new("large.zip", "uploads", 1000000);
903                original.block_size = Some(4096);
904                let json = serde_json::to_string(&original).unwrap();
905                let deserialized: UploadPrepareRequest = serde_json::from_str(&json).unwrap();
906                assert_eq!(original.file_name, deserialized.file_name);
907                assert_eq!(original.parent_token, deserialized.parent_token);
908                assert_eq!(original.size, deserialized.size);
909                assert_eq!(original.block_size, deserialized.block_size);
910            }
911            "upload_part_request" => {
912                let original = UploadPartRequest::builder()
913                    .upload_id("test_upload")
914                    .seq(1)
915                    .size(1024)
916                    .checksum("test_checksum")
917                    .build();
918                let json = serde_json::to_string(&original).unwrap();
919                let deserialized: UploadPartRequest = serde_json::from_str(&json).unwrap();
920                assert_eq!(original.upload_id, deserialized.upload_id);
921                assert_eq!(original.seq, deserialized.seq);
922                assert_eq!(original.size, deserialized.size);
923                assert_eq!(original.checksum, deserialized.checksum);
924            }
925            "upload_finish_request" => {
926                let checksums = vec!["hash1".to_string(), "hash2".to_string()];
927                let block_infos: Vec<BlockInfo> = checksums
928                    .into_iter()
929                    .enumerate()
930                    .map(|(i, etag)| BlockInfo {
931                        etag,
932                        seq: i as i32,
933                    })
934                    .collect();
935                let original = UploadFinishRequest::new("upload123", block_infos.clone());
936                let json = serde_json::to_string(&original).unwrap();
937                let deserialized: UploadFinishRequest = serde_json::from_str(&json).unwrap();
938                assert_eq!(original.upload_id, deserialized.upload_id);
939                assert_eq!(original.block_infos, deserialized.block_infos);
940            }
941            "download_media_request" => {
942                let original = DownloadMediaRequest::new("download_token");
943                let json = serde_json::to_string(&original).unwrap();
944                let deserialized: DownloadMediaRequest = serde_json::from_str(&json).unwrap();
945                assert_eq!(original.file_token, deserialized.file_token);
946            }
947            "batch_get_tmp_download_url_request" => {
948                let original = BatchGetTmpDownloadUrlRequest::new(vec!["temp_token".to_string()]);
949                let json = serde_json::to_string(&original).unwrap();
950                let deserialized: BatchGetTmpDownloadUrlRequest =
951                    serde_json::from_str(&json).unwrap();
952                assert_eq!(original.file_tokens, deserialized.file_tokens);
953            }
954            _ => panic!("Unknown request type: {}", request_type),
955        }
956    }
957
958    #[rstest]
959    #[case("upload_media_resp")]
960    #[case("upload_prepare_resp")]
961    #[case("upload_part_resp")]
962    #[case("upload_finish_resp")]
963    #[case("batch_get_tmp_download_url_resp")]
964    fn test_response_serialization_roundtrip(#[case] response_type: &str) {
965        match response_type {
966            "upload_media_resp" => {
967                let original = UploadMediaRespData {
968                    file_token: "response_token".to_string(),
969                };
970                let json = serde_json::to_string(&original).unwrap();
971                let deserialized: UploadMediaRespData = serde_json::from_str(&json).unwrap();
972                assert_eq!(original.file_token, deserialized.file_token);
973            }
974            "upload_prepare_resp" => {
975                let original = UploadPrepareRespData {
976                    upload_id: "prepared_id".to_string(),
977                    block_size: 4096,
978                    block_num: 5,
979                };
980                let json = serde_json::to_string(&original).unwrap();
981                let deserialized: UploadPrepareRespData = serde_json::from_str(&json).unwrap();
982                assert_eq!(original.upload_id, deserialized.upload_id);
983                assert_eq!(original.block_size, deserialized.block_size);
984                assert_eq!(original.block_num, deserialized.block_num);
985            }
986            "upload_part_resp" => {
987                let original = UploadPartRespData {
988                    etag: "part_etag".to_string(),
989                };
990                let json = serde_json::to_string(&original).unwrap();
991                let deserialized: UploadPartRespData = serde_json::from_str(&json).unwrap();
992                assert_eq!(original.etag, deserialized.etag);
993            }
994            "upload_finish_resp" => {
995                let original = UploadFinishRespData {
996                    file_token: "final_token".to_string(),
997                };
998                let json = serde_json::to_string(&original).unwrap();
999                let deserialized: UploadFinishRespData = serde_json::from_str(&json).unwrap();
1000                assert_eq!(original.file_token, deserialized.file_token);
1001            }
1002            "batch_get_tmp_download_url_resp" => {
1003                let original = BatchGetTmpDownloadUrlRespData {
1004                    tmp_download_urls: vec![TmpDownloadUrl {
1005                        file_token: "token123".to_string(),
1006                        tmp_download_url: "https://example.com/temp".to_string(),
1007                    }],
1008                };
1009                let json = serde_json::to_string(&original).unwrap();
1010                let deserialized: BatchGetTmpDownloadUrlRespData =
1011                    serde_json::from_str(&json).unwrap();
1012                assert_eq!(original.tmp_download_urls, deserialized.tmp_download_urls);
1013            }
1014            _ => panic!("Unknown response type: {}", response_type),
1015        }
1016    }
1017
1018    // === ApiResponseTrait Tests ===
1019
1020    #[rstest]
1021    #[case("UploadMediaRespData")]
1022    #[case("UploadPrepareRespData")]
1023    #[case("UploadPartRespData")]
1024    #[case("UploadFinishRespData")]
1025    #[case("BatchGetTmpDownloadUrlRespData")]
1026    fn test_api_response_trait(#[case] response_type: &str) {
1027        let format = match response_type {
1028            "UploadMediaRespData" => UploadMediaRespData::data_format(),
1029            "UploadPrepareRespData" => UploadPrepareRespData::data_format(),
1030            "UploadPartRespData" => UploadPartRespData::data_format(),
1031            "UploadFinishRespData" => UploadFinishRespData::data_format(),
1032            "BatchGetTmpDownloadUrlRespData" => BatchGetTmpDownloadUrlRespData::data_format(),
1033            _ => panic!("Unknown response type: {}", response_type),
1034        };
1035        assert_eq!(format, ResponseFormat::Data);
1036    }
1037
1038    // === Validation Tests ===
1039
1040    #[test]
1041    fn test_upload_media_builder_validate_success() {
1042        let builder = UploadMediaRequestBuilder::default()
1043            .file_name("valid.txt")
1044            .parent_token("valid_parent")
1045            .size(100);
1046
1047        let result = builder.validate();
1048        assert!(result.is_valid());
1049    }
1050
1051    #[test]
1052    fn test_upload_media_builder_validate_missing_filename() {
1053        let builder = UploadMediaRequestBuilder::default()
1054            .parent_token("valid_parent")
1055            .size(100);
1056
1057        let result = builder.validate();
1058        assert!(!result.is_valid());
1059        assert!(result.error().unwrap().contains("file_name is required"));
1060    }
1061
1062    #[test]
1063    fn test_upload_media_builder_validate_missing_parent() {
1064        let builder = UploadMediaRequestBuilder::default()
1065            .file_name("valid.txt")
1066            .size(100);
1067
1068        let result = builder.validate();
1069        assert!(!result.is_valid());
1070        assert!(result.error().unwrap().contains("parent_token is required"));
1071    }
1072
1073    #[test]
1074    fn test_upload_media_builder_validate_invalid_size() {
1075        let builder = UploadMediaRequestBuilder::default()
1076            .file_name("valid.txt")
1077            .parent_token("valid_parent")
1078            .size(0);
1079
1080        let result = builder.validate();
1081        assert!(!result.is_valid());
1082        assert!(result
1083            .error()
1084            .unwrap()
1085            .contains("file size must be greater than 0"));
1086    }
1087
1088    // === Edge Cases and Boundary Tests ===
1089
1090    #[test]
1091    fn test_upload_large_file_size() {
1092        let request = UploadPrepareRequest::new("huge_file.bin", "storage", i64::MAX);
1093        assert_eq!(request.size, i64::MAX);
1094    }
1095
1096    #[test]
1097    fn test_upload_zero_block_size() {
1098        let mut request = UploadPrepareRequest::new("test.txt", "parent", 1000);
1099        request.block_size = Some(0);
1100        assert_eq!(request.block_size, Some(0));
1101    }
1102
1103    #[test]
1104    fn test_upload_part_maximum_sequence() {
1105        let request = UploadPartRequest::builder()
1106            .upload_id("upload123")
1107            .seq(i32::MAX)
1108            .size(0)
1109            .build();
1110        assert_eq!(request.seq, i32::MAX);
1111    }
1112
1113    #[test]
1114    fn test_unicode_filename() {
1115        let unicode_name = "测试文件🎉.docx";
1116        let request = UploadMediaRequest::builder()
1117            .file_name(unicode_name)
1118            .parent_token("folder")
1119            .size(1024)
1120            .build();
1121        assert_eq!(request.file_name, unicode_name);
1122    }
1123
1124    #[test]
1125    fn test_special_characters_in_filename() {
1126        let special_name = "file with spaces & symbols @#$.txt";
1127        let request = UploadMediaRequest::builder()
1128            .file_name(special_name)
1129            .parent_token("parent")
1130            .size(512)
1131            .build();
1132        assert_eq!(request.file_name, special_name);
1133    }
1134
1135    #[test]
1136    fn test_empty_block_checksums() {
1137        let empty_block_infos: Vec<BlockInfo> = vec![];
1138        let request = UploadFinishRequest::new("upload456", empty_block_infos);
1139        assert_eq!(request.block_infos.len(), 0);
1140    }
1141
1142    #[test]
1143    fn test_many_block_checksums() {
1144        let many_checksums: Vec<String> = (0..1000).map(|i| format!("hash_{}", i)).collect();
1145        let many_block_infos: Vec<BlockInfo> = many_checksums
1146            .into_iter()
1147            .enumerate()
1148            .map(|(i, etag)| BlockInfo {
1149                etag,
1150                seq: i as i32,
1151            })
1152            .collect();
1153        let request = UploadFinishRequest::new("upload789", many_block_infos.clone());
1154        assert_eq!(request.block_infos.len(), 1000);
1155        assert_eq!(request.block_infos[0].etag, "hash_0");
1156        assert_eq!(request.block_infos[999].etag, "hash_999");
1157    }
1158
1159    #[test]
1160    fn test_very_long_token() {
1161        let long_token = "a".repeat(1000);
1162        let request = DownloadMediaRequest::new(&long_token);
1163        assert_eq!(request.file_token, long_token);
1164    }
1165
1166    #[test]
1167    fn test_very_long_url() {
1168        let long_url = format!("https://example.com/{}", "a".repeat(1000));
1169        let data = BatchGetTmpDownloadUrlRespData {
1170            tmp_download_urls: vec![TmpDownloadUrl {
1171                file_token: "token123".to_string(),
1172                tmp_download_url: long_url.clone(),
1173            }],
1174        };
1175        assert_eq!(data.tmp_download_urls[0].tmp_download_url, long_url);
1176    }
1177
1178    // === Clone and Debug Tests ===
1179
1180    #[test]
1181    fn test_request_cloning() {
1182        let original = UploadMediaRequest::builder()
1183            .file_name("clone_test.txt")
1184            .parent_token("parent")
1185            .size(256)
1186            .checksum("test_hash")
1187            .build();
1188        let cloned = original.clone();
1189
1190        assert_eq!(original.file_name, cloned.file_name);
1191        assert_eq!(original.parent_token, cloned.parent_token);
1192        assert_eq!(original.size, cloned.size);
1193        assert_eq!(original.checksum, cloned.checksum);
1194    }
1195
1196    #[test]
1197    fn test_response_debug() {
1198        let data = UploadPrepareRespData {
1199            upload_id: "debug_test".to_string(),
1200            block_size: 4096,
1201            block_num: 2,
1202        };
1203        let debug_str = format!("{:?}", data);
1204        assert!(debug_str.contains("debug_test"));
1205        assert!(debug_str.contains("4096"));
1206        assert!(debug_str.contains("2"));
1207    }
1208
1209    // === Builder Chain Tests ===
1210
1211    #[test]
1212    fn test_upload_part_builder_method_chaining() {
1213        let chunk_data = vec![0xFF; 1024];
1214        let request = UploadPartRequest::builder()
1215            .upload_id("chain_test")
1216            .seq(3)
1217            .size(1024)
1218            .checksum("md5:abc123")
1219            .file_chunk(chunk_data.clone())
1220            .build();
1221
1222        assert_eq!(request.upload_id, "chain_test");
1223        assert_eq!(request.seq, 3);
1224        assert_eq!(request.size, 1024);
1225        assert_eq!(request.checksum, Some("md5:abc123".to_string()));
1226        assert_eq!(request.api_req.file, chunk_data);
1227    }
1228
1229    #[test]
1230    fn test_upload_part_builder_overwrite_values() {
1231        let request = UploadPartRequest::builder()
1232            .upload_id("first")
1233            .upload_id("second") // Overwrite
1234            .seq(1)
1235            .seq(5) // Overwrite
1236            .build();
1237
1238        assert_eq!(request.upload_id, "second");
1239        assert_eq!(request.seq, 5);
1240    }
1241
1242    #[test]
1243    fn test_default_values() {
1244        let request = UploadPartRequestBuilder::default().build();
1245        assert_eq!(request.upload_id, "");
1246        assert_eq!(request.seq, 0);
1247        assert_eq!(request.size, 0);
1248        assert!(request.checksum.is_none());
1249        assert!(request.api_req.file.is_empty());
1250    }
1251}