1#![forbid(unsafe_code)]
9pub mod calculator;
135pub mod config;
136pub mod error;
137pub mod validation;
138pub mod performance;
139
140pub use calculator::{Calculator, BatchOptions, BatchResult, BatchCalculation};
142pub use calculator::builder::{CalculatorBuilder, BonusConfig, CalculatorPreset};
143pub use calculator::utils::{ScoreExplanation, ScorePrediction, AgentComparison};
144pub use config::CalculatorConfig;
145pub use error::{ReputationError, ValidationError, BuilderError, CalculationError, Result};
146
147pub use reputation_types::{ConfidenceLevel, ScoreComponents, PriorBreakdown};
149
150pub const ALGORITHM_VERSION: &str = "1.0.0";
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use chrono::{Duration, Utc};
157 use reputation_types::AgentData;
158
159 fn create_valid_agent() -> AgentData {
160 AgentData {
161 did: "did:test:123".to_string(),
162 created_at: Utc::now() - Duration::days(1),
163 mcp_level: None,
164 identity_verified: false,
165 security_audit_passed: false,
166 open_source: false,
167 total_interactions: 0,
168 total_reviews: 0,
169 average_rating: None,
170 positive_reviews: 0,
171 negative_reviews: 0,
172 }
173 }
174
175 #[test]
176 fn test_default_calculation() {
177 let agent = create_valid_agent();
178 let calc = Calculator::default();
179 let score = calc.calculate(&agent).unwrap();
180
181 assert_eq!(score.score, 50.0);
182 assert_eq!(score.confidence, 0.0);
183 }
184
185 #[test]
186 fn test_invalid_did_format() {
187 let mut agent = create_valid_agent();
188 agent.did = "invalid-did".to_string();
189
190 let calc = Calculator::default();
191 let result = calc.calculate(&agent);
192
193 assert!(result.is_err());
194 match result.unwrap_err() {
195 ReputationError::ValidationError(ValidationError::InvalidDid(msg)) => {
196 assert!(msg.contains("DID must start with 'did:' prefix"));
198 }
199 _ => panic!("Expected InvalidDid error"),
200 }
201 }
202
203 #[test]
204 fn test_future_date_error() {
205 let mut agent = create_valid_agent();
206 agent.created_at = Utc::now() + Duration::days(1);
207
208 let calc = Calculator::default();
209 let result = calc.calculate(&agent);
210
211 assert!(result.is_err());
212 match result.unwrap_err() {
213 ReputationError::ValidationError(ValidationError::FutureDate(_)) => {}
214 _ => panic!("Expected FutureDate error"),
215 }
216 }
217
218 #[test]
219 fn test_invalid_rating_error() {
220 let mut agent = create_valid_agent();
221 agent.average_rating = Some(6.0);
222 agent.total_reviews = 10;
223 agent.positive_reviews = 6;
224 agent.negative_reviews = 4;
225 agent.total_interactions = 10;
226
227 let calc = Calculator::default();
228 let result = calc.calculate(&agent);
229
230 assert!(result.is_err());
231 match result.unwrap_err() {
232 ReputationError::ValidationError(ValidationError::InvalidRating(rating)) => {
233 assert_eq!(rating, 6.0);
234 }
235 _ => panic!("Expected InvalidRating error"),
236 }
237 }
238
239 #[test]
240 fn test_invalid_mcp_level_error() {
241 let mut agent = create_valid_agent();
242 agent.mcp_level = Some(5);
243
244 let calc = Calculator::default();
245 let result = calc.calculate(&agent);
246
247 assert!(result.is_err());
248 match result.unwrap_err() {
249 ReputationError::ValidationError(ValidationError::InvalidMcpLevel(level)) => {
250 assert_eq!(level, 5);
251 }
252 _ => panic!("Expected InvalidMcpLevel error"),
253 }
254 }
255
256 #[test]
257 fn test_inconsistent_reviews_error() {
258 let mut agent = create_valid_agent();
259 agent.positive_reviews = 10;
260 agent.negative_reviews = 5;
261 agent.total_reviews = 20; agent.total_interactions = 20;
263 agent.average_rating = Some(4.0);
264
265 let calc = Calculator::default();
266 let result = calc.calculate(&agent);
267
268 assert!(result.is_err());
269 match result.unwrap_err() {
270 ReputationError::ValidationError(ValidationError::InconsistentReviews) => {
271 }
273 _ => panic!("Expected InconsistentReviews error"),
274 }
275 }
276
277 #[test]
278 fn test_reviews_exceed_interactions_error() {
279 let mut agent = create_valid_agent();
280 agent.total_reviews = 10;
281 agent.positive_reviews = 6;
282 agent.negative_reviews = 4;
283 agent.total_interactions = 5;
284 agent.average_rating = Some(4.0); let calc = Calculator::default();
287 let result = calc.calculate(&agent);
288
289 assert!(result.is_err());
290 match result.unwrap_err() {
291 ReputationError::ValidationError(ValidationError::InvalidField { field, .. }) => {
292 assert_eq!(field, "total_reviews");
293 }
294 _ => panic!("Expected InvalidField error"),
295 }
296 }
297
298 #[test]
299 fn test_calculator_new_invalid_confidence_k() {
300 let result = Calculator::new(-1.0, 50.0, 80.0);
301 assert!(result.is_err());
302 match result.unwrap_err() {
303 ReputationError::CalculationError(msg) => {
304 assert!(msg.contains("confidence_k must be positive"));
305 }
306 _ => panic!("Expected BuilderError for negative confidence_k"),
307 }
308 }
309
310 #[test]
311 fn test_calculator_new_invalid_prior_base() {
312 let result = Calculator::new(15.0, -10.0, 80.0);
313 assert!(result.is_err());
314
315 let result = Calculator::new(15.0, 110.0, 80.0);
316 assert!(result.is_err());
317 }
318
319 #[test]
320 fn test_calculator_new_invalid_prior_max() {
321 let result = Calculator::new(15.0, 50.0, 40.0); assert!(result.is_err());
323
324 let result = Calculator::new(15.0, 50.0, 110.0); assert!(result.is_err());
326 }
327
328 #[test]
329 fn test_valid_calculation_with_reviews() {
330 let mut agent = create_valid_agent();
331 agent.total_interactions = 100;
332 agent.total_reviews = 50;
333 agent.positive_reviews = 40;
334 agent.negative_reviews = 10;
335 agent.average_rating = Some(4.2);
336 agent.mcp_level = Some(2);
337
338 let calc = Calculator::default();
339 let score = calc.calculate(&agent).unwrap();
340
341 assert!(score.score > 50.0); assert!(score.confidence > 0.0 && score.confidence < 1.0);
343 }
344
345 #[test]
346 fn test_edge_case_minimum_rating() {
347 let mut agent = create_valid_agent();
348 agent.total_interactions = 10;
349 agent.total_reviews = 10;
350 agent.positive_reviews = 0;
351 agent.negative_reviews = 10;
352 agent.average_rating = Some(1.0);
353
354 let calc = Calculator::default();
355 let score = calc.calculate(&agent).unwrap();
356
357 assert!(score.score >= 0.0);
358 assert!(score.score <= 100.0);
359 }
360
361 #[test]
362 fn test_edge_case_maximum_rating() {
363 let mut agent = create_valid_agent();
364 agent.total_interactions = 10;
365 agent.total_reviews = 10;
366 agent.positive_reviews = 10;
367 agent.negative_reviews = 0;
368 agent.average_rating = Some(5.0);
369
370 let calc = Calculator::default();
371 let score = calc.calculate(&agent).unwrap();
372
373 assert!(score.score >= 0.0);
374 assert!(score.score <= 100.0);
375 }
376
377 #[test]
378 fn test_mcp_level_bonuses() {
379 let base_agent = create_valid_agent();
380 let calc = Calculator::default();
381
382 for level in 0..=3 {
384 let mut agent = base_agent.clone();
385 agent.mcp_level = Some(level);
386 let score = calc.calculate(&agent).unwrap();
387
388 let expected_bonus = match level {
390 1 => 5.0,
391 2 => 10.0,
392 3 => 15.0,
393 _ => 0.0,
394 };
395 assert_eq!(score.score, 50.0 + expected_bonus);
396 }
397 }
398
399 #[test]
400 fn test_confidence_calculation() {
401 let calc = Calculator::default();
402 let mut agent = create_valid_agent();
403
404 let test_cases = vec![(0, 0.0), (15, 0.5), (30, 0.667), (150, 0.909)];
406
407 for (interactions, expected_confidence) in test_cases {
408 agent.total_interactions = interactions;
409 let score = calc.calculate(&agent).unwrap();
410 assert!((score.confidence - expected_confidence).abs() < 0.01);
411 }
412 }
413
414 #[test]
415 fn test_error_propagation_chain() {
416 let mut agent = create_valid_agent();
418 agent.did = "bad-did".to_string();
419 agent.average_rating = Some(10.0); let calc = Calculator::default();
422 let result = calc.calculate(&agent);
423
424 assert!(result.is_err());
425 match result.unwrap_err() {
427 ReputationError::ValidationError(ValidationError::InvalidDid(_)) => {}
428 _ => panic!("Expected InvalidDid error to be caught first"),
429 }
430 }
431
432 #[test]
433 fn test_valid_edge_case_ratings() {
434 let calc = Calculator::default();
435
436 let test_ratings = vec![1.0, 5.0, 3.0];
438
439 for rating in test_ratings {
440 let mut agent = create_valid_agent();
441 agent.total_interactions = 50;
442 agent.total_reviews = 50;
443 agent.positive_reviews = 25;
444 agent.negative_reviews = 25;
445 agent.average_rating = Some(rating);
446
447 let result = calc.calculate(&agent);
448 assert!(result.is_ok(), "Rating {} should be valid", rating);
449 }
450 }
451
452 #[test]
453 fn test_zero_confidence_k_prevention() {
454 let result = Calculator::new(0.0, 50.0, 80.0);
455 assert!(result.is_err());
456 }
457
458 #[test]
459 fn test_nan_prevention_in_calculation() {
460 let calc = Calculator::default();
464 let agent = create_valid_agent();
465
466 let result = calc.calculate(&agent);
468 assert!(result.is_ok());
469 let score = result.unwrap();
470 assert!(!score.score.is_nan());
471 assert!(!score.confidence.is_nan());
472 }
473
474 #[test]
475 fn test_all_identity_flags() {
476 let calc = Calculator::default();
477 let mut agent = create_valid_agent();
478
479 agent.identity_verified = true;
481 agent.security_audit_passed = true;
482 agent.open_source = true;
483 agent.mcp_level = Some(3);
484
485 let result = calc.calculate(&agent);
486 assert!(result.is_ok());
487
488 let score = result.unwrap();
490 assert_eq!(score.score, 80.0); }
492
493 #[test]
494 fn test_identity_verified_bonus() {
495 let calc = Calculator::default();
496
497 let mut agent = create_valid_agent();
499 let score_without = calc.calculate(&agent).unwrap();
500
501 agent.identity_verified = true;
503 let score_with = calc.calculate(&agent).unwrap();
504
505 assert_eq!(score_with.score - score_without.score, 5.0);
507 assert_eq!(score_with.score, 55.0); }
509
510 #[test]
511 fn test_security_audit_bonus() {
512 let calc = Calculator::default();
513
514 let mut agent = create_valid_agent();
516 let score_without = calc.calculate(&agent).unwrap();
517
518 agent.security_audit_passed = true;
520 let score_with = calc.calculate(&agent).unwrap();
521
522 assert_eq!(score_with.score - score_without.score, 7.0);
524 assert_eq!(score_with.score, 57.0); }
526
527 #[test]
528 fn test_open_source_bonus() {
529 let calc = Calculator::default();
530
531 let mut agent = create_valid_agent();
533 let score_without = calc.calculate(&agent).unwrap();
534
535 agent.open_source = true;
537 let score_with = calc.calculate(&agent).unwrap();
538
539 assert_eq!(score_with.score - score_without.score, 3.0);
541 assert_eq!(score_with.score, 53.0); }
543
544 #[test]
545 fn test_age_bonus() {
546 let calc = Calculator::default();
547
548 let mut agent = create_valid_agent();
550 agent.created_at = Utc::now() - Duration::days(1);
551 let score_young = calc.calculate(&agent).unwrap();
552
553 agent.created_at = Utc::now() - Duration::days(400);
555 let score_old = calc.calculate(&agent).unwrap();
556
557 assert_eq!(score_old.score - score_young.score, 5.0);
559 assert_eq!(score_young.score, 50.0); assert_eq!(score_old.score, 55.0); }
562
563 #[test]
564 fn test_age_bonus_edge_cases() {
565 let calc = Calculator::default();
566 let mut agent = create_valid_agent();
567
568 agent.created_at = Utc::now() - Duration::days(365);
570 let score_365 = calc.calculate(&agent).unwrap();
571 assert_eq!(score_365.score, 50.0); agent.created_at = Utc::now() - Duration::days(366);
575 let score_366 = calc.calculate(&agent).unwrap();
576 assert_eq!(score_366.score, 55.0); }
578
579 #[test]
580 fn test_combined_bonuses() {
581 let calc = Calculator::default();
582 let mut agent = create_valid_agent();
583
584 agent.mcp_level = Some(2); agent.identity_verified = true; let score = calc.calculate(&agent).unwrap();
588 assert_eq!(score.score, 65.0); agent.open_source = true; let score = calc.calculate(&agent).unwrap();
593 assert_eq!(score.score, 68.0); agent.security_audit_passed = true; let score = calc.calculate(&agent).unwrap();
598 assert_eq!(score.score, 75.0); }
600
601 #[test]
602 fn test_prior_score_cap() {
603 let calc = Calculator::default();
604 let mut agent = create_valid_agent();
605
606 agent.mcp_level = Some(3); agent.identity_verified = true; agent.security_audit_passed = true; agent.open_source = true; agent.created_at = Utc::now() - Duration::days(400); let score = calc.calculate(&agent).unwrap();
615 assert_eq!(score.score, 80.0);
616 }
617
618 #[test]
619 fn test_prior_score_cap_with_interactions() {
620 let calc = Calculator::default();
621 let mut agent = create_valid_agent();
622
623 agent.mcp_level = Some(3);
625 agent.identity_verified = true;
626 agent.security_audit_passed = true;
627 agent.open_source = true;
628 agent.created_at = Utc::now() - Duration::days(400);
629
630 agent.total_interactions = 50;
632 agent.total_reviews = 50;
633 agent.positive_reviews = 45;
634 agent.negative_reviews = 5;
635 agent.average_rating = Some(4.5);
636
637 let score = calc.calculate(&agent).unwrap();
638
639 assert!(score.score > 80.0); assert!(score.score < 90.0);
644 }
645
646 #[test]
647 fn test_enhanced_score_structure() {
648 let calc = Calculator::default();
649 let mut agent = create_valid_agent();
650
651 agent.total_interactions = 50;
653 agent.total_reviews = 30;
654 agent.positive_reviews = 25;
655 agent.negative_reviews = 5;
656 agent.average_rating = Some(4.2);
657 agent.mcp_level = Some(2);
658 agent.identity_verified = true;
659
660 let score = calc.calculate(&agent).unwrap();
661
662 assert_eq!(score.level, ConfidenceLevel::High); assert!(!score.is_provisional); assert_eq!(score.data_points, 80); assert_eq!(score.components.prior_score, 65.0); assert_eq!(score.components.empirical_score, 80.0); assert_eq!(score.components.confidence_level, ConfidenceLevel::High);
671 assert!(score.components.confidence_value > 0.7);
672
673 assert_eq!(score.components.prior_breakdown.base_score, 50.0);
675 assert_eq!(score.components.prior_breakdown.mcp_bonus, 10.0);
676 assert_eq!(score.components.prior_breakdown.identity_bonus, 5.0);
677 assert_eq!(score.components.prior_breakdown.total, 65.0);
678 }
679
680 #[test]
681 fn test_provisional_score() {
682 let calc = Calculator::default();
683 let mut agent = create_valid_agent();
684
685 agent.total_interactions = 2;
687 agent.total_reviews = 2;
688 agent.positive_reviews = 1;
689 agent.negative_reviews = 1;
690 agent.average_rating = Some(3.0);
691
692 let score = calc.calculate(&agent).unwrap();
693
694 assert!(score.is_provisional);
696 assert_eq!(score.level, ConfidenceLevel::Low);
697 assert!(score.confidence < 0.2);
698 }
699
700 #[test]
701 fn test_confidence_level_boundaries() {
702 let calc = Calculator::default();
703 let mut agent = create_valid_agent();
704
705 agent.total_interactions = 3; agent.total_reviews = 3;
708 agent.positive_reviews = 2;
709 agent.negative_reviews = 1;
710 agent.average_rating = Some(4.0);
711 let score = calc.calculate(&agent).unwrap();
712 assert_eq!(score.level, ConfidenceLevel::Low);
713
714 agent.total_interactions = 15; agent.total_reviews = 15;
717 agent.positive_reviews = 12;
718 agent.negative_reviews = 3;
719 let score = calc.calculate(&agent).unwrap();
720 assert_eq!(score.level, ConfidenceLevel::Medium);
721
722 agent.total_interactions = 50; agent.total_reviews = 40;
725 agent.positive_reviews = 32;
726 agent.negative_reviews = 8;
727 let score = calc.calculate(&agent).unwrap();
728 assert_eq!(score.level, ConfidenceLevel::High);
729 }
730
731 #[test]
732 fn test_individual_bonus_independence() {
733 let calc = Calculator::default();
734
735 let base_agent = create_valid_agent();
737 let base_score = calc.calculate(&base_agent).unwrap().score;
738
739 let mut agent = base_agent.clone();
741 agent.identity_verified = true;
742 let score = calc.calculate(&agent).unwrap().score;
743 assert_eq!(score - base_score, 5.0, "Identity bonus should be 5");
744
745 let mut agent = base_agent.clone();
747 agent.security_audit_passed = true;
748 let score = calc.calculate(&agent).unwrap().score;
749 assert_eq!(score - base_score, 7.0, "Security audit bonus should be 7");
750
751 let mut agent = base_agent.clone();
753 agent.open_source = true;
754 let score = calc.calculate(&agent).unwrap().score;
755 assert_eq!(score - base_score, 3.0, "Open source bonus should be 3");
756
757 let mut agent = base_agent.clone();
759 agent.mcp_level = Some(1);
760 let score = calc.calculate(&agent).unwrap().score;
761 assert_eq!(score - base_score, 5.0, "MCP level 1 bonus should be 5");
762
763 let mut agent = base_agent.clone();
765 agent.created_at = Utc::now() - Duration::days(400);
766 let score = calc.calculate(&agent).unwrap().score;
767 assert_eq!(score - base_score, 5.0, "Age bonus should be 5");
768 }
769}