1pub mod config;
31pub mod config_manager;
32pub mod health;
33pub mod site_waf;
34pub mod tls;
35pub mod utils;
36pub mod vhost;
37
38pub mod access;
40pub mod admin_server;
41pub mod api;
42pub mod intelligence;
43pub mod metrics;
44pub mod ratelimit;
45pub mod reload;
46
47pub mod dlp;
49pub mod entity;
50pub mod fingerprint;
51pub mod tarpit;
52
53pub mod sni_validation;
55pub mod validation;
56
57pub mod persistence;
59
60pub mod signals;
62pub mod telemetry;
63
64pub mod trap;
66
67pub mod correlation;
69
70pub mod actor;
72
73pub mod session;
75
76pub mod interrogator;
78
79pub mod shadow;
81
82pub mod profiler;
84
85pub mod crawler;
87pub mod horizon;
88pub mod payload;
89pub mod trends;
90pub mod tunnel;
91
92pub mod detection;
94pub mod geo;
95pub mod waf;
96
97pub mod block_log;
99pub mod tui;
100
101pub mod headers;
103
104pub mod body;
106
107pub mod block_page;
109
110pub use config::{ConfigFile, ConfigLoader, GlobalConfig};
112pub use health::{HealthChecker, HealthResponse, HealthStatus};
113pub use site_waf::{SiteWafConfig, SiteWafManager, WafAction};
114pub use tls::{TlsManager, TlsVersion};
115pub use vhost::{SiteConfig, VhostMatcher};
116
117pub use access::{AccessDecision, AccessList, AccessListManager};
119pub use api::{ApiHandler, ApiResponse, EvaluateResult};
120pub use metrics::{BandwidthDataPoint, BandwidthStats, MetricsRegistry, ProfilingMetrics};
121pub use ratelimit::{RateLimitConfig, RateLimitDecision, RateLimitManager};
122pub use reload::{ConfigReloader, ReloadResult};
123
124pub use dlp::{
126 validate_credit_card, validate_iban, validate_phone, validate_ssn, DlpConfig, DlpMatch,
127 DlpScanner, DlpStats, PatternSeverity, ScanResult, SensitiveDataType,
128};
129pub use entity::{
130 BlockDecision, EntityConfig, EntityManager, EntityMetrics, EntitySnapshot, EntityState,
131 RiskApplication,
132};
133pub use fingerprint::{
134 analyze_ja4, analyze_ja4h, extract_client_fingerprint, generate_ja4h, parse_ja4_from_header,
135 ClientFingerprint, HttpHeaders, Ja4Analysis, Ja4Fingerprint, Ja4Protocol, Ja4SniType,
136 Ja4hAnalysis, Ja4hFingerprint,
137};
138pub use tarpit::{TarpitConfig, TarpitDecision, TarpitManager, TarpitState, TarpitStats};
139
140pub use validation::{
142 validate_certificate_file, validate_domain_name, validate_private_key_file,
143 validate_tls_config, ValidationError, ValidationResult,
144};
145
146pub use sni_validation::{
148 SniValidationConfig, SniValidationMode, SniValidationResult, SniValidator,
149};
150
151pub use trap::{TrapConfig, TrapMatcher};
153
154pub use block_log::{BlockEvent, BlockLog};
156
157pub use actor::{ActorConfig, ActorManager, ActorState, ActorStats, RuleMatch};
159
160pub use session::{
162 HijackAlert, HijackType, SessionConfig, SessionDecision, SessionManager, SessionState,
163 SessionStats,
164};
165
166pub use interrogator::{
168 ActorChallengeState, ChallengeLevel, ChallengeResponse, CookieChallenge, CookieConfig,
169 CookieManager, CookieStats, Interrogator, JsChallenge, JsChallengeConfig, JsChallengeManager,
170 JsChallengeStats, ProgressionConfig, ProgressionManager, ProgressionStats,
171 ValidationResult as ChallengeValidationResult,
172};
173
174pub use shadow::{
176 MirrorPayload, RateLimiter as ShadowRateLimiter, RateLimiterStats as ShadowRateLimiterStats,
177 ShadowClientStats, ShadowMirrorClient, ShadowMirrorConfig, ShadowMirrorError,
178 ShadowMirrorManager, ShadowMirrorStats,
179};
180
181pub use profiler::{
183 detect_pattern,
184 entropy_z_score,
185 is_entropy_anomaly,
186 matches_pattern,
187 normalized_entropy,
188 shannon_entropy,
189 AnomalyResult,
190 AnomalySignal,
191 AnomalySignalType,
192 Distribution,
193 EndpointProfile,
194 FieldSchema,
195 FieldType,
196 HeaderAnomaly,
197 HeaderAnomalyResult,
198 HeaderBaseline,
199 HeaderProfiler,
201 HeaderProfilerStats,
202 JsonEndpointSchema,
203 ParameterSchema,
204 PatternType,
205 PercentilesTracker,
206 ProfileStore,
207 ProfileStoreConfig,
208 ProfileStoreMetrics,
209 Profiler,
210 RateTracker,
211 SchemaLearner,
213 SchemaLearnerConfig,
214 SchemaLearnerStats,
215 SchemaViolation,
216 SegmentCardinality,
217 ValueStats,
218 ViolationSeverity,
219 ViolationType,
220};
221
222pub use config::ProfilerConfig;
224
225pub use crawler::{
227 BadBotSeverity, BadBotSignature, CrawlerConfig, CrawlerDefinition, CrawlerDetection,
228 CrawlerDetector, CrawlerStats, CrawlerStatsSnapshot, CrawlerVerificationResult,
229 DnsFailurePolicy, VerificationMethod,
230};
231
232pub use horizon::{
234 BlockType, BlocklistCache, BlocklistEntry, BlocklistUpdate, ClientStats, ConnectionState,
235 HorizonClient, HorizonConfig, HorizonError, HorizonManager, HorizonStats, HorizonStatsSnapshot,
236 Severity, SignalType, ThreatSignal,
237};
238
239pub use payload::{
241 BandwidthBucket, EndpointPayloadStats, EndpointPayloadStatsSnapshot, EndpointSortBy,
242 EntityBandwidth, PayloadAnomaly, PayloadAnomalyMetadata, PayloadAnomalySeverity,
243 PayloadAnomalyType, PayloadConfig, PayloadManager, PayloadSummary, PayloadWindow, SizeStats,
244};
245
246pub use trends::{
248 Anomaly, AnomalyDetector, AnomalyDetectorConfig, AnomalyMetadata, AnomalyQueryOptions,
249 AnomalySeverity, AnomalyType, BucketSummary, CategorySummary, Correlation, CorrelationEngine,
250 CorrelationMetadata, CorrelationType, Signal, SignalBucket, SignalCategory, SignalExtractor,
251 SignalMetadata, SignalTrend, SignalType as TrendsSignalType, TimeStore, TimeStoreStats,
252 TrendHistogramBucket, TrendQueryOptions, TrendsConfig, TrendsManager, TrendsManagerStats,
253 TrendsStats, TrendsSummary,
254};
255
256pub use intelligence::{
258 Signal as IntelligenceSignal, SignalCategory as IntelligenceSignalCategory, SignalManager,
259 SignalManagerConfig, SignalQueryOptions, SignalSummary as IntelligenceSignalSummary,
260 TopSignalType as IntelligenceTopSignalType,
261};
262
263pub use geo::{
265 calculate_speed, haversine_distance, is_valid_coordinates, GeoLocation,
266 ImpossibleTravelDetector, LoginEvent, Severity as GeoSeverity, TravelAlert, TravelConfig,
267 TravelStats,
268};
269
270pub use waf::{
272 boolean_operands, build_rule_index, get_candidate_rule_indices, method_to_mask, now_ms,
273 repeat_multiplier, Action as WafRuleAction, AnomalyContribution as WafAnomalyContribution,
274 AnomalySignal as WafAnomalySignal, AnomalySignalType as WafAnomalySignalType,
275 AnomalyType as WafAnomalyType, ArgEntry, BlockingMode as WafBlockingMode, CandidateCache,
276 CandidateCacheKey, Engine as WafEngine, EvalContext, Header as WafHeader, IndexedRule,
277 MatchCondition, MatchValue, Request as WafRequest, RiskConfig as WafRiskConfig,
278 RiskContribution as WafRiskContribution, RuleIndex, StateStore, Synapse, Verdict as WafVerdict,
279 WafError, WafRule,
280};
281
282pub use detection::{
284 AuthAttempt, AuthMetrics, AuthResult, CredentialStuffingDetector, DistributedAttack,
285 EntityEndpointKey, StuffingConfig, StuffingEvent, StuffingSeverity, StuffingState,
286 StuffingStats, StuffingVerdict, TakeoverAlert,
287};
288
289#[cfg(test)]
296mod actor_session_integration_tests {
297 use super::*;
298 use std::net::IpAddr;
299 use std::sync::Arc;
300
301 fn create_test_actor_manager() -> Arc<ActorManager> {
306 Arc::new(ActorManager::new(ActorConfig {
307 max_actors: 1000,
308 decay_interval_secs: 900,
309 correlation_threshold: 0.7,
310 risk_decay_factor: 0.9,
311 max_rule_matches: 100,
312 max_session_ids: 50,
313 enabled: true,
314 max_risk: 100.0,
315 persist_interval_secs: 300,
316 max_fingerprints_per_actor: 20,
317 max_fingerprint_mappings: 500_000,
318 }))
319 }
320
321 fn create_test_session_manager() -> Arc<SessionManager> {
322 Arc::new(SessionManager::new(SessionConfig {
323 max_sessions: 1000,
324 session_ttl_secs: 3600,
325 idle_timeout_secs: 900,
326 cleanup_interval_secs: 300,
327 enable_ja4_binding: true,
328 enable_ip_binding: false,
329 ja4_mismatch_threshold: 1,
330 ip_change_window_secs: 60,
331 max_alerts_per_session: 10,
332 enabled: true,
333 }))
334 }
335
336 fn create_test_ip(last_octet: u8) -> IpAddr {
337 format!("192.168.1.{}", last_octet).parse().unwrap()
338 }
339
340 #[test]
345 fn test_request_with_ip_creates_actor() {
346 let actor_manager = create_test_actor_manager();
347 let ip = create_test_ip(100);
348
349 let actor_id = actor_manager.get_or_create_actor(ip, None);
350
351 assert!(!actor_id.is_empty());
352 assert_eq!(actor_manager.len(), 1);
353
354 let actor = actor_manager.get_actor(&actor_id).unwrap();
355 assert!(actor.ips.contains(&ip));
356 assert!(!actor.is_blocked);
357 assert_eq!(actor.risk_score, 0.0);
358 }
359
360 #[test]
361 fn test_request_with_ip_and_fingerprint_creates_actor() {
362 let actor_manager = create_test_actor_manager();
363 let ip = create_test_ip(101);
364 let fingerprint = "t13d1516h2_abc123_ja4hash";
365
366 let actor_id = actor_manager.get_or_create_actor(ip, Some(fingerprint));
367
368 let actor = actor_manager.get_actor(&actor_id).unwrap();
369 assert!(actor.ips.contains(&ip));
370 assert!(actor.fingerprints.contains(fingerprint));
371 }
372
373 #[test]
374 fn test_multiple_ips_correlated_via_fingerprint() {
375 let actor_manager = create_test_actor_manager();
376 let ip1 = create_test_ip(1);
377 let ip2 = create_test_ip(2);
378 let ip3 = create_test_ip(3);
379 let shared_fingerprint = "t13d1516h2_shared_fingerprint";
380
381 let actor_id1 = actor_manager.get_or_create_actor(ip1, Some(shared_fingerprint));
382 let actor_id2 = actor_manager.get_or_create_actor(ip2, Some(shared_fingerprint));
383 let actor_id3 = actor_manager.get_or_create_actor(ip3, Some(shared_fingerprint));
384
385 assert_eq!(actor_id1, actor_id2);
386 assert_eq!(actor_id2, actor_id3);
387 assert_eq!(actor_manager.len(), 1);
388
389 let actor = actor_manager.get_actor(&actor_id1).unwrap();
390 assert!(actor.ips.contains(&ip1));
391 assert!(actor.ips.contains(&ip2));
392 assert!(actor.ips.contains(&ip3));
393 assert_eq!(actor.ips.len(), 3);
394 }
395
396 #[test]
397 fn test_same_ip_subsequent_requests_correlate() {
398 let actor_manager = create_test_actor_manager();
399 let ip = create_test_ip(50);
400
401 let actor_id1 = actor_manager.get_or_create_actor(ip, None);
402 let actor_id2 = actor_manager.get_or_create_actor(ip, None);
403 let actor_id3 = actor_manager.get_or_create_actor(ip, None);
404
405 assert_eq!(actor_id1, actor_id2);
406 assert_eq!(actor_id2, actor_id3);
407 assert_eq!(actor_manager.len(), 1);
408 }
409
410 #[test]
411 fn test_different_ips_without_fingerprint_create_separate_actors() {
412 let actor_manager = create_test_actor_manager();
413 let ip1 = create_test_ip(10);
414 let ip2 = create_test_ip(20);
415
416 let actor_id1 = actor_manager.get_or_create_actor(ip1, None);
417 let actor_id2 = actor_manager.get_or_create_actor(ip2, None);
418
419 assert_ne!(actor_id1, actor_id2);
420 assert_eq!(actor_manager.len(), 2);
421 }
422
423 #[test]
428 fn test_matched_rules_recorded_to_actor_history() {
429 let actor_manager = create_test_actor_manager();
430 let ip = create_test_ip(100);
431
432 let actor_id = actor_manager.get_or_create_actor(ip, None);
433 actor_manager.record_rule_match(&actor_id, "sqli-001", 25.0, "sqli");
434
435 let actor = actor_manager.get_actor(&actor_id).unwrap();
436 assert_eq!(actor.rule_matches.len(), 1);
437 assert_eq!(actor.rule_matches[0].rule_id, "sqli-001");
438 assert_eq!(actor.rule_matches[0].category, "sqli");
439 assert_eq!(actor.rule_matches[0].risk_contribution, 25.0);
440 }
441
442 #[test]
443 fn test_risk_score_accumulates_correctly() {
444 let actor_manager = create_test_actor_manager();
445 let ip = create_test_ip(101);
446
447 let actor_id = actor_manager.get_or_create_actor(ip, None);
448
449 actor_manager.record_rule_match(&actor_id, "sqli-001", 25.0, "sqli");
450 actor_manager.record_rule_match(&actor_id, "xss-001", 20.0, "xss");
451 actor_manager.record_rule_match(&actor_id, "path-001", 15.0, "path_traversal");
452
453 let actor = actor_manager.get_actor(&actor_id).unwrap();
454 assert_eq!(actor.risk_score, 60.0);
455 assert_eq!(actor.rule_matches.len(), 3);
456 }
457
458 #[test]
459 fn test_risk_score_capped_at_max() {
460 let actor_manager = create_test_actor_manager();
461 let ip = create_test_ip(102);
462
463 let actor_id = actor_manager.get_or_create_actor(ip, None);
464
465 for i in 0..15 {
466 actor_manager.record_rule_match(&actor_id, &format!("rule-{}", i), 10.0, "attack");
467 }
468
469 let actor = actor_manager.get_actor(&actor_id).unwrap();
470 assert!(actor.risk_score <= 100.0);
471 assert_eq!(actor.risk_score, 100.0);
472 }
473
474 #[test]
475 fn test_category_mapping_works() {
476 let actor_manager = create_test_actor_manager();
477 let ip = create_test_ip(103);
478
479 let actor_id = actor_manager.get_or_create_actor(ip, None);
480
481 actor_manager.record_rule_match(&actor_id, "rule_940001", 10.0, "sqli");
482 actor_manager.record_rule_match(&actor_id, "rule_941001", 10.0, "xss");
483 actor_manager.record_rule_match(&actor_id, "rule_930001", 10.0, "path_traversal");
484 actor_manager.record_rule_match(&actor_id, "rule_932001", 10.0, "rce");
485 actor_manager.record_rule_match(&actor_id, "rule_913001", 10.0, "scanner");
486
487 let actor = actor_manager.get_actor(&actor_id).unwrap();
488 let categories: Vec<&str> = actor
489 .rule_matches
490 .iter()
491 .map(|m| m.category.as_str())
492 .collect();
493 assert!(categories.contains(&"sqli"));
494 assert!(categories.contains(&"xss"));
495 assert!(categories.contains(&"path_traversal"));
496 assert!(categories.contains(&"rce"));
497 assert!(categories.contains(&"scanner"));
498 }
499
500 #[test]
505 fn test_high_risk_actor_gets_blocked() {
506 let actor_manager = create_test_actor_manager();
507 let ip = create_test_ip(100);
508
509 let actor_id = actor_manager.get_or_create_actor(ip, None);
510 assert!(!actor_manager.is_blocked(&actor_id));
511
512 let blocked = actor_manager.block_actor(&actor_id, "High risk score exceeded threshold");
513
514 assert!(blocked);
515 assert!(actor_manager.is_blocked(&actor_id));
516
517 let actor = actor_manager.get_actor(&actor_id).unwrap();
518 assert!(actor.is_blocked);
519 assert_eq!(
520 actor.block_reason,
521 Some("High risk score exceeded threshold".to_string())
522 );
523 assert!(actor.blocked_since.is_some());
524 }
525
526 #[test]
527 fn test_block_decision_enforced_in_subsequent_requests() {
528 let actor_manager = create_test_actor_manager();
529 let ip = create_test_ip(101);
530 let fingerprint = "blocked_actor_fingerprint";
531
532 let actor_id = actor_manager.get_or_create_actor(ip, Some(fingerprint));
533 actor_manager.block_actor(&actor_id, "Malicious activity detected");
534
535 let actor_id2 = actor_manager.get_or_create_actor(ip, Some(fingerprint));
536 assert_eq!(actor_id, actor_id2);
537 assert!(actor_manager.is_blocked(&actor_id2));
538
539 let ip2 = create_test_ip(102);
540 let actor_id3 = actor_manager.get_or_create_actor(ip2, Some(fingerprint));
541 assert_eq!(actor_id, actor_id3);
542 assert!(actor_manager.is_blocked(&actor_id3));
543 }
544
545 #[test]
546 fn test_unblock_actor() {
547 let actor_manager = create_test_actor_manager();
548 let ip = create_test_ip(103);
549
550 let actor_id = actor_manager.get_or_create_actor(ip, None);
551
552 actor_manager.block_actor(&actor_id, "Test block");
553 assert!(actor_manager.is_blocked(&actor_id));
554
555 let unblocked = actor_manager.unblock_actor(&actor_id);
556 assert!(unblocked);
557 assert!(!actor_manager.is_blocked(&actor_id));
558
559 let actor = actor_manager.get_actor(&actor_id).unwrap();
560 assert!(!actor.is_blocked);
561 assert!(actor.block_reason.is_none());
562 assert!(actor.blocked_since.is_none());
563 }
564
565 #[test]
566 fn test_list_blocked_actors() {
567 let actor_manager = create_test_actor_manager();
568
569 for i in 0..10 {
570 let ip = create_test_ip(i);
571 let actor_id = actor_manager.get_or_create_actor(ip, None);
572 if i % 2 == 0 {
573 actor_manager.block_actor(&actor_id, &format!("Blocked actor {}", i));
574 }
575 }
576
577 let blocked = actor_manager.list_blocked_actors();
578 assert_eq!(blocked.len(), 5);
579
580 for actor in blocked {
581 assert!(actor.is_blocked);
582 }
583 }
584
585 #[test]
586 fn test_blocking_updates_statistics() {
587 let actor_manager = create_test_actor_manager();
588 let ip = create_test_ip(105);
589
590 let actor_id = actor_manager.get_or_create_actor(ip, None);
591
592 let stats = actor_manager.stats().snapshot();
593 assert_eq!(stats.blocked_actors, 0);
594
595 actor_manager.block_actor(&actor_id, "Test");
596
597 let stats = actor_manager.stats().snapshot();
598 assert_eq!(stats.blocked_actors, 1);
599
600 actor_manager.unblock_actor(&actor_id);
601
602 let stats = actor_manager.stats().snapshot();
603 assert_eq!(stats.blocked_actors, 0);
604 }
605
606 #[test]
611 fn test_session_token_extraction_and_validation() {
612 let session_manager = create_test_session_manager();
613 let ip = create_test_ip(100);
614 let token_hash = "abc123def456";
615
616 let decision = session_manager.validate_request(token_hash, ip, None);
617 assert_eq!(decision, SessionDecision::New);
618 assert_eq!(session_manager.len(), 1);
619
620 let decision = session_manager.validate_request(token_hash, ip, None);
621 assert_eq!(decision, SessionDecision::Valid);
622 assert_eq!(session_manager.len(), 1);
623 }
624
625 #[test]
626 fn test_valid_session_passes() {
627 let session_manager = create_test_session_manager();
628 let ip = create_test_ip(101);
629 let token_hash = "valid_session_hash";
630 let ja4 = "t13d1516h2_fingerprint";
631
632 session_manager.create_session(token_hash, ip, Some(ja4));
633
634 let decision = session_manager.validate_request(token_hash, ip, Some(ja4));
635 assert_eq!(decision, SessionDecision::Valid);
636 }
637
638 #[test]
639 fn test_ja4_mismatch_triggers_alert() {
640 let session_manager = create_test_session_manager();
641 let ip = create_test_ip(102);
642 let token_hash = "session_for_hijack_test";
643 let original_ja4 = "t13d1516h2_original_fingerprint";
644 let new_ja4 = "t13d1516h2_different_fingerprint";
645
646 session_manager.create_session(token_hash, ip, Some(original_ja4));
647
648 let decision = session_manager.validate_request(token_hash, ip, Some(new_ja4));
649
650 match decision {
651 SessionDecision::Suspicious(alert) => {
652 assert_eq!(alert.alert_type, HijackType::Ja4Mismatch);
653 assert_eq!(alert.original_value, original_ja4);
654 assert_eq!(alert.new_value, new_ja4);
655 assert!(
656 alert.confidence >= 0.9,
657 "JA4 mismatch should have high confidence"
658 );
659 }
660 _ => panic!(
661 "Expected Suspicious decision for JA4 mismatch, got {:?}",
662 decision
663 ),
664 }
665 }
666
667 #[test]
668 fn test_expired_session_detected() {
669 let config = SessionConfig {
670 session_ttl_secs: 0, idle_timeout_secs: 3600,
672 ..SessionConfig::default()
673 };
674 let session_manager = Arc::new(SessionManager::new(config));
675 let ip = create_test_ip(103);
676 let token_hash = "expiring_session";
677
678 session_manager.create_session(token_hash, ip, None);
679 std::thread::sleep(std::time::Duration::from_millis(10));
680
681 let decision = session_manager.validate_request(token_hash, ip, None);
682 assert_eq!(decision, SessionDecision::Expired);
683 }
684
685 #[test]
686 fn test_session_request_count_increments() {
687 let session_manager = create_test_session_manager();
688 let ip = create_test_ip(104);
689 let token_hash = "counting_session";
690
691 session_manager.validate_request(token_hash, ip, None); session_manager.validate_request(token_hash, ip, None); session_manager.validate_request(token_hash, ip, None); session_manager.validate_request(token_hash, ip, None); let session = session_manager.get_session(token_hash).unwrap();
697 assert_eq!(session.request_count, 4);
698 }
699
700 #[test]
701 fn test_first_ja4_binds_to_session() {
702 let session_manager = create_test_session_manager();
703 let ip = create_test_ip(105);
704 let token_hash = "binding_session";
705 let ja4 = "t13d1516h2_bound_fingerprint";
706
707 session_manager.create_session(token_hash, ip, None);
708
709 let session = session_manager.get_session(token_hash).unwrap();
710 assert!(session.bound_ja4.is_none());
711
712 session_manager.validate_request(token_hash, ip, Some(ja4));
713
714 let session = session_manager.get_session(token_hash).unwrap();
715 assert_eq!(session.bound_ja4, Some(ja4.to_string()));
716 }
717
718 #[test]
719 fn test_session_with_no_ja4_binding_allows_any_fingerprint() {
720 let config = SessionConfig {
721 enable_ja4_binding: false,
722 ..SessionConfig::default()
723 };
724 let session_manager = Arc::new(SessionManager::new(config));
725 let ip = create_test_ip(106);
726 let token_hash = "unbound_session";
727
728 session_manager.create_session(token_hash, ip, Some("original_ja4"));
729
730 let decision = session_manager.validate_request(token_hash, ip, Some("different_ja4"));
731 assert_eq!(decision, SessionDecision::Valid);
732 }
733
734 #[test]
739 fn test_get_actors_returns_real_data() {
740 let actor_manager = create_test_actor_manager();
741
742 for i in 0..5 {
743 let ip = create_test_ip(i);
744 let actor_id = actor_manager.get_or_create_actor(ip, None);
745 actor_manager.record_rule_match(&actor_id, &format!("rule-{}", i), 10.0, "test");
746 }
747
748 let api_handler = api::ApiHandler::builder()
749 .actor_manager(Arc::clone(&actor_manager))
750 .build();
751
752 let actors = api_handler.handle_list_actors(10);
753
754 assert_eq!(actors.len(), 5);
755 for actor in &actors {
756 assert!(!actor.actor_id.is_empty());
757 assert_eq!(actor.rule_matches.len(), 1);
758 assert_eq!(actor.risk_score, 10.0);
759 }
760 }
761
762 #[test]
763 fn test_get_sessions_returns_real_data() {
764 let session_manager = create_test_session_manager();
765
766 for i in 0..5 {
767 let ip = create_test_ip(i);
768 let token_hash = format!("session_token_{}", i);
769 session_manager.create_session(&token_hash, ip, Some(&format!("ja4_{}", i)));
770 }
771
772 let api_handler = api::ApiHandler::builder()
773 .session_manager(Arc::clone(&session_manager))
774 .build();
775
776 let sessions = api_handler.handle_list_sessions(10);
777
778 assert_eq!(sessions.len(), 5);
779 for session in &sessions {
780 assert!(!session.session_id.is_empty());
781 assert!(session.session_id.starts_with("sess-"));
782 assert!(session.bound_ja4.is_some());
783 }
784 }
785
786 #[test]
787 fn test_get_actor_stats_returns_real_data() {
788 let actor_manager = create_test_actor_manager();
789
790 for i in 0..10 {
791 let ip = create_test_ip(i);
792 let actor_id = actor_manager.get_or_create_actor(ip, None);
793 actor_manager.record_rule_match(&actor_id, "test-rule", 5.0, "test");
794 if i % 3 == 0 {
795 actor_manager.block_actor(&actor_id, "Test block");
796 }
797 }
798
799 let api_handler = api::ApiHandler::builder()
800 .actor_manager(Arc::clone(&actor_manager))
801 .build();
802
803 let stats = api_handler.handle_actor_stats();
804
805 assert!(stats.is_some());
806 let stats = stats.unwrap();
807 assert_eq!(stats.total_actors, 10);
808 assert_eq!(stats.blocked_actors, 4); assert_eq!(stats.total_created, 10);
810 assert_eq!(stats.total_rule_matches, 10);
811 }
812
813 #[test]
814 fn test_get_session_stats_returns_real_data() {
815 let session_manager = create_test_session_manager();
816
817 for i in 0..5 {
818 let ip = create_test_ip(i);
819 let token_hash = format!("session_{}", i);
820 session_manager.create_session(&token_hash, ip, None);
821 }
822
823 let api_handler = api::ApiHandler::builder()
824 .session_manager(Arc::clone(&session_manager))
825 .build();
826
827 let stats = api_handler.handle_session_stats();
828
829 assert!(stats.is_some());
830 let stats = stats.unwrap();
831 assert_eq!(stats.total_sessions, 5);
832 assert_eq!(stats.active_sessions, 5);
833 assert_eq!(stats.total_created, 5);
834 }
835
836 #[test]
837 fn test_api_handler_without_managers_returns_empty() {
838 let api_handler = api::ApiHandler::builder().build();
839
840 let actors = api_handler.handle_list_actors(10);
841 assert!(actors.is_empty());
842
843 let sessions = api_handler.handle_list_sessions(10);
844 assert!(sessions.is_empty());
845
846 let actor_stats = api_handler.handle_actor_stats();
847 assert!(actor_stats.is_none());
848
849 let session_stats = api_handler.handle_session_stats();
850 assert!(session_stats.is_none());
851 }
852
853 #[test]
854 fn test_list_actors_respects_limit() {
855 let actor_manager = create_test_actor_manager();
856
857 for i in 0..20 {
858 let ip = create_test_ip(i);
859 actor_manager.get_or_create_actor(ip, None);
860 }
861
862 let api_handler = api::ApiHandler::builder()
863 .actor_manager(Arc::clone(&actor_manager))
864 .build();
865
866 let actors = api_handler.handle_list_actors(5);
867 assert_eq!(actors.len(), 5);
868
869 let actors = api_handler.handle_list_actors(100);
870 assert_eq!(actors.len(), 20);
871 }
872
873 #[test]
878 fn test_session_bound_to_actor() {
879 let actor_manager = create_test_actor_manager();
880 let session_manager = create_test_session_manager();
881 let ip = create_test_ip(100);
882 let fingerprint = "combined_test_fingerprint";
883 let token_hash = "combined_session_token";
884
885 let actor_id = actor_manager.get_or_create_actor(ip, Some(fingerprint));
886 session_manager.create_session(token_hash, ip, Some(fingerprint));
887 session_manager.bind_to_actor(token_hash, &actor_id);
888
889 let session = session_manager.get_session(token_hash).unwrap();
890 assert_eq!(session.actor_id, Some(actor_id.clone()));
891
892 let actor_sessions = session_manager.get_actor_sessions(&actor_id);
893 assert_eq!(actor_sessions.len(), 1);
894 assert_eq!(actor_sessions[0].token_hash, token_hash);
895 }
896
897 #[test]
898 fn test_multi_session_single_actor() {
899 let actor_manager = create_test_actor_manager();
900 let session_manager = create_test_session_manager();
901 let ip = create_test_ip(101);
902 let fingerprint = "multi_session_fingerprint";
903
904 let actor_id = actor_manager.get_or_create_actor(ip, Some(fingerprint));
905
906 session_manager.create_session("session_tab_1", ip, Some(fingerprint));
907 session_manager.create_session("session_tab_2", ip, Some(fingerprint));
908 session_manager.create_session("session_mobile", ip, Some(fingerprint));
909
910 session_manager.bind_to_actor("session_tab_1", &actor_id);
911 session_manager.bind_to_actor("session_tab_2", &actor_id);
912 session_manager.bind_to_actor("session_mobile", &actor_id);
913
914 let actor_sessions = session_manager.get_actor_sessions(&actor_id);
915 assert_eq!(actor_sessions.len(), 3);
916 }
917
918 #[test]
919 fn test_blocked_actor_affects_all_sessions() {
920 let actor_manager = create_test_actor_manager();
921 let session_manager = create_test_session_manager();
922 let ip = create_test_ip(102);
923 let fingerprint = "blocked_user_fingerprint";
924
925 let actor_id = actor_manager.get_or_create_actor(ip, Some(fingerprint));
926 session_manager.create_session("blocked_user_session_1", ip, Some(fingerprint));
927 session_manager.create_session("blocked_user_session_2", ip, Some(fingerprint));
928 session_manager.bind_to_actor("blocked_user_session_1", &actor_id);
929 session_manager.bind_to_actor("blocked_user_session_2", &actor_id);
930
931 actor_manager.block_actor(&actor_id, "Malicious activity");
932
933 assert!(actor_manager.is_blocked(&actor_id));
934
935 let sessions = session_manager.get_actor_sessions(&actor_id);
936 assert_eq!(sessions.len(), 2);
937
938 let actor_id_check = actor_manager.get_or_create_actor(ip, Some(fingerprint));
939 assert_eq!(actor_id, actor_id_check);
940 assert!(actor_manager.is_blocked(&actor_id_check));
941 }
942
943 #[test]
944 fn test_risk_accumulation_workflow() {
945 let actor_manager = create_test_actor_manager();
946 let ip = create_test_ip(103);
947
948 let actor_id = actor_manager.get_or_create_actor(ip, None);
949
950 actor_manager.record_rule_match(&actor_id, "sqli-001", 30.0, "sqli");
951 let actor = actor_manager.get_actor(&actor_id).unwrap();
952 assert_eq!(actor.risk_score, 30.0);
953 assert!(!actor_manager.is_blocked(&actor_id));
954
955 actor_manager.record_rule_match(&actor_id, "sqli-002", 40.0, "sqli");
956 let actor = actor_manager.get_actor(&actor_id).unwrap();
957 assert_eq!(actor.risk_score, 70.0);
958 assert!(!actor_manager.is_blocked(&actor_id));
959
960 actor_manager.record_rule_match(&actor_id, "xss-001", 20.0, "xss");
961 let actor = actor_manager.get_actor(&actor_id).unwrap();
962 assert_eq!(actor.risk_score, 90.0);
963
964 if actor.risk_score >= 80.0 {
965 actor_manager.block_actor(&actor_id, "Risk threshold exceeded");
966 }
967
968 assert!(actor_manager.is_blocked(&actor_id));
969 }
970}