1use crate::error::Result;
6use crate::http::HttpClient;
7use crate::types::session::{
8 CreateSessionRequest, RevertRequest, Session, SessionCreateOptions, SessionDiff, SessionStatus,
9 ShareInfo, SummarizeRequest, TodoItem, UpdateSessionRequest,
10};
11use reqwest::Method;
12
13#[derive(Clone)]
15pub struct SessionsApi {
16 http: HttpClient,
17}
18
19impl SessionsApi {
20 pub fn new(http: HttpClient) -> Self {
22 Self { http }
23 }
24
25 pub async fn create(&self, req: &CreateSessionRequest) -> Result<Session> {
31 let body = serde_json::to_value(req)?;
32
33 let path = if let Some(directory) = &req.directory {
34 format!("/session?directory={}", urlencoding::encode(directory))
35 } else {
36 "/session".to_string()
37 };
38
39 self.http
40 .request_json(Method::POST, &path, Some(body))
41 .await
42 }
43
44 pub async fn create_with(&self, options: SessionCreateOptions) -> Result<Session> {
52 let req: CreateSessionRequest = options.into();
53 self.create(&req).await
54 }
55
56 pub async fn get(&self, id: &str) -> Result<Session> {
62 self.http
63 .request_json(Method::GET, &format!("/session/{}", id), None)
64 .await
65 }
66
67 pub async fn list(&self) -> Result<Vec<Session>> {
73 self.http.request_json(Method::GET, "/session", None).await
74 }
75
76 pub async fn delete(&self, id: &str) -> Result<()> {
82 self.http
83 .request_empty(Method::DELETE, &format!("/session/{}", id), None)
84 .await
85 }
86
87 pub async fn fork(&self, id: &str) -> Result<Session> {
93 self.http
94 .request_json(
95 Method::POST,
96 &format!("/session/{}/fork", id),
97 Some(serde_json::json!({})),
98 )
99 .await
100 }
101
102 pub async fn abort(&self, id: &str) -> Result<()> {
108 self.http
109 .request_empty(
110 Method::POST,
111 &format!("/session/{}/abort", id),
112 Some(serde_json::json!({})),
113 )
114 .await
115 }
116
117 pub async fn status(&self) -> Result<SessionStatus> {
123 self.http
124 .request_json(Method::GET, "/session/status", None)
125 .await
126 }
127
128 pub async fn children(&self, id: &str) -> Result<Vec<Session>> {
134 self.http
135 .request_json(Method::GET, &format!("/session/{}/children", id), None)
136 .await
137 }
138
139 pub async fn todo(&self, id: &str) -> Result<Vec<TodoItem>> {
145 self.http
146 .request_json(Method::GET, &format!("/session/{}/todo", id), None)
147 .await
148 }
149
150 pub async fn update(&self, id: &str, req: &UpdateSessionRequest) -> Result<Session> {
156 let body = serde_json::to_value(req)?;
157 self.http
158 .request_json(Method::PATCH, &format!("/session/{}", id), Some(body))
159 .await
160 }
161
162 pub async fn init(&self, id: &str) -> Result<Session> {
168 self.http
169 .request_json(
170 Method::POST,
171 &format!("/session/{}/init", id),
172 Some(serde_json::json!({})),
173 )
174 .await
175 }
176
177 pub async fn share(&self, id: &str) -> Result<ShareInfo> {
183 self.http
184 .request_json(
185 Method::POST,
186 &format!("/session/{}/share", id),
187 Some(serde_json::json!({})),
188 )
189 .await
190 }
191
192 pub async fn unshare(&self, id: &str) -> Result<()> {
198 self.http
199 .request_empty(Method::DELETE, &format!("/session/{}/share", id), None)
200 .await
201 }
202
203 pub async fn diff(&self, id: &str) -> Result<SessionDiff> {
209 self.http
210 .request_json(Method::GET, &format!("/session/{}/diff", id), None)
211 .await
212 }
213
214 pub async fn diff_since_message(&self, id: &str, message_id: &str) -> Result<SessionDiff> {
220 let encoded = urlencoding::encode(message_id);
221 self.http
222 .request_json(
223 Method::GET,
224 &format!("/session/{}/diff?messageID={}", id, encoded),
225 None,
226 )
227 .await
228 }
229
230 pub async fn summarize(&self, id: &str, req: &SummarizeRequest) -> Result<Session> {
236 let body = serde_json::to_value(req)?;
237 self.http
238 .request_json(
239 Method::POST,
240 &format!("/session/{}/summarize", id),
241 Some(body),
242 )
243 .await
244 }
245
246 pub async fn revert(&self, id: &str, req: &RevertRequest) -> Result<Session> {
252 let body = serde_json::to_value(req)?;
253 self.http
254 .request_json(Method::POST, &format!("/session/{}/revert", id), Some(body))
255 .await
256 }
257
258 pub async fn unrevert(&self, id: &str) -> Result<Session> {
264 self.http
265 .request_json(
266 Method::POST,
267 &format!("/session/{}/unrevert", id),
268 Some(serde_json::json!({})),
269 )
270 .await
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use crate::http::HttpConfig;
278 use std::time::Duration;
279 use wiremock::matchers::{body_json, method, path, query_param};
280 use wiremock::{Mock, MockServer, ResponseTemplate};
281
282 #[tokio::test]
283 async fn test_create_session() {
284 let mock_server = MockServer::start().await;
285
286 Mock::given(method("POST"))
287 .and(path("/session"))
288 .and(body_json(serde_json::json!({})))
289 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
290 "id": "session123",
291 "projectId": "proj1",
292 "directory": "/path",
293 "title": "New Session",
294 "version": "1.0",
295 "time": {"created": 1234567890, "updated": 1234567890}
296 })))
297 .mount(&mock_server)
298 .await;
299
300 let http = HttpClient::new(HttpConfig {
301 base_url: mock_server.uri(),
302 directory: None,
303 timeout: Duration::from_secs(30),
304 })
305 .unwrap();
306
307 let sessions = SessionsApi::new(http);
308 let session = sessions
309 .create(&CreateSessionRequest::default())
310 .await
311 .unwrap();
312 assert_eq!(session.id, "session123");
313 }
314
315 #[tokio::test]
316 async fn test_create_session_with_options() {
317 let mock_server = MockServer::start().await;
318
319 Mock::given(method("POST"))
320 .and(path("/session"))
321 .and(query_param("directory", "/tmp/project"))
322 .and(body_json(serde_json::json!({
323 "title": "Quick Win",
324 "parentID": "parent-123"
325 })))
326 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
327 "id": "session123",
328 "projectId": "proj1",
329 "directory": "/tmp/project",
330 "title": "Quick Win",
331 "version": "1.0",
332 "time": {"created": 1234567890, "updated": 1234567890}
333 })))
334 .mount(&mock_server)
335 .await;
336
337 let http = HttpClient::new(HttpConfig {
338 base_url: mock_server.uri(),
339 directory: None,
340 timeout: Duration::from_secs(30),
341 })
342 .unwrap();
343
344 let sessions = SessionsApi::new(http);
345 let session = sessions
346 .create_with(
347 SessionCreateOptions::new()
348 .with_title("Quick Win")
349 .with_parent_id("parent-123")
350 .with_directory("/tmp/project"),
351 )
352 .await
353 .unwrap();
354
355 assert_eq!(session.id, "session123");
356 assert_eq!(session.title, "Quick Win");
357 }
358
359 #[tokio::test]
360 async fn test_get_session() {
361 let mock_server = MockServer::start().await;
362
363 Mock::given(method("GET"))
364 .and(path("/session/abc123"))
365 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
366 "id": "abc123",
367 "projectId": "p1",
368 "directory": "/path",
369 "title": "Test Session",
370 "version": "1.0",
371 "time": {"created": 1234567890, "updated": 1234567890}
372 })))
373 .mount(&mock_server)
374 .await;
375
376 let http = HttpClient::new(HttpConfig {
377 base_url: mock_server.uri(),
378 directory: None,
379 timeout: Duration::from_secs(30),
380 })
381 .unwrap();
382
383 let sessions = SessionsApi::new(http);
384 let session = sessions.get("abc123").await.unwrap();
385 assert_eq!(session.id, "abc123");
386 assert_eq!(session.title, "Test Session");
387 }
388
389 #[tokio::test]
390 async fn test_list_sessions() {
391 let mock_server = MockServer::start().await;
392
393 Mock::given(method("GET"))
394 .and(path("/session"))
395 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
396 {"id": "s1", "projectId": "p1", "directory": "/path", "title": "S1", "version": "1.0", "time": {"created": 1234567890, "updated": 1234567890}},
397 {"id": "s2", "projectId": "p1", "directory": "/path", "title": "S2", "version": "1.0", "time": {"created": 1234567890, "updated": 1234567890}}
398 ])))
399 .mount(&mock_server)
400 .await;
401
402 let http = HttpClient::new(HttpConfig {
403 base_url: mock_server.uri(),
404 directory: None,
405 timeout: Duration::from_secs(30),
406 })
407 .unwrap();
408
409 let sessions = SessionsApi::new(http);
410 let list = sessions.list().await.unwrap();
411 assert_eq!(list.len(), 2);
412 }
413
414 #[tokio::test]
415 async fn test_delete_session() {
416 let mock_server = MockServer::start().await;
417
418 Mock::given(method("DELETE"))
419 .and(path("/session/del123"))
420 .respond_with(ResponseTemplate::new(204))
421 .mount(&mock_server)
422 .await;
423
424 let http = HttpClient::new(HttpConfig {
425 base_url: mock_server.uri(),
426 directory: None,
427 timeout: Duration::from_secs(30),
428 })
429 .unwrap();
430
431 let sessions = SessionsApi::new(http);
432 sessions.delete("del123").await.unwrap();
433 }
434
435 #[tokio::test]
436 async fn test_children() {
437 let mock_server = MockServer::start().await;
438
439 Mock::given(method("GET"))
440 .and(path("/session/parent123/children"))
441 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
442 {"id": "child1", "projectId": "p1", "directory": "/path", "title": "Child 1", "version": "1.0", "time": {"created": 1234567890, "updated": 1234567890}}
443 ])))
444 .mount(&mock_server)
445 .await;
446
447 let http = HttpClient::new(HttpConfig {
448 base_url: mock_server.uri(),
449 directory: None,
450 timeout: Duration::from_secs(30),
451 })
452 .unwrap();
453
454 let sessions = SessionsApi::new(http);
455 let children = sessions.children("parent123").await.unwrap();
456 assert_eq!(children.len(), 1);
457 assert_eq!(children[0].id, "child1");
458 }
459
460 #[tokio::test]
461 async fn test_todo() {
462 let mock_server = MockServer::start().await;
463
464 Mock::given(method("GET"))
465 .and(path("/session/s1/todo"))
466 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
467 {"id": "t1", "content": "Task 1", "completed": false},
468 {"id": "t2", "content": "Task 2", "completed": true}
469 ])))
470 .mount(&mock_server)
471 .await;
472
473 let http = HttpClient::new(HttpConfig {
474 base_url: mock_server.uri(),
475 directory: None,
476 timeout: Duration::from_secs(30),
477 })
478 .unwrap();
479
480 let sessions = SessionsApi::new(http);
481 let todos = sessions.todo("s1").await.unwrap();
482 assert_eq!(todos.len(), 2);
483 assert!(!todos[0].completed);
484 assert!(todos[1].completed);
485 }
486
487 #[tokio::test]
488 async fn test_update_session() {
489 let mock_server = MockServer::start().await;
490
491 Mock::given(method("PATCH"))
492 .and(path("/session/s1"))
493 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
494 "id": "s1",
495 "projectId": "p1",
496 "directory": "/path",
497 "title": "Updated Title",
498 "version": "1.0",
499 "time": {"created": 1234567890, "updated": 1234567891}
500 })))
501 .mount(&mock_server)
502 .await;
503
504 let http = HttpClient::new(HttpConfig {
505 base_url: mock_server.uri(),
506 directory: None,
507 timeout: Duration::from_secs(30),
508 })
509 .unwrap();
510
511 let sessions = SessionsApi::new(http);
512 let session = sessions
513 .update(
514 "s1",
515 &UpdateSessionRequest {
516 title: Some("Updated Title".into()),
517 },
518 )
519 .await
520 .unwrap();
521 assert_eq!(session.title, "Updated Title");
522 }
523
524 #[tokio::test]
525 async fn test_share() {
526 let mock_server = MockServer::start().await;
527
528 Mock::given(method("POST"))
529 .and(path("/session/s1/share"))
530 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
531 "url": "https://share.example.com/s1"
532 })))
533 .mount(&mock_server)
534 .await;
535
536 let http = HttpClient::new(HttpConfig {
537 base_url: mock_server.uri(),
538 directory: None,
539 timeout: Duration::from_secs(30),
540 })
541 .unwrap();
542
543 let sessions = SessionsApi::new(http);
544 let share = sessions.share("s1").await.unwrap();
545 assert_eq!(share.url, "https://share.example.com/s1");
546 }
547
548 #[tokio::test]
549 async fn test_unshare() {
550 let mock_server = MockServer::start().await;
551
552 Mock::given(method("DELETE"))
553 .and(path("/session/s1/share"))
554 .respond_with(ResponseTemplate::new(204))
555 .mount(&mock_server)
556 .await;
557
558 let http = HttpClient::new(HttpConfig {
559 base_url: mock_server.uri(),
560 directory: None,
561 timeout: Duration::from_secs(30),
562 })
563 .unwrap();
564
565 let sessions = SessionsApi::new(http);
566 sessions.unshare("s1").await.unwrap();
567 }
568
569 #[tokio::test]
570 async fn test_diff() {
571 let mock_server = MockServer::start().await;
572
573 Mock::given(method("GET"))
574 .and(path("/session/s1/diff"))
575 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
576 "diff": "--- a/file.rs\n+++ b/file.rs\n@@ -1 +1 @@\n-old\n+new",
577 "files": ["file.rs"]
578 })))
579 .mount(&mock_server)
580 .await;
581
582 let http = HttpClient::new(HttpConfig {
583 base_url: mock_server.uri(),
584 directory: None,
585 timeout: Duration::from_secs(30),
586 })
587 .unwrap();
588
589 let sessions = SessionsApi::new(http);
590 let diff = sessions.diff("s1").await.unwrap();
591 assert!(diff.diff.contains("file.rs"));
592 assert_eq!(diff.files.len(), 1);
593 }
594
595 #[tokio::test]
596 async fn test_summarize() {
597 let mock_server = MockServer::start().await;
598
599 Mock::given(method("POST"))
600 .and(path("/session/s1/summarize"))
601 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
602 "id": "s1",
603 "projectId": "p1",
604 "directory": "/path",
605 "title": "Summarized Session",
606 "version": "1.0",
607 "time": {"created": 1234567890, "updated": 1234567891}
608 })))
609 .mount(&mock_server)
610 .await;
611
612 let http = HttpClient::new(HttpConfig {
613 base_url: mock_server.uri(),
614 directory: None,
615 timeout: Duration::from_secs(30),
616 })
617 .unwrap();
618
619 let sessions = SessionsApi::new(http);
620 let session = sessions
621 .summarize(
622 "s1",
623 &SummarizeRequest {
624 provider_id: "anthropic".into(),
625 model_id: "claude-3-5-sonnet".into(),
626 auto: None,
627 },
628 )
629 .await
630 .unwrap();
631 assert_eq!(session.id, "s1");
632 }
633
634 #[tokio::test]
635 async fn test_revert() {
636 let mock_server = MockServer::start().await;
637
638 Mock::given(method("POST"))
639 .and(path("/session/s1/revert"))
640 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
641 "id": "s1",
642 "projectId": "p1",
643 "directory": "/path",
644 "title": "Reverted Session",
645 "version": "1.0",
646 "time": {"created": 1234567890, "updated": 1234567891}
647 })))
648 .mount(&mock_server)
649 .await;
650
651 let http = HttpClient::new(HttpConfig {
652 base_url: mock_server.uri(),
653 directory: None,
654 timeout: Duration::from_secs(30),
655 })
656 .unwrap();
657
658 let sessions = SessionsApi::new(http);
659 let session = sessions
660 .revert(
661 "s1",
662 &crate::types::session::RevertRequest {
663 message_id: "m5".into(),
664 part_id: None,
665 },
666 )
667 .await
668 .unwrap();
669 assert_eq!(session.id, "s1");
670 }
671
672 #[tokio::test]
673 async fn test_unrevert() {
674 let mock_server = MockServer::start().await;
675
676 Mock::given(method("POST"))
677 .and(path("/session/s1/unrevert"))
678 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
679 "id": "s1",
680 "projectId": "p1",
681 "directory": "/path",
682 "title": "Unreverted Session",
683 "version": "1.0",
684 "time": {"created": 1234567890, "updated": 1234567891}
685 })))
686 .mount(&mock_server)
687 .await;
688
689 let http = HttpClient::new(HttpConfig {
690 base_url: mock_server.uri(),
691 directory: None,
692 timeout: Duration::from_secs(30),
693 })
694 .unwrap();
695
696 let sessions = SessionsApi::new(http);
697 let session = sessions.unrevert("s1").await.unwrap();
698 assert_eq!(session.id, "s1");
699 }
700
701 #[tokio::test]
704 async fn test_get_session_not_found() {
705 let mock_server = MockServer::start().await;
706
707 Mock::given(method("GET"))
708 .and(path("/session/nonexistent"))
709 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
710 "name": "NotFound",
711 "message": "Session not found",
712 "data": {"id": "nonexistent"}
713 })))
714 .mount(&mock_server)
715 .await;
716
717 let http = HttpClient::new(HttpConfig {
718 base_url: mock_server.uri(),
719 directory: None,
720 timeout: Duration::from_secs(30),
721 })
722 .unwrap();
723
724 let sessions = SessionsApi::new(http);
725 let result = sessions.get("nonexistent").await;
726 assert!(result.is_err());
727 let err = result.unwrap_err();
728 assert!(err.is_not_found());
729 assert_eq!(err.error_name(), Some("NotFound"));
730 }
731
732 #[tokio::test]
733 async fn test_create_session_validation_error() {
734 let mock_server = MockServer::start().await;
735
736 Mock::given(method("POST"))
737 .and(path("/session"))
738 .respond_with(ResponseTemplate::new(400).set_body_json(serde_json::json!({
739 "name": "ValidationError",
740 "message": "Invalid session configuration"
741 })))
742 .mount(&mock_server)
743 .await;
744
745 let http = HttpClient::new(HttpConfig {
746 base_url: mock_server.uri(),
747 directory: None,
748 timeout: Duration::from_secs(30),
749 })
750 .unwrap();
751
752 let sessions = SessionsApi::new(http);
753 let result = sessions.create(&CreateSessionRequest::default()).await;
754 assert!(result.is_err());
755 let err = result.unwrap_err();
756 assert!(err.is_validation_error());
757 }
758
759 #[tokio::test]
760 async fn test_children_not_found() {
761 let mock_server = MockServer::start().await;
762
763 Mock::given(method("GET"))
764 .and(path("/session/missing/children"))
765 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
766 "name": "NotFound",
767 "message": "Session not found"
768 })))
769 .mount(&mock_server)
770 .await;
771
772 let http = HttpClient::new(HttpConfig {
773 base_url: mock_server.uri(),
774 directory: None,
775 timeout: Duration::from_secs(30),
776 })
777 .unwrap();
778
779 let sessions = SessionsApi::new(http);
780 let result = sessions.children("missing").await;
781 assert!(result.is_err());
782 assert!(result.unwrap_err().is_not_found());
783 }
784
785 #[tokio::test]
786 async fn test_update_session_not_found() {
787 let mock_server = MockServer::start().await;
788
789 Mock::given(method("PATCH"))
790 .and(path("/session/missing"))
791 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
792 "name": "NotFound",
793 "message": "Session not found"
794 })))
795 .mount(&mock_server)
796 .await;
797
798 let http = HttpClient::new(HttpConfig {
799 base_url: mock_server.uri(),
800 directory: None,
801 timeout: Duration::from_secs(30),
802 })
803 .unwrap();
804
805 let sessions = SessionsApi::new(http);
806 let result = sessions
807 .update("missing", &UpdateSessionRequest { title: None })
808 .await;
809 assert!(result.is_err());
810 assert!(result.unwrap_err().is_not_found());
811 }
812
813 #[tokio::test]
814 async fn test_share_server_error() {
815 let mock_server = MockServer::start().await;
816
817 Mock::given(method("POST"))
818 .and(path("/session/s1/share"))
819 .respond_with(ResponseTemplate::new(500).set_body_json(serde_json::json!({
820 "name": "InternalError",
821 "message": "Failed to generate share link"
822 })))
823 .mount(&mock_server)
824 .await;
825
826 let http = HttpClient::new(HttpConfig {
827 base_url: mock_server.uri(),
828 directory: None,
829 timeout: Duration::from_secs(30),
830 })
831 .unwrap();
832
833 let sessions = SessionsApi::new(http);
834 let result = sessions.share("s1").await;
835 assert!(result.is_err());
836 assert!(result.unwrap_err().is_server_error());
837 }
838
839 #[tokio::test]
840 async fn test_diff_not_found() {
841 let mock_server = MockServer::start().await;
842
843 Mock::given(method("GET"))
844 .and(path("/session/missing/diff"))
845 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
846 "name": "NotFound",
847 "message": "Session not found"
848 })))
849 .mount(&mock_server)
850 .await;
851
852 let http = HttpClient::new(HttpConfig {
853 base_url: mock_server.uri(),
854 directory: None,
855 timeout: Duration::from_secs(30),
856 })
857 .unwrap();
858
859 let sessions = SessionsApi::new(http);
860 let result = sessions.diff("missing").await;
861 assert!(result.is_err());
862 assert!(result.unwrap_err().is_not_found());
863 }
864
865 #[tokio::test]
866 async fn test_summarize_validation_error() {
867 let mock_server = MockServer::start().await;
868
869 Mock::given(method("POST"))
870 .and(path("/session/s1/summarize"))
871 .respond_with(ResponseTemplate::new(400).set_body_json(serde_json::json!({
872 "name": "ValidationError",
873 "message": "Invalid provider or model"
874 })))
875 .mount(&mock_server)
876 .await;
877
878 let http = HttpClient::new(HttpConfig {
879 base_url: mock_server.uri(),
880 directory: None,
881 timeout: Duration::from_secs(30),
882 })
883 .unwrap();
884
885 let sessions = SessionsApi::new(http);
886 let result = sessions
887 .summarize(
888 "s1",
889 &SummarizeRequest {
890 provider_id: "invalid".into(),
891 model_id: "invalid".into(),
892 auto: None,
893 },
894 )
895 .await;
896 assert!(result.is_err());
897 assert!(result.unwrap_err().is_validation_error());
898 }
899
900 #[tokio::test]
901 async fn test_revert_not_found() {
902 let mock_server = MockServer::start().await;
903
904 Mock::given(method("POST"))
905 .and(path("/session/missing/revert"))
906 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
907 "name": "NotFound",
908 "message": "Session not found"
909 })))
910 .mount(&mock_server)
911 .await;
912
913 let http = HttpClient::new(HttpConfig {
914 base_url: mock_server.uri(),
915 directory: None,
916 timeout: Duration::from_secs(30),
917 })
918 .unwrap();
919
920 let sessions = SessionsApi::new(http);
921 let result = sessions
922 .revert(
923 "missing",
924 &crate::types::session::RevertRequest {
925 message_id: "m1".into(),
926 part_id: None,
927 },
928 )
929 .await;
930 assert!(result.is_err());
931 assert!(result.unwrap_err().is_not_found());
932 }
933
934 #[tokio::test]
935 async fn test_abort_server_error() {
936 let mock_server = MockServer::start().await;
937
938 Mock::given(method("POST"))
939 .and(path("/session/s1/abort"))
940 .respond_with(ResponseTemplate::new(500).set_body_json(serde_json::json!({
941 "name": "InternalError",
942 "message": "Failed to abort session"
943 })))
944 .mount(&mock_server)
945 .await;
946
947 let http = HttpClient::new(HttpConfig {
948 base_url: mock_server.uri(),
949 directory: None,
950 timeout: Duration::from_secs(30),
951 })
952 .unwrap();
953
954 let sessions = SessionsApi::new(http);
955 let result = sessions.abort("s1").await;
956 assert!(result.is_err());
957 assert!(result.unwrap_err().is_server_error());
958 }
959
960 #[tokio::test]
961 async fn test_todo_not_found() {
962 let mock_server = MockServer::start().await;
963
964 Mock::given(method("GET"))
965 .and(path("/session/missing/todo"))
966 .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
967 "name": "NotFound",
968 "message": "Session not found"
969 })))
970 .mount(&mock_server)
971 .await;
972
973 let http = HttpClient::new(HttpConfig {
974 base_url: mock_server.uri(),
975 directory: None,
976 timeout: Duration::from_secs(30),
977 })
978 .unwrap();
979
980 let sessions = SessionsApi::new(http);
981 let result = sessions.todo("missing").await;
982 assert!(result.is_err());
983 assert!(result.unwrap_err().is_not_found());
984 }
985}