1pub mod error;
227pub mod idempotency;
229pub mod jobs;
231pub mod provider;
233pub mod provider_extensions;
235pub mod queue;
237pub mod retry;
239pub mod sentiment;
241pub mod signer;
243pub mod spawn;
245pub mod tool;
247pub mod util;
249
250pub use riglr_config::{
252 AppConfig, Config, DatabaseConfig, Environment, FeaturesConfig, NetworkConfig, ProvidersConfig,
253};
254
255pub use error::{CoreError, ToolError};
256pub use idempotency::*;
257pub use jobs::*;
258pub use queue::*;
259pub use signer::SignerContext;
260pub use signer::SignerError;
261pub use signer::UnifiedSigner;
262pub use tool::*;
263#[cfg(test)]
267mod tests {
268 use super::*;
269 use std::sync::Arc;
270
271 #[derive(Clone)]
272 struct MockTool {
273 name: String,
274 should_fail: bool,
275 }
276
277 #[async_trait::async_trait]
278 impl Tool for MockTool {
279 async fn execute(
280 &self,
281 params: serde_json::Value,
282 _context: &crate::provider::ApplicationContext,
283 ) -> Result<JobResult, ToolError> {
284 if self.should_fail {
285 return Err(ToolError::permanent_string("Mock tool failure"));
286 }
287
288 let message = params["message"].as_str().unwrap_or("Hello");
289 Ok(JobResult::success(&format!("{}: {}", self.name, message))?)
290 }
291
292 fn name(&self) -> &str {
293 &self.name
294 }
295
296 fn description(&self) -> &str {
297 ""
298 }
299 }
300
301 #[tokio::test]
302 async fn test_job_creation() -> anyhow::Result<()> {
303 let job = Job::new("test_tool", &serde_json::json!({"message": "test"}), 3)?;
304
305 assert_eq!(job.tool_name, "test_tool");
306 assert_eq!(job.max_retries, 3);
307 assert_eq!(job.retry_count, 0);
308 Ok(())
309 }
310
311 #[tokio::test]
312 async fn test_job_result_success() -> anyhow::Result<()> {
313 let result = JobResult::success(&"test result")?;
314
315 match result {
316 JobResult::Success { value, tx_hash } => {
317 assert_eq!(value, serde_json::json!("test result"));
318 assert!(tx_hash.is_none());
319 }
320 _ => panic!("Expected success result"),
321 }
322 Ok(())
323 }
324
325 #[tokio::test]
326 async fn test_job_result_failure() {
327 let result = JobResult::Failure {
328 error: crate::error::ToolError::retriable_string("test error"),
329 };
330
331 match result {
332 JobResult::Failure { ref error } => {
333 assert!(error.contains("test error"));
334 assert!(result.is_retriable());
335 }
336 _ => panic!("Expected failure result"),
337 }
338 }
339
340 #[tokio::test]
341 async fn test_tool_worker_creation() {
342 let _worker = ToolWorker::<InMemoryIdempotencyStore>::new(
343 ExecutionConfig::default(),
344 provider::ApplicationContext::default(),
345 );
346
347 }
349
350 #[tokio::test]
351 async fn test_tool_registration_and_execution() -> anyhow::Result<()> {
352 let worker = ToolWorker::<InMemoryIdempotencyStore>::new(
353 ExecutionConfig::default(),
354 provider::ApplicationContext::default(),
355 );
356
357 let tool = Arc::new(MockTool {
358 name: "test_tool".to_string(),
359 should_fail: false,
360 });
361
362 worker.register_tool(tool).await;
363
364 let job = Job::new(
365 "test_tool",
366 &serde_json::json!({"message": "Hello World"}),
367 3,
368 )?;
369
370 let result = worker.process_job(job).await;
371 assert!(result.is_ok());
372
373 match result.unwrap() {
374 JobResult::Success { value, .. } => {
375 assert!(value.as_str().unwrap().contains("Hello World"));
376 }
377 _ => panic!("Expected successful job result"),
378 }
379
380 Ok(())
381 }
382
383 #[tokio::test]
384 async fn test_tool_error_handling() -> anyhow::Result<()> {
385 let worker = ToolWorker::<InMemoryIdempotencyStore>::new(
386 ExecutionConfig::default(),
387 provider::ApplicationContext::default(),
388 );
389
390 let tool = Arc::new(MockTool {
391 name: "failing_tool".to_string(),
392 should_fail: true,
393 });
394
395 worker.register_tool(tool).await;
396
397 let job = Job::new("failing_tool", &serde_json::json!({"message": "test"}), 3)?;
398
399 let result = worker.process_job(job).await;
400 assert!(result.is_ok());
401
402 match result.unwrap() {
404 JobResult::Failure { error, .. } => {
405 assert!(error.contains("Mock tool failure"));
406 }
407 _ => panic!("Expected failure job result"),
408 }
409
410 Ok(())
411 }
412
413 #[test]
414 fn test_tool_error_types() {
415 let retriable = ToolError::retriable_string("Network timeout");
416 assert!(retriable.is_retriable());
417 assert!(!retriable.is_rate_limited());
418
419 let rate_limited = ToolError::rate_limited_string("API rate limit exceeded");
420 assert!(rate_limited.is_retriable());
421 assert!(rate_limited.is_rate_limited());
422
423 let permanent = ToolError::permanent_string("Invalid parameters");
424 assert!(!permanent.is_retriable());
425 assert!(!permanent.is_rate_limited());
426 }
427
428 #[test]
429 fn test_error_conversions() {
430 let anyhow_error = anyhow::anyhow!("Test error");
431 let tool_error: ToolError = ToolError::permanent_string(anyhow_error.to_string());
432 assert!(!tool_error.is_retriable());
433
434 let string_error = "Test string error".to_string();
435 let tool_error: ToolError = ToolError::permanent_string(string_error);
436 assert!(!tool_error.is_retriable());
437
438 let str_error = "Test str error";
439 let tool_error: ToolError = ToolError::permanent_string(str_error);
440 assert!(!tool_error.is_retriable());
441 }
442
443 #[tokio::test]
444 async fn test_execution_config() {
445 let config = ExecutionConfig::default();
446
447 assert!(config.default_timeout > std::time::Duration::from_millis(0));
448 assert!(config.max_concurrency > 0);
449 assert!(config.initial_retry_delay > std::time::Duration::from_millis(0));
450 }
451
452 #[tokio::test]
453 async fn test_idempotency_store() -> anyhow::Result<()> {
454 let store = InMemoryIdempotencyStore::new();
455 let key = "test_key";
456 let value = serde_json::json!({"test": "value"});
457
458 assert!(store.get(key).await?.is_none());
460
461 let job_result = JobResult::success(&value)?;
463 store
464 .set(
465 key,
466 Arc::new(job_result),
467 std::time::Duration::from_secs(60),
468 )
469 .await?;
470
471 let retrieved = store.get(key).await?;
473 assert!(retrieved.is_some());
474 if let Some(arc_result) = retrieved {
476 if let JobResult::Success { value, .. } = arc_result.as_ref() {
477 assert_eq!(*value, serde_json::json!({"test": "value"}));
478 } else {
479 panic!("Expected Success variant");
480 }
481 }
482
483 Ok(())
484 }
485
486 #[test]
489 fn test_job_result_success_with_tx_hash() -> anyhow::Result<()> {
490 let result = JobResult::success_with_tx(&"test result", "0x123abc")?;
491
492 match result {
493 JobResult::Success { value, tx_hash } => {
494 assert_eq!(value, serde_json::json!("test result"));
495 assert_eq!(tx_hash, Some("0x123abc".to_string()));
496 }
497 _ => panic!("Expected success result with tx hash"),
498 }
499 Ok(())
500 }
501
502 #[test]
503 fn test_job_result_permanent_failure() {
504 let result = JobResult::Failure {
505 error: crate::error::ToolError::permanent_string("invalid parameters"),
506 };
507
508 match result {
509 JobResult::Failure { ref error } => {
510 assert!(error.contains("invalid parameters"));
511 assert!(!result.is_retriable());
512 }
513 _ => panic!("Expected permanent failure result"),
514 }
515 }
516
517 #[test]
518 fn test_job_retry_logic() -> anyhow::Result<()> {
519 let mut job = Job::new("test_tool", &serde_json::json!({"test": "data"}), 3)?;
520
521 assert!(job.can_retry());
522 assert_eq!(job.retry_count, 0);
523
524 job.retry_count = 1;
526 assert!(job.can_retry());
527
528 job.retry_count = 2;
529 assert!(job.can_retry());
530
531 job.retry_count = 3;
532 assert!(!job.can_retry());
533
534 job.retry_count = 4;
535 assert!(!job.can_retry());
536
537 Ok(())
538 }
539
540 #[test]
541 fn test_job_new_idempotent() -> anyhow::Result<()> {
542 let job = Job::new_idempotent(
543 "test_tool",
544 &serde_json::json!({"message": "test"}),
545 5,
546 "unique_key_123",
547 )?;
548
549 assert_eq!(job.tool_name, "test_tool");
550 assert_eq!(job.max_retries, 5);
551 assert_eq!(job.retry_count, 0);
552 assert_eq!(job.idempotency_key, Some("unique_key_123".to_string()));
553 Ok(())
554 }
555
556 #[test]
557 fn test_job_new_idempotent_without_key() -> anyhow::Result<()> {
558 let job = Job::new("test_tool", &serde_json::json!({"message": "test"}), 2)?;
559
560 assert_eq!(job.tool_name, "test_tool");
561 assert_eq!(job.max_retries, 2);
562 assert_eq!(job.retry_count, 0);
563 assert!(job.idempotency_key.is_none());
564 Ok(())
565 }
566
567 #[test]
568 fn test_core_error_display() {
569 let queue_error = CoreError::Queue("Failed to connect".to_string());
570 assert!(queue_error
571 .to_string()
572 .contains("Queue error: Failed to connect"));
573
574 let job_error = CoreError::JobExecution("Timeout occurred".to_string());
575 assert!(job_error
576 .to_string()
577 .contains("Job execution error: Timeout occurred"));
578
579 let generic_error = CoreError::Generic("Something went wrong".to_string());
580 assert!(generic_error
581 .to_string()
582 .contains("Core error: Something went wrong"));
583 }
584
585 #[test]
586 fn test_core_error_from_serde_json() {
587 let json_error = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
588 let core_error: CoreError = json_error.into();
589
590 match core_error {
591 CoreError::Serialization(_) => (),
592 _ => panic!("Expected Serialization error"),
593 }
594 }
595
596 #[test]
597 fn test_tool_error_permanent() {
598 let error = ToolError::permanent_string("Authorization failed");
599 assert!(!error.is_retriable());
600 assert!(!error.is_rate_limited());
601 assert!(error.to_string().contains("Authorization failed"));
602 }
603
604 #[test]
605 fn test_tool_error_retriable() {
606 let error = ToolError::retriable_string("Connection timeout");
607 assert!(error.is_retriable());
608 assert!(!error.is_rate_limited());
609 assert!(error.to_string().contains("Connection timeout"));
610 }
611
612 #[test]
613 fn test_tool_error_rate_limited() {
614 let error = ToolError::rate_limited_string("Too many requests");
615 assert!(error.is_retriable());
616 assert!(error.is_rate_limited());
617 assert!(error.to_string().contains("Too many requests"));
618 }
619
620 #[test]
621 fn test_tool_error_from_anyhow() {
622 let anyhow_error = anyhow::anyhow!("Test anyhow error");
623 let tool_error: ToolError = ToolError::permanent_string(anyhow_error.to_string());
624 assert!(!tool_error.is_retriable());
625 assert!(!tool_error.is_rate_limited());
626 }
627
628 #[test]
629 fn test_tool_error_from_str() {
630 let str_error = "Test str error";
631 let tool_error: ToolError = ToolError::permanent_string(str_error);
632 assert!(!tool_error.is_retriable());
633 assert!(!tool_error.is_rate_limited());
634 assert!(tool_error.to_string().contains("Test str error"));
635 }
636
637 #[test]
638 fn test_tool_error_from_string() {
639 let string_error = "Test string error".to_string();
640 let tool_error: ToolError = ToolError::permanent_string(string_error);
641 assert!(!tool_error.is_retriable());
642 assert!(!tool_error.is_rate_limited());
643 assert!(tool_error.to_string().contains("Test string error"));
644 }
645
646 #[test]
647 fn test_execution_config_customization() {
648 let mut config = ExecutionConfig::default();
649
650 assert!(config.default_timeout > std::time::Duration::from_millis(0));
652 assert!(config.max_concurrency > 0);
653 assert!(config.initial_retry_delay > std::time::Duration::from_millis(0));
654
655 config.max_concurrency = 10;
657 config.default_timeout = std::time::Duration::from_secs(30);
658 config.initial_retry_delay = std::time::Duration::from_millis(100);
659
660 assert_eq!(config.max_concurrency, 10);
661 assert_eq!(config.default_timeout, std::time::Duration::from_secs(30));
662 assert_eq!(
663 config.initial_retry_delay,
664 std::time::Duration::from_millis(100)
665 );
666 }
667
668 #[tokio::test]
669 async fn test_tool_worker_with_non_existent_tool() -> anyhow::Result<()> {
670 let worker = ToolWorker::<InMemoryIdempotencyStore>::new(
671 ExecutionConfig::default(),
672 provider::ApplicationContext::default(),
673 );
674
675 let job = Job::new(
676 "non_existent_tool",
677 &serde_json::json!({"message": "test"}),
678 1,
679 )?;
680
681 let result = worker.process_job(job).await;
682
683 assert!(result.is_err());
685
686 let error = result.unwrap_err();
687 assert!(error.to_string().contains("not found") || error.to_string().contains("Tool"));
688
689 Ok(())
690 }
691
692 #[tokio::test]
693 async fn test_job_with_empty_parameters() -> anyhow::Result<()> {
694 let job = Job::new("test_tool", &serde_json::json!({}), 1)?;
695
696 assert_eq!(job.tool_name, "test_tool");
697 assert_eq!(job.params, serde_json::json!({}));
698 assert_eq!(job.max_retries, 1);
699 assert_eq!(job.retry_count, 0);
700 Ok(())
701 }
702
703 #[tokio::test]
704 async fn test_job_with_null_parameters() -> anyhow::Result<()> {
705 let job = Job::new("test_tool", &serde_json::Value::Null, 1)?;
706
707 assert_eq!(job.tool_name, "test_tool");
708 assert_eq!(job.params, serde_json::Value::Null);
709 assert_eq!(job.max_retries, 1);
710 assert_eq!(job.retry_count, 0);
711 Ok(())
712 }
713
714 #[test]
715 fn test_job_retry_boundary_conditions() -> anyhow::Result<()> {
716 let job = Job::new("test_tool", &serde_json::json!({}), 0)?;
718 assert!(!job.can_retry());
719
720 let mut job = Job::new("test_tool", &serde_json::json!({}), 1)?;
722 assert!(job.can_retry());
723 job.retry_count = 1;
724 assert!(!job.can_retry());
725
726 Ok(())
727 }
728
729 #[tokio::test]
730 async fn test_mock_tool_with_empty_message() -> anyhow::Result<()> {
731 let tool = MockTool {
732 name: "test_tool".to_string(),
733 should_fail: false,
734 };
735
736 let context = provider::ApplicationContext::default();
737 let result = tool.execute(serde_json::json!({}), &context).await.unwrap();
738
739 match result {
740 JobResult::Success { value, .. } => {
741 assert!(value.as_str().unwrap().contains("test_tool"));
742 assert!(value.as_str().unwrap().contains("Hello")); }
744 _ => panic!("Expected success result"),
745 }
746
747 Ok(())
748 }
749
750 #[test]
751 fn test_mock_tool_name_and_description() {
752 let tool = MockTool {
753 name: "custom_tool".to_string(),
754 should_fail: false,
755 };
756
757 assert_eq!(tool.name(), "custom_tool");
758 assert_eq!(tool.description(), "");
759 }
760
761 #[tokio::test]
762 async fn test_worker_multiple_tool_registration() -> anyhow::Result<()> {
763 let worker = ToolWorker::<InMemoryIdempotencyStore>::new(
764 ExecutionConfig::default(),
765 provider::ApplicationContext::default(),
766 );
767
768 let tool1 = Arc::new(MockTool {
769 name: "tool1".to_string(),
770 should_fail: false,
771 });
772
773 let tool2 = Arc::new(MockTool {
774 name: "tool2".to_string(),
775 should_fail: false,
776 });
777
778 worker.register_tool(tool1).await;
779 worker.register_tool(tool2).await;
780
781 let job1 = Job::new("tool1", &serde_json::json!({"message": "test1"}), 1)?;
783 let result1 = worker.process_job(job1).await?;
784
785 let job2 = Job::new("tool2", &serde_json::json!({"message": "test2"}), 1)?;
786 let result2 = worker.process_job(job2).await?;
787
788 match (&result1, &result2) {
789 (JobResult::Success { .. }, JobResult::Success { .. }) => (),
790 _ => panic!("Both tools should succeed"),
791 }
792
793 Ok(())
794 }
795
796 #[test]
797 fn test_job_result_serialization() -> anyhow::Result<()> {
798 let success_result = JobResult::success(&"test data")?;
799 let serialized = serde_json::to_string(&success_result)?;
800 let deserialized: JobResult = serde_json::from_str(&serialized)?;
801
802 match deserialized {
803 JobResult::Success { value, tx_hash } => {
804 assert_eq!(value, serde_json::json!("test data"));
805 assert!(tx_hash.is_none());
806 }
807 _ => panic!("Expected success result after deserialization"),
808 }
809
810 Ok(())
811 }
812
813 #[test]
814 fn test_job_result_failure_serialization() -> anyhow::Result<()> {
815 let failure_result = JobResult::Failure {
816 error: crate::error::ToolError::retriable_string("network error"),
817 };
818 let serialized = serde_json::to_string(&failure_result)?;
819 let deserialized: JobResult = serde_json::from_str(&serialized)?;
820
821 match deserialized {
822 JobResult::Failure { ref error } => {
823 assert!(error.contains("network error"));
824 assert!(deserialized.is_retriable());
825 }
826 _ => panic!("Expected failure result after deserialization"),
827 }
828
829 Ok(())
830 }
831
832 #[tokio::test]
833 async fn test_idempotency_store_expiration() -> anyhow::Result<()> {
834 let store = InMemoryIdempotencyStore::new();
835 let key = "expiring_key";
836 let value = serde_json::json!({"test": "expiry"});
837
838 let job_result = JobResult::success(&value)?;
839
840 store
842 .set(
843 key,
844 Arc::new(job_result),
845 std::time::Duration::from_millis(1),
846 )
847 .await?;
848
849 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
851
852 let retrieved = store.get(key).await?;
854 assert!(retrieved.is_none());
855
856 Ok(())
857 }
858}