# frozen_string_literal: true require "test_helper" class PathScannerDetectorJobTest < ActiveJob::TestCase setup do @project = Project.first || #Project.create!( name: "Test Project", slug: "test-project", public_key: SecureRandom.hex(16) ) end test "creates ban rule for IP hitting scanner paths" do ip = "192.168.1.100" # Create events hitting scanner paths ["/.env", "/.git", "/wp-admin"].each do |path| Event.create!( project: @project, event_id: SecureRandom.uuid, timestamp: Time.current, ip_address: ip, request_path: path, waf_action: "allow" ) end count = PathScannerDetectorJob.perform_now assert_equal 1, count rule = Rule.where(source: "auto:scanner_detected").last assert_not_nil rule assert_equal "network_v4", rule.rule_type assert_equal "deny", rule.action assert_equal "#{ip}/32", rule.cidr assert_equal 32, rule.priority assert rule.enabled? end test "sets 24 hour expiration on ban rules" do ip = "192.168.1.100" 3.times do |i| Event.create!( project: @project, event_id: SecureRandom.uuid, timestamp: Time.current, ip_address: ip, request_path: "/.env", waf_action: "allow" ) end PathScannerDetectorJob.perform_now rule = Rule.where(source: "auto:scanner_detected").last assert_not_nil rule.expires_at # Should expire in approximately 24 hours time_until_expiry = rule.expires_at - Time.current assert time_until_expiry > 23.hours assert time_until_expiry < 25.hours end test "includes metadata about detected paths" do ip = "192.168.1.100" paths = ["/.env", "/.git", "/wp-admin"] paths.each do |path| Event.create!( project: @project, event_id: SecureRandom.uuid, timestamp: Time.current, ip_address: ip, request_path: path, waf_action: "allow" ) end PathScannerDetectorJob.perform_now rule = Rule.where(source: "auto:scanner_detected").last assert_equal 3, rule.metadata["hit_count"] assert_equal paths.sort, rule.metadata["paths"].sort assert rule.metadata["reason"].include?("Scanner detected") assert rule.metadata["auto_generated"] end test "does not create rule for insufficient hits" do ip = "192.168.1.100" # Only 2 hits, minimum is 3 2.times do Event.create!( project: @project, event_id: SecureRandom.uuid, timestamp: Time.current, ip_address: ip, request_path: "/.env", waf_action: "allow" ) end count = PathScannerDetectorJob.perform_now assert_equal 0, count end test "only considers recent events" do ip = "192.168.1.100" # Old event (outside lookback window) old_event = Event.create!( project: @project, event_id: SecureRandom.uuid, timestamp: 10.minutes.ago, ip_address: ip, request_path: "/.env", waf_action: "allow" ) # Recent events 2.times do Event.create!( project: @project, event_id: SecureRandom.uuid, timestamp: Time.current, ip_address: ip, request_path: "/.git", waf_action: "allow" ) end count = PathScannerDetectorJob.perform_now # Should not find sufficient hits (only 2 recent, 1 old) assert_equal 0, count end test "does not create duplicate rules for existing IP" do ip = "192.168.1.100" # Create existing rule Rule.create!( rule_type: "network_v4", action: "deny", conditions: { cidr: "#{ip}/32" }, enabled: true ) # Create scanner events 3.times do Event.create!( project: @project, event_id: SecureRandom.uuid, timestamp: Time.current, ip_address: ip, request_path: "/.env", waf_action: "allow" ) end count = PathScannerDetectorJob.perform_now assert_equal 0, count end test "handles IPv6 addresses" do ip = "2001:db8::1" 3.times do Event.create!( project: @project, event_id: SecureRandom.uuid, timestamp: Time.current, ip_address: ip, request_path: "/.env", waf_action: "allow" ) end count = PathScannerDetectorJob.perform_now assert_equal 1, count rule = Rule.where(source: "auto:scanner_detected").last assert_equal "network_v6", rule.rule_type assert_equal "#{ip}/32", rule.cidr end test "creates separate rules for different IPs" do ip1 = "192.168.1.100" ip2 = "192.168.1.101" [ip1, ip2].each do |ip| 3.times do Event.create!( project: @project, event_id: SecureRandom.uuid, timestamp: Time.current, ip_address: ip, request_path: "/.env", waf_action: "allow" ) end end count = PathScannerDetectorJob.perform_now assert_equal 2, count end test "handles invalid IP addresses gracefully" do # Create event with invalid IP Event.create!( project: @project, event_id: SecureRandom.uuid, timestamp: Time.current, ip_address: "invalid-ip", request_path: "/.env", waf_action: "allow" ) assert_nothing_raised do PathScannerDetectorJob.perform_now end end test "returns count of created rules" do 3.times do |i| ip = "192.168.1.#{100 + i}" 3.times do Event.create!( project: @project, event_id: SecureRandom.uuid, timestamp: Time.current, ip_address: ip, request_path: "/.env", waf_action: "allow" ) end end count = PathScannerDetectorJob.perform_now assert_equal 3, count end end