Many updates
This commit is contained in:
68
test/models/dsn_auth_service_test.rb
Normal file
68
test/models/dsn_auth_service_test.rb
Normal file
@@ -0,0 +1,68 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class DsnAuthServiceTest < ActiveSupport::TestCase
|
||||
self.use_transactional_tests = true
|
||||
|
||||
def setup
|
||||
@dsn = Dsn.create!(name: "Test DSN", key: "test-auth-key-1234567890abcdef")
|
||||
end
|
||||
|
||||
def teardown
|
||||
Dsn.delete_all
|
||||
end
|
||||
|
||||
test "should authenticate via query parameter baffle_key" do
|
||||
request = ActionDispatch::TestRequest.create
|
||||
request.query_parameters = { "baffle_key" => @dsn.key }
|
||||
|
||||
authenticated_dsn = DsnAuthenticationService.authenticate(request)
|
||||
assert_equal @dsn, authenticated_dsn
|
||||
end
|
||||
|
||||
test "should authenticate via Authorization Bearer header" do
|
||||
request = ActionDispatch::TestRequest.create
|
||||
request.headers["Authorization"] = "Bearer #{@dsn.key}"
|
||||
|
||||
authenticated_dsn = DsnAuthenticationService.authenticate(request)
|
||||
assert_equal @dsn, authenticated_dsn
|
||||
end
|
||||
|
||||
test "should authenticate via Basic auth with username as key" do
|
||||
request = ActionDispatch::TestRequest.create
|
||||
credentials = Base64.strict_encode64("#{@dsn.key}:ignored-password")
|
||||
request.headers["Authorization"] = "Basic #{credentials}"
|
||||
|
||||
authenticated_dsn = DsnAuthenticationService.authenticate(request)
|
||||
assert_equal @dsn, authenticated_dsn
|
||||
end
|
||||
|
||||
test "should fail authentication with disabled DSN" do
|
||||
@dsn.update!(enabled: false)
|
||||
|
||||
request = ActionDispatch::TestRequest.create
|
||||
request.query_parameters = { "baffle_key" => @dsn.key }
|
||||
|
||||
assert_raises(DsnAuthenticationService::AuthenticationError) do
|
||||
DsnAuthenticationService.authenticate(request)
|
||||
end
|
||||
end
|
||||
|
||||
test "should fail authentication with non-existent key" do
|
||||
request = ActionDispatch::TestRequest.create
|
||||
request.query_parameters = { "baffle_key" => "non-existent-key" }
|
||||
|
||||
assert_raises(DsnAuthenticationService::AuthenticationError) do
|
||||
DsnAuthenticationService.authenticate(request)
|
||||
end
|
||||
end
|
||||
|
||||
test "should fail authentication with no authentication method" do
|
||||
request = ActionDispatch::TestRequest.create
|
||||
|
||||
assert_raises(DsnAuthenticationService::AuthenticationError) do
|
||||
DsnAuthenticationService.authenticate(request)
|
||||
end
|
||||
end
|
||||
end
|
||||
140
test/models/dsn_simple_test.rb
Normal file
140
test/models/dsn_simple_test.rb
Normal file
@@ -0,0 +1,140 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class DsnSimpleTest < ActiveSupport::TestCase
|
||||
# Don't use any fixtures
|
||||
self.use_transactional_tests = true
|
||||
|
||||
def setup
|
||||
@dsn = Dsn.new(name: "Test DSN")
|
||||
end
|
||||
|
||||
def teardown
|
||||
Dsn.delete_all
|
||||
end
|
||||
|
||||
test "should be valid with valid attributes" do
|
||||
assert @dsn.valid?
|
||||
end
|
||||
|
||||
test "should not be valid without name" do
|
||||
@dsn.name = nil
|
||||
assert_not @dsn.valid?
|
||||
assert_includes @dsn.errors[:name], "can't be blank"
|
||||
end
|
||||
|
||||
test "should automatically generate key on create" do
|
||||
@dsn.save!
|
||||
assert_not_nil @dsn.key
|
||||
assert_equal 64, @dsn.key.length # hex(32) = 64 characters
|
||||
assert_match /\A[a-f0-9]{64}\z/, @dsn.key
|
||||
end
|
||||
|
||||
test "should not override existing key when saved" do
|
||||
@dsn.key = "existing-key-123"
|
||||
@dsn.save!
|
||||
assert_equal "existing-key-123", @dsn.key
|
||||
end
|
||||
|
||||
test "should enforce unique keys" do
|
||||
@dsn.save!
|
||||
dsn2 = Dsn.new(name: "Another DSN", key: @dsn.key)
|
||||
assert_not dsn2.valid?
|
||||
assert_includes dsn2.errors[:key], "has already been taken"
|
||||
end
|
||||
|
||||
test "should default to enabled" do
|
||||
@dsn.save!
|
||||
assert @dsn.enabled?
|
||||
end
|
||||
|
||||
test "should authenticate with valid key" do
|
||||
@dsn.save!
|
||||
authenticated_dsn = Dsn.authenticate(@dsn.key)
|
||||
assert_equal @dsn, authenticated_dsn
|
||||
end
|
||||
|
||||
test "should not authenticate with invalid key" do
|
||||
@dsn.save!
|
||||
assert_nil Dsn.authenticate("invalid-key")
|
||||
end
|
||||
|
||||
test "should not authenticate disabled DSNs" do
|
||||
@dsn.save!
|
||||
@dsn.update!(enabled: false)
|
||||
assert_nil Dsn.authenticate(@dsn.key)
|
||||
end
|
||||
|
||||
# URL Generation Tests
|
||||
test "should generate full DSN URL in development" do
|
||||
@dsn.key = "test-key-1234567890abcdef"
|
||||
@dsn.save!
|
||||
|
||||
expected = "http://test-key-1234567890abcdef@localhost"
|
||||
assert_equal expected, @dsn.full_dsn_url
|
||||
end
|
||||
|
||||
test "should generate API endpoint URL in development" do
|
||||
@dsn.save!
|
||||
|
||||
expected = "http://localhost"
|
||||
assert_equal expected, @dsn.api_endpoint_url
|
||||
end
|
||||
|
||||
test "should use custom host from environment variable" do
|
||||
ENV['RAILS_HOST'] = 'baffle.example.com'
|
||||
|
||||
@dsn.key = "custom-key-1234567890abcdef"
|
||||
@dsn.save!
|
||||
|
||||
assert_equal "http://custom-key-1234567890abcdef@baffle.example.com", @dsn.full_dsn_url
|
||||
assert_equal "http://baffle.example.com", @dsn.api_endpoint_url
|
||||
|
||||
ENV.delete('RAILS_HOST')
|
||||
end
|
||||
|
||||
test "should handle long hex keys in URLs" do
|
||||
long_key = "c92b7f8ad94ea3400299d8a6ff19e409c2df8c4540022c3167b8ac1002931624"
|
||||
@dsn.key = long_key
|
||||
@dsn.save!
|
||||
|
||||
expected = "http://#{long_key}@localhost"
|
||||
assert_equal expected, @dsn.full_dsn_url
|
||||
end
|
||||
|
||||
# Scope Tests
|
||||
test "enabled scope should return only enabled DSNs" do
|
||||
enabled_dsn = Dsn.create!(name: "Enabled DSN", enabled: true)
|
||||
disabled_dsn = Dsn.create!(name: "Disabled DSN", enabled: false)
|
||||
|
||||
enabled_dsns = Dsn.enabled
|
||||
|
||||
assert_includes enabled_dsns, enabled_dsn
|
||||
assert_not_includes enabled_dsns, disabled_dsn
|
||||
end
|
||||
|
||||
# Security Tests
|
||||
test "should generate cryptographically secure keys" do
|
||||
keys = []
|
||||
5.times do
|
||||
dsn = Dsn.create!(name: "Test DSN #{Time.current.to_f}")
|
||||
keys << dsn.key
|
||||
end
|
||||
|
||||
# All keys should be unique
|
||||
assert_equal keys.length, keys.uniq.length
|
||||
|
||||
# All keys should be valid hex
|
||||
keys.each do |key|
|
||||
assert_equal 64, key.length
|
||||
assert_match /\A[a-f0-9]{64}\z/, key
|
||||
end
|
||||
end
|
||||
|
||||
test "should not allow nil keys" do
|
||||
@dsn.key = nil
|
||||
assert_not @dsn.valid?
|
||||
assert_includes @dsn.errors[:key], "can't be blank"
|
||||
end
|
||||
end
|
||||
162
test/models/dsn_test.rb
Normal file
162
test/models/dsn_test.rb
Normal file
@@ -0,0 +1,162 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class DsnTest < ActiveSupport::TestCase
|
||||
# Disable fixtures since we're creating test data manually
|
||||
self.use_instantiated_fixtures = false
|
||||
def setup
|
||||
@dsn = Dsn.new(name: "Test DSN")
|
||||
end
|
||||
|
||||
test "should be valid with valid attributes" do
|
||||
assert @dsn.valid?
|
||||
end
|
||||
|
||||
test "should not be valid without name" do
|
||||
@dsn.name = nil
|
||||
assert_not @dsn.valid?
|
||||
assert_includes @dsn.errors[:name], "can't be blank"
|
||||
end
|
||||
|
||||
test "should automatically generate key on create" do
|
||||
@dsn.save!
|
||||
assert_not_nil @dsn.key
|
||||
assert_equal 64, @dsn.key.length # hex(32) = 64 characters
|
||||
assert_match /\A[a-f0-9]{64}\z/, @dsn.key
|
||||
end
|
||||
|
||||
test "should not override existing key when saved" do
|
||||
@dsn.key = "existing-key-123"
|
||||
@dsn.save!
|
||||
assert_equal "existing-key-123", @dsn.key
|
||||
end
|
||||
|
||||
test "should enforce unique keys" do
|
||||
@dsn.save!
|
||||
dsn2 = Dsn.new(name: "Another DSN", key: @dsn.key)
|
||||
assert_not dsn2.valid?
|
||||
assert_includes dsn2.errors[:key], "has already been taken"
|
||||
end
|
||||
|
||||
test "should default to enabled" do
|
||||
@dsn.save!
|
||||
assert @dsn.enabled?
|
||||
end
|
||||
|
||||
test "should authenticate with valid key" do
|
||||
@dsn.save!
|
||||
authenticated_dsn = Dsn.authenticate(@dsn.key)
|
||||
assert_equal @dsn, authenticated_dsn
|
||||
end
|
||||
|
||||
test "should not authenticate with invalid key" do
|
||||
@dsn.save!
|
||||
assert_nil Dsn.authenticate("invalid-key")
|
||||
end
|
||||
|
||||
test "should not authenticate disabled DSNs" do
|
||||
@dsn.save!
|
||||
@dsn.update!(enabled: false)
|
||||
assert_nil Dsn.authenticate(@dsn.key)
|
||||
end
|
||||
|
||||
# URL Generation Tests
|
||||
test "should generate full DSN URL in development" do
|
||||
@dsn.key = "test-key-1234567890abcdef"
|
||||
@dsn.save!
|
||||
|
||||
expected = "http://test-key-1234567890abcdef@localhost"
|
||||
assert_equal expected, @dsn.full_dsn_url
|
||||
end
|
||||
|
||||
test "should generate API endpoint URL in development" do
|
||||
@dsn.save!
|
||||
|
||||
expected = "http://localhost"
|
||||
assert_equal expected, @dsn.api_endpoint_url
|
||||
end
|
||||
|
||||
test "should use HTTPS in production environment" do
|
||||
# Temporarily switch to production environment
|
||||
original_env = Rails.env
|
||||
Rails.env = "production"
|
||||
|
||||
@dsn.key = "prod-key-1234567890abcdef"
|
||||
@dsn.save!
|
||||
|
||||
assert_equal "https://prod-key-1234567890abcdef@localhost", @dsn.full_dsn_url
|
||||
assert_equal "https://localhost", @dsn.api_endpoint_url
|
||||
|
||||
# Restore original environment
|
||||
Rails.env = original_env
|
||||
end
|
||||
|
||||
test "should use custom host from environment variable" do
|
||||
ENV['RAILS_HOST'] = 'baffle.example.com'
|
||||
|
||||
@dsn.key = "custom-key-1234567890abcdef"
|
||||
@dsn.save!
|
||||
|
||||
assert_equal "http://custom-key-1234567890abcdef@baffle.example.com", @dsn.full_dsn_url
|
||||
assert_equal "http://baffle.example.com", @dsn.api_endpoint_url
|
||||
|
||||
ENV.delete('RAILS_HOST')
|
||||
end
|
||||
|
||||
test "should use action mailer default host if configured" do
|
||||
Rails.application.config.action_mailer.default_url_options = { host: 'mail.baffle.com' }
|
||||
|
||||
@dsn.key = "mail-key-1234567890abcdef"
|
||||
@dsn.save!
|
||||
|
||||
assert_equal "http://mail-key-1234567890abcdef@mail.baffle.com", @dsn.full_dsn_url
|
||||
assert_equal "http://mail.baffle.com", @dsn.api_endpoint_url
|
||||
|
||||
Rails.application.config.action_mailer.default_url_options = {}
|
||||
end
|
||||
|
||||
test "should handle long hex keys in URLs" do
|
||||
long_key = "c92b7f8ad94ea3400299d8a6ff19e409c2df8c4540022c3167b8ac1002931624"
|
||||
@dsn.key = long_key
|
||||
@dsn.save!
|
||||
|
||||
expected = "http://#{long_key}@localhost"
|
||||
assert_equal expected, @dsn.full_dsn_url
|
||||
end
|
||||
|
||||
# Scope Tests
|
||||
test "enabled scope should return only enabled DSNs" do
|
||||
enabled_dsn = Dsn.create!(name: "Enabled DSN", enabled: true)
|
||||
disabled_dsn = Dsn.create!(name: "Disabled DSN", enabled: false)
|
||||
|
||||
enabled_dsns = Dsn.enabled
|
||||
|
||||
assert_includes enabled_dsns, enabled_dsn
|
||||
assert_not_includes enabled_dsns, disabled_dsn
|
||||
end
|
||||
|
||||
# Security Tests
|
||||
test "should generate cryptographically secure keys" do
|
||||
keys = []
|
||||
10.times do
|
||||
dsn = Dsn.create!(name: "Test DSN #{Time.current.to_f}")
|
||||
keys << dsn.key
|
||||
end
|
||||
|
||||
# All keys should be unique
|
||||
assert_equal keys.length, keys.uniq.length
|
||||
|
||||
# All keys should be valid hex
|
||||
keys.each do |key|
|
||||
assert_equal 64, key.length
|
||||
assert_match /\A[a-f0-9]{64}\z/, key
|
||||
end
|
||||
end
|
||||
|
||||
test "should not allow nil keys" do
|
||||
@dsn.key = nil
|
||||
assert_not @dsn.valid?
|
||||
assert_includes @dsn.errors[:key], "can't be blank"
|
||||
end
|
||||
end
|
||||
@@ -5,7 +5,7 @@ require "test_helper"
|
||||
class EventTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
@sample_payload = {
|
||||
"event_id" => "test-event-123",
|
||||
"request_id" => "test-event-123",
|
||||
"timestamp" => Time.now.iso8601,
|
||||
"request" => {
|
||||
"ip" => "192.168.1.1",
|
||||
@@ -46,7 +46,7 @@ class EventTest < ActiveSupport::TestCase
|
||||
event = Event.create_from_waf_payload!("test-123", @sample_payload)
|
||||
|
||||
assert event.persisted?
|
||||
assert_equal "test-123", event.event_id
|
||||
assert_equal "test-123", event.request_id
|
||||
assert_equal "192.168.1.1", event.ip_address
|
||||
assert_equal "/api/test", event.request_path
|
||||
assert_equal 200, event.response_status
|
||||
@@ -66,7 +66,7 @@ class EventTest < ActiveSupport::TestCase
|
||||
test_methods.each_with_index do |method, index|
|
||||
payload = @sample_payload.dup
|
||||
payload["request"]["method"] = method
|
||||
payload["event_id"] = "test-method-#{method.downcase}"
|
||||
payload["request_id"] = "test-method-#{method.downcase}"
|
||||
|
||||
event = Event.create_from_waf_payload!("test-method-#{method.downcase}", payload)
|
||||
|
||||
@@ -91,7 +91,7 @@ class EventTest < ActiveSupport::TestCase
|
||||
test_actions.each do |action, expected_enum, expected_int|
|
||||
payload = @sample_payload.dup
|
||||
payload["waf_action"] = action
|
||||
payload["event_id"] = "test-action-#{action}"
|
||||
payload["request_id"] = "test-action-#{action}"
|
||||
|
||||
event = Event.create_from_waf_payload!("test-action-#{action}", payload)
|
||||
|
||||
@@ -143,7 +143,7 @@ class EventTest < ActiveSupport::TestCase
|
||||
|
||||
# Event 1: GET + allow
|
||||
Event.create_from_waf_payload!("get-allow", {
|
||||
"event_id" => "get-allow",
|
||||
"request_id" => "get-allow",
|
||||
"timestamp" => Time.now.iso8601,
|
||||
"request" => {
|
||||
"ip" => "192.168.1.1",
|
||||
@@ -157,7 +157,7 @@ class EventTest < ActiveSupport::TestCase
|
||||
|
||||
# Event 2: POST + allow
|
||||
Event.create_from_waf_payload!("post-allow", {
|
||||
"event_id" => "post-allow",
|
||||
"request_id" => "post-allow",
|
||||
"timestamp" => Time.now.iso8601,
|
||||
"request" => {
|
||||
"ip" => "192.168.1.1",
|
||||
@@ -171,7 +171,7 @@ class EventTest < ActiveSupport::TestCase
|
||||
|
||||
# Event 3: GET + deny
|
||||
Event.create_from_waf_payload!("get-deny", {
|
||||
"event_id" => "get-deny",
|
||||
"request_id" => "get-deny",
|
||||
"timestamp" => Time.now.iso8601,
|
||||
"request" => {
|
||||
"ip" => "192.168.1.1",
|
||||
@@ -202,7 +202,7 @@ class EventTest < ActiveSupport::TestCase
|
||||
# Create event without enum values (simulating old data)
|
||||
event = Event.create!(
|
||||
project: @project,
|
||||
event_id: "normalization-test",
|
||||
request_id: "normalization-test",
|
||||
timestamp: Time.current,
|
||||
payload: @sample_payload,
|
||||
ip_address: "192.168.1.1",
|
||||
@@ -279,7 +279,7 @@ class EventTest < ActiveSupport::TestCase
|
||||
timestamps.each_with_index do |timestamp, index|
|
||||
payload = @sample_payload.dup
|
||||
payload["timestamp"] = timestamp
|
||||
payload["event_id"] = "timestamp-test-#{index}"
|
||||
payload["request_id"] = "timestamp-test-#{index}"
|
||||
|
||||
event = Event.create_from_waf_payload!("timestamp-test-#{index}", payload)
|
||||
assert event.timestamp.is_a?(Time), "Timestamp #{index} should be parsed as Time"
|
||||
@@ -289,7 +289,7 @@ class EventTest < ActiveSupport::TestCase
|
||||
|
||||
test "handles missing optional fields gracefully" do
|
||||
minimal_payload = {
|
||||
"event_id" => "minimal-test",
|
||||
"request_id" => "minimal-test",
|
||||
"timestamp" => Time.now.iso8601,
|
||||
"request" => {
|
||||
"ip" => "10.0.0.1",
|
||||
|
||||
675
test/models/network_range_test.rb
Normal file
675
test/models/network_range_test.rb
Normal file
@@ -0,0 +1,675 @@
|
||||
require "test_helper"
|
||||
|
||||
class NetworkRangeTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@ipv4_range = NetworkRange.new(network: "192.168.1.0/24")
|
||||
@ipv6_range = NetworkRange.new(network: "2001:db8::/32")
|
||||
@user = users(:jason)
|
||||
end
|
||||
|
||||
# Validations
|
||||
test "should be valid with network address" do
|
||||
assert @ipv4_range.valid?
|
||||
assert @ipv6_range.valid?
|
||||
end
|
||||
|
||||
test "should not be valid without network" do
|
||||
@ipv4_range.network = nil
|
||||
assert_not @ipv4_range.valid?
|
||||
assert_includes @ipv4_range.errors[:network], "can't be blank"
|
||||
end
|
||||
|
||||
test "should validate network uniqueness" do
|
||||
@ipv4_range.save!
|
||||
duplicate = NetworkRange.new(network: "192.168.1.0/24")
|
||||
assert_not duplicate.valid?
|
||||
assert_includes duplicate.errors[:network], "has already been taken"
|
||||
end
|
||||
|
||||
test "should validate source inclusion" do
|
||||
valid_sources = %w[api_imported user_created manual auto_generated inherited geolite_asn geolite_country]
|
||||
valid_sources.each do |source|
|
||||
@ipv4_range.source = source
|
||||
assert @ipv4_range.valid?, "Source #{source} should be valid"
|
||||
end
|
||||
|
||||
@ipv4_range.source = "invalid_source"
|
||||
assert_not @ipv4_range.valid?
|
||||
assert_includes @ipv4_range.errors[:source], "is not included in the list"
|
||||
end
|
||||
|
||||
test "should validate ASN numericality" do
|
||||
@ipv4_range.asn = 12345
|
||||
assert @ipv4_range.valid?
|
||||
|
||||
@ipv4_range.asn = 0
|
||||
assert_not @ipv4_range.valid?
|
||||
assert_includes @ipv4_range.errors[:asn], "must be greater than 0"
|
||||
|
||||
@ipv4_range.asn = -1
|
||||
assert_not @ipv4_range.valid?
|
||||
assert_includes @ipv4_range.errors[:asn], "must be greater than 0"
|
||||
|
||||
@ipv4_range.asn = "not_a_number"
|
||||
assert_not @ipv4_range.valid?
|
||||
|
||||
@ipv4_range.asn = nil
|
||||
assert @ipv4_range.valid?
|
||||
end
|
||||
|
||||
# Callbacks
|
||||
test "should set default source before validation" do
|
||||
range = NetworkRange.new(network: "10.0.0.0/8")
|
||||
range.valid?
|
||||
assert_equal "api_imported", range.source
|
||||
end
|
||||
|
||||
test "should not override existing source" do
|
||||
range = NetworkRange.new(network: "10.0.0.0/8", source: "user_created")
|
||||
range.valid?
|
||||
assert_equal "user_created", range.source
|
||||
end
|
||||
|
||||
# Virtual Attributes (CIDR)
|
||||
test "cidr getter returns network as string" do
|
||||
@ipv4_range.save!
|
||||
assert_equal "192.168.1.0/24", @ipv4_range.cidr
|
||||
assert_equal "192.168.1.0/24", @ipv4_range.network.to_s
|
||||
end
|
||||
|
||||
test "cidr setter sets network from string" do
|
||||
range = NetworkRange.new
|
||||
range.cidr = "10.0.0.0/16"
|
||||
assert_equal "10.0.0.0/16", range.network.to_s
|
||||
end
|
||||
|
||||
# Network Properties
|
||||
test "prefix_length returns correct network prefix" do
|
||||
@ipv4_range.save!
|
||||
@ipv6_range.save!
|
||||
|
||||
assert_equal 24, @ipv4_range.prefix_length
|
||||
assert_equal 32, @ipv6_range.prefix_length
|
||||
end
|
||||
|
||||
test "network_address returns network address" do
|
||||
@ipv4_range.save!
|
||||
assert_equal "192.168.1.0", @ipv4_range.network_address
|
||||
end
|
||||
|
||||
test "broadcast_address returns correct broadcast for IPv4" do
|
||||
@ipv4_range.save!
|
||||
assert_equal "192.168.1.255", @ipv4_range.broadcast_address
|
||||
end
|
||||
|
||||
test "broadcast_address returns nil for IPv6" do
|
||||
@ipv6_range.save!
|
||||
assert_nil @ipv6_range.broadcast_address
|
||||
end
|
||||
|
||||
test "family detection works correctly" do
|
||||
@ipv4_range.save!
|
||||
@ipv6_range.save!
|
||||
|
||||
assert_equal 4, @ipv4_range.family
|
||||
assert_equal 6, @ipv6_range.family
|
||||
end
|
||||
|
||||
test "ipv4? and ipv6? predicate methods work" do
|
||||
@ipv4_range.save!
|
||||
@ipv6_range.save!
|
||||
|
||||
assert @ipv4_range.ipv4?
|
||||
assert_not @ipv4_range.ipv6?
|
||||
|
||||
assert @ipv6_range.ipv6?
|
||||
assert_not @ipv6_range.ipv4?
|
||||
end
|
||||
|
||||
test "virtual? works correctly" do
|
||||
range = NetworkRange.new(network: "10.0.0.0/8")
|
||||
assert range.virtual?
|
||||
|
||||
range.save!
|
||||
assert_not range.virtual?
|
||||
end
|
||||
|
||||
# Network Containment
|
||||
test "contains_ip? works correctly" do
|
||||
@ipv4_range.save!
|
||||
|
||||
assert @ipv4_range.contains_ip?("192.168.1.1")
|
||||
assert @ipv4_range.contains_ip?("192.168.1.254")
|
||||
assert_not @ipv4_range.contains_ip?("192.168.2.1")
|
||||
assert_not @ipv4_range.contains_ip?("2001:db8::1")
|
||||
|
||||
# Test IPv6
|
||||
@ipv6_range.save!
|
||||
assert @ipv6_range.contains_ip?("2001:db8::1")
|
||||
assert @ipv6_range.contains_ip?("2001:db8:ffff::ffff")
|
||||
assert_not @ipv6_range.contains_ip?("2001:db9::1")
|
||||
end
|
||||
|
||||
test "contains_network? works correctly" do
|
||||
@ipv4_range.save!
|
||||
|
||||
# More specific network
|
||||
assert @ipv4_range.contains_network?("192.168.1.0/25")
|
||||
assert @ipv4_range.contains_network?("192.168.1.128/25")
|
||||
|
||||
# Same network
|
||||
assert @ipv4_range.contains_network?("192.168.1.0/24")
|
||||
|
||||
# Less specific network
|
||||
assert_not @ipv4_range.contains_network?("192.168.0.0/16")
|
||||
|
||||
# Different network
|
||||
assert_not @ipv4_range.contains_network?("10.0.0.0/8")
|
||||
end
|
||||
|
||||
test "overlaps? works correctly" do
|
||||
@ipv4_range.save!
|
||||
|
||||
# Overlapping networks
|
||||
assert @ipv4_range.overlaps?("192.168.1.0/25") # More specific
|
||||
assert @ipv4_range.overlaps?("192.168.0.0/23") # Less specific
|
||||
assert @ipv4_range.overlaps?("192.168.1.128/25") # Partial overlap
|
||||
|
||||
# Non-overlapping
|
||||
assert_not @ipv4_range.overlaps?("10.0.0.0/8")
|
||||
assert_not @ipv4_range.overlaps?("172.16.0.0/12")
|
||||
end
|
||||
|
||||
# Parent/Child Relationships
|
||||
test "parent_ranges finds containing networks" do
|
||||
# Create parent and child networks
|
||||
parent = NetworkRange.create!(network: "192.168.0.0/16")
|
||||
@ipv4_range.save! # 192.168.1.0/24
|
||||
child = NetworkRange.create!(network: "192.168.1.0/25")
|
||||
|
||||
parents = @ipv4_range.parent_ranges
|
||||
assert_includes parents, parent
|
||||
assert_not_includes parents, child
|
||||
assert_not_includes parents, @ipv4_range
|
||||
|
||||
# Should be ordered by specificity (more specific first)
|
||||
assert_equal parent, parents.first
|
||||
end
|
||||
|
||||
test "child_ranges finds contained networks" do
|
||||
# Create parent and child networks
|
||||
parent = NetworkRange.create!(network: "192.168.0.0/16")
|
||||
@ipv4_range.save! # 192.168.1.0/24
|
||||
child = NetworkRange.create!(network: "192.168.1.0/25")
|
||||
|
||||
children = parent.child_ranges
|
||||
assert_includes children, @ipv4_range
|
||||
assert_includes children, child
|
||||
assert_not_includes children, parent
|
||||
|
||||
# Should be ordered by specificity (less specific first)
|
||||
assert_equal @ipv4_range, children.first
|
||||
end
|
||||
|
||||
test "sibling_ranges finds same-level networks" do
|
||||
# Create sibling networks
|
||||
sibling1 = NetworkRange.create!(network: "192.168.0.0/24")
|
||||
@ipv4_range.save! # 192.168.1.0/24
|
||||
sibling2 = NetworkRange.create!(network: "192.168.2.0/24")
|
||||
|
||||
siblings = @ipv4_range.sibling_ranges
|
||||
assert_includes siblings, sibling1
|
||||
assert_includes siblings, sibling2
|
||||
assert_not_includes siblings, @ipv4_range
|
||||
end
|
||||
|
||||
# Intelligence and Inheritance
|
||||
test "has_intelligence? detects available intelligence data" do
|
||||
range = NetworkRange.new(network: "10.0.0.0/8")
|
||||
assert_not range.has_intelligence?
|
||||
|
||||
range.asn = 12345
|
||||
assert range.has_intelligence?
|
||||
|
||||
range.asn = nil
|
||||
range.company = "Test Company"
|
||||
assert range.has_intelligence?
|
||||
|
||||
range.company = nil
|
||||
range.country = "US"
|
||||
assert range.has_intelligence?
|
||||
|
||||
range.country = nil
|
||||
range.is_datacenter = true
|
||||
assert range.has_intelligence?
|
||||
end
|
||||
|
||||
test "own_intelligence returns correct data structure" do
|
||||
range = NetworkRange.create!(
|
||||
network: "10.0.0.0/8",
|
||||
asn: 12345,
|
||||
asn_org: "Test ASN",
|
||||
company: "Test Company",
|
||||
country: "US",
|
||||
is_datacenter: true,
|
||||
is_proxy: false,
|
||||
is_vpn: false,
|
||||
source: "manual"
|
||||
)
|
||||
|
||||
intelligence = range.own_intelligence
|
||||
assert_equal 12345, intelligence[:asn]
|
||||
assert_equal "Test ASN", intelligence[:asn_org]
|
||||
assert_equal "Test Company", intelligence[:company]
|
||||
assert_equal "US", intelligence[:country]
|
||||
assert_equal true, intelligence[:is_datacenter]
|
||||
assert_equal false, intelligence[:is_proxy]
|
||||
assert_equal false, intelligence[:is_vpn]
|
||||
assert_equal false, intelligence[:inherited]
|
||||
assert_equal "manual", intelligence[:source]
|
||||
end
|
||||
|
||||
test "inherited_intelligence returns own data when available" do
|
||||
child = NetworkRange.create!(
|
||||
network: "192.168.1.0/24",
|
||||
country: "US",
|
||||
company: "Test Company"
|
||||
)
|
||||
|
||||
intelligence = child.inherited_intelligence
|
||||
assert_equal "US", intelligence[:country]
|
||||
assert_equal "Test Company", intelligence[:company]
|
||||
assert_equal false, intelligence[:inherited]
|
||||
end
|
||||
|
||||
test "inherited_intelligence inherits from parent when needed" do
|
||||
parent = NetworkRange.create!(
|
||||
network: "192.168.0.0/16",
|
||||
country: "US",
|
||||
company: "Test Company"
|
||||
)
|
||||
child = NetworkRange.create!(network: "192.168.1.0/24")
|
||||
|
||||
intelligence = child.inherited_intelligence
|
||||
assert_equal "US", intelligence[:country]
|
||||
assert_equal "Test Company", intelligence[:company]
|
||||
assert_equal true, intelligence[:inherited]
|
||||
assert_equal parent.cidr, intelligence[:parent_cidr]
|
||||
end
|
||||
|
||||
test "parent_with_intelligence finds nearest parent with data" do
|
||||
grandparent = NetworkRange.create!(network: "10.0.0.0/8", country: "US")
|
||||
parent = NetworkRange.create!(network: "10.1.0.0/16")
|
||||
child = NetworkRange.create!(network: "10.1.1.0/24")
|
||||
|
||||
found_parent = child.parent_with_intelligence
|
||||
assert_equal grandparent, found_parent
|
||||
assert_not_equal parent, found_parent
|
||||
end
|
||||
|
||||
# API Data Management
|
||||
test "is_fetching_api_data? tracks active fetches" do
|
||||
range = NetworkRange.create!(network: "10.0.0.0/8")
|
||||
|
||||
assert_not range.is_fetching_api_data?(:ipapi)
|
||||
|
||||
range.mark_as_fetching_api_data!(:ipapi)
|
||||
assert range.is_fetching_api_data?(:ipapi)
|
||||
|
||||
range.clear_fetching_status!(:ipapi)
|
||||
assert_not range.is_fetching_api_data?(:ipapi)
|
||||
end
|
||||
|
||||
test "should_fetch_api_data? prevents duplicate fetches" do
|
||||
range = NetworkRange.create!(network: "10.0.0.0/8")
|
||||
|
||||
# Should fetch initially
|
||||
assert range.should_fetch_api_data?(:ipapi)
|
||||
|
||||
# Should not fetch while fetching
|
||||
range.mark_as_fetching_api_data!(:ipapi)
|
||||
assert_not range.should_fetch_api_data?(:ipapi)
|
||||
|
||||
# Should fetch again after clearing
|
||||
range.clear_fetching_status!(:ipapi)
|
||||
assert range.should_fetch_api_data?(:ipapi)
|
||||
end
|
||||
|
||||
test "has_ipapi_data_available? checks inheritance" do
|
||||
parent = NetworkRange.create!(network: "10.0.0.0/8")
|
||||
parent.set_network_data(:ipapi, { country: "US" })
|
||||
parent.save!
|
||||
|
||||
child = NetworkRange.create!(network: "10.0.1.0/24")
|
||||
|
||||
assert child.has_ipapi_data_available?
|
||||
end
|
||||
|
||||
test "should_fetch_ipapi_data? respects parent fetching status" do
|
||||
parent = NetworkRange.create!(network: "10.0.0.0/8")
|
||||
child = NetworkRange.create!(network: "10.0.1.0/24")
|
||||
|
||||
parent.mark_as_fetching_api_data!(:ipapi)
|
||||
assert_not child.should_fetch_ipapi_data?
|
||||
|
||||
parent.clear_fetching_status!(:ipapi)
|
||||
assert child.should_fetch_ipapi_data?
|
||||
end
|
||||
|
||||
# Network Data Management
|
||||
test "network_data_for and set_network_data work correctly" do
|
||||
range = NetworkRange.create!(network: "10.0.0.0/8")
|
||||
|
||||
assert_equal({}, range.network_data_for(:ipapi))
|
||||
|
||||
data = { country: "US", city: "New York" }
|
||||
range.set_network_data(:ipapi, data)
|
||||
range.save!
|
||||
|
||||
assert_equal data, range.network_data_for(:ipapi)
|
||||
end
|
||||
|
||||
test "has_network_data_from? checks data presence" do
|
||||
range = NetworkRange.create!(network: "10.0.0.0/8")
|
||||
|
||||
assert_not range.has_network_data_from?(:ipapi)
|
||||
|
||||
range.set_network_data(:ipapi, { country: "US" })
|
||||
range.save!
|
||||
|
||||
assert range.has_network_data_from?(:ipapi)
|
||||
end
|
||||
|
||||
# IPAPI Tracking Methods
|
||||
test "find_or_create_tracking_network_for_ip works correctly" do
|
||||
# IPv4 - should create /24
|
||||
tracking_range = NetworkRange.find_or_create_tracking_network_for_ip("192.168.1.100")
|
||||
assert_equal "192.168.1.0/24", tracking_range.network.to_s
|
||||
assert_equal "auto_generated", tracking_range.source
|
||||
assert_equal "IPAPI tracking network", tracking_range.creation_reason
|
||||
|
||||
# IPv6 - should create /64
|
||||
ipv6_tracking = NetworkRange.find_or_create_tracking_network_for_ip("2001:db8::1")
|
||||
assert_equal "2001:db8::/64", ipv6_tracking.network.to_s
|
||||
end
|
||||
|
||||
test "should_fetch_ipapi_for_ip? works correctly" do
|
||||
tracking_range = NetworkRange.create!(network: "192.168.1.0/8")
|
||||
|
||||
# Should fetch initially
|
||||
assert NetworkRange.should_fetch_ipapi_for_ip?("192.168.1.100")
|
||||
|
||||
# Mark as queried recently
|
||||
tracking_range.mark_ipapi_queried!("192.168.1.0/24")
|
||||
assert_not NetworkRange.should_fetch_ipapi_for_ip?("192.168.1.100")
|
||||
|
||||
# Should fetch for old queries
|
||||
tracking_range.network_data['ipapi_queried_at'] = 2.years.ago.to_i
|
||||
tracking_range.save!
|
||||
assert NetworkRange.should_fetch_ipapi_for_ip?("192.168.1.100")
|
||||
end
|
||||
|
||||
test "mark_ipapi_queried! stores query metadata" do
|
||||
range = NetworkRange.create!(network: "192.168.1.0/24")
|
||||
|
||||
range.mark_ipapi_queried!("192.168.1.128/25")
|
||||
|
||||
assert range.network_data['ipapi_queried_at'] > 5.seconds.ago.to_i
|
||||
assert_equal "192.168.1.128/25", range.network_data['ipapi_returned_cidr']
|
||||
end
|
||||
|
||||
# JSON Helper Methods
|
||||
test "abuser_scores_hash handles JSON correctly" do
|
||||
range = NetworkRange.create!(network: "10.0.0.0/8")
|
||||
|
||||
assert_equal({}, range.abuser_scores_hash)
|
||||
|
||||
range.abuser_scores_hash = { ipquality: 85, abuseipdb: 92 }
|
||||
range.save!
|
||||
|
||||
assert_equal({ "ipquality" => 85, "abuseipdb" => 92 }, JSON.parse(range.abuser_scores))
|
||||
assert_equal({ ipquality: 85, abuseipdb: 92 }, range.abuser_scores_hash)
|
||||
end
|
||||
|
||||
test "additional_data_hash handles JSON correctly" do
|
||||
range = NetworkRange.create!(network: "10.0.0.0/8")
|
||||
|
||||
assert_equal({}, range.additional_data_hash)
|
||||
|
||||
range.additional_data_hash = { tags: ["malicious", "botnet"], notes: "Test data" }
|
||||
range.save!
|
||||
|
||||
parsed_data = JSON.parse(range.additional_data)
|
||||
assert_equal ["malicious", "botnet"], parsed_data["tags"]
|
||||
assert_equal "Test data", parsed_data["notes"]
|
||||
end
|
||||
|
||||
# Scopes
|
||||
test "ipv4 and ipv6 scopes work correctly" do
|
||||
ipv4_range = NetworkRange.create!(network: "192.168.1.0/24")
|
||||
ipv6_range = NetworkRange.create!(network: "2001:db8::/32")
|
||||
|
||||
assert_includes NetworkRange.ipv4, ipv4_range
|
||||
assert_not_includes NetworkRange.ipv4, ipv6_range
|
||||
|
||||
assert_includes NetworkRange.ipv6, ipv6_range
|
||||
assert_not_includes NetworkRange.ipv6, ipv4_range
|
||||
end
|
||||
|
||||
test "filtering scopes work correctly" do
|
||||
range1 = NetworkRange.create!(network: "192.168.1.0/24", country: "US", company: "Google", asn: 15169, is_datacenter: true)
|
||||
range2 = NetworkRange.create!(network: "10.0.0.0/8", country: "BR", company: "Amazon", asn: 16509, is_proxy: true)
|
||||
|
||||
assert_includes NetworkRange.by_country("US"), range1
|
||||
assert_not_includes NetworkRange.by_country("US"), range2
|
||||
|
||||
assert_includes NetworkRange.by_company("Google"), range1
|
||||
assert_not_includes NetworkRange.by_company("Google"), range2
|
||||
|
||||
assert_includes NetworkRange.by_asn(15169), range1
|
||||
assert_not_includes NetworkRange.by_asn(15169), range2
|
||||
|
||||
assert_includes NetworkRange.datacenter, range1
|
||||
assert_not_includes NetworkRange.datacenter, range2
|
||||
|
||||
assert_includes NetworkRange.proxy, range2
|
||||
assert_not_includes NetworkRange.proxy, range1
|
||||
end
|
||||
|
||||
# Class Methods
|
||||
test "contains_ip class method finds most specific network" do
|
||||
parent = NetworkRange.create!(network: "192.168.0.0/16")
|
||||
child = NetworkRange.create!(network: "192.168.1.0/24")
|
||||
|
||||
found = NetworkRange.contains_ip("192.168.1.100")
|
||||
assert_equal child, found.first # More specific should come first
|
||||
end
|
||||
|
||||
test "overlapping class method finds overlapping networks" do
|
||||
range1 = NetworkRange.create!(network: "192.168.0.0/16")
|
||||
range2 = NetworkRange.create!(network: "192.168.1.0/24")
|
||||
range3 = NetworkRange.create!(network: "10.0.0.0/8")
|
||||
|
||||
overlapping = NetworkRange.overlapping("192.168.1.0/24")
|
||||
assert_includes overlapping, range1
|
||||
assert_includes overlapping, range2
|
||||
assert_not_includes overlapping, range3
|
||||
end
|
||||
|
||||
test "find_or_create_by_cidr works correctly" do
|
||||
# Creates new record
|
||||
range = NetworkRange.find_or_create_by_cidr("10.0.0.0/8", user: @user, source: "manual")
|
||||
assert range.persisted?
|
||||
assert_equal "10.0.0.0/8", range.network.to_s
|
||||
assert_equal @user, range.user
|
||||
assert_equal "manual", range.source
|
||||
|
||||
# Returns existing record
|
||||
existing = NetworkRange.find_or_create_by_cidr("10.0.0.0/8")
|
||||
assert_equal range.id, existing.id
|
||||
end
|
||||
|
||||
test "find_by_ip_or_network handles both IP and network inputs" do
|
||||
range = NetworkRange.create!(network: "192.168.1.0/24")
|
||||
|
||||
# Find by IP within range
|
||||
found_by_ip = NetworkRange.find_by_ip_or_network("192.168.1.100")
|
||||
assert_includes found_by_ip, range
|
||||
|
||||
# Find by exact network
|
||||
found_by_network = NetworkRange.find_by_ip_or_network("192.168.1.0/24")
|
||||
assert_includes found_by_network, range
|
||||
|
||||
# Return none for invalid input
|
||||
assert_equal NetworkRange.none, NetworkRange.find_by_ip_or_network("")
|
||||
assert_equal NetworkRange.none, NetworkRange.find_by_ip_or_network("invalid")
|
||||
end
|
||||
|
||||
# Analytics Methods
|
||||
test "events_count returns counter cache value" do
|
||||
range = NetworkRange.create!(network: "192.168.1.0/24")
|
||||
|
||||
assert_equal 0, range.events_count
|
||||
|
||||
# Update counter cache manually for testing
|
||||
range.update_column(:events_count, 5)
|
||||
assert_equal 5, range.events_count
|
||||
end
|
||||
|
||||
test "events method finds events within range" do
|
||||
range = NetworkRange.create!(network: "192.168.1.0/24")
|
||||
|
||||
# Create test events
|
||||
matching_event = Event.create!(
|
||||
request_id: "test-1",
|
||||
timestamp: Time.current,
|
||||
payload: {},
|
||||
ip_address: "192.168.1.100"
|
||||
)
|
||||
non_matching_event = Event.create!(
|
||||
request_id: "test-2",
|
||||
timestamp: Time.current,
|
||||
payload: {},
|
||||
ip_address: "10.0.0.1"
|
||||
)
|
||||
|
||||
found_events = range.events
|
||||
assert_includes found_events, matching_event
|
||||
assert_not_includes found_events, non_matching_event
|
||||
end
|
||||
|
||||
test "blocking_rules and active_rules work correctly" do
|
||||
range = NetworkRange.create!(network: "192.168.1.0/24")
|
||||
|
||||
blocking_rule = Rule.create!(
|
||||
rule_type: "network",
|
||||
action: "deny",
|
||||
network_range: range,
|
||||
user: @user,
|
||||
enabled: true
|
||||
)
|
||||
allow_rule = Rule.create!(
|
||||
rule_type: "network",
|
||||
action: "allow",
|
||||
network_range: range,
|
||||
user: @user,
|
||||
enabled: true
|
||||
)
|
||||
disabled_rule = Rule.create!(
|
||||
rule_type: "network",
|
||||
action: "deny",
|
||||
network_range: range,
|
||||
user: @user,
|
||||
enabled: false
|
||||
)
|
||||
|
||||
assert_includes range.blocking_rules, blocking_rule
|
||||
assert_not_includes range.blocking_rules, allow_rule
|
||||
assert_not_includes range.blocking_rules, disabled_rule
|
||||
|
||||
assert_includes range.active_rules, blocking_rule
|
||||
assert_includes range.active_rules, allow_rule
|
||||
assert_not_includes range.active_rules, disabled_rule
|
||||
end
|
||||
|
||||
# Policy Evaluation
|
||||
test "needs_policy_evaluation? works correctly" do
|
||||
range = NetworkRange.create!(network: "192.168.1.0/24")
|
||||
|
||||
# Should evaluate if never evaluated
|
||||
assert range.needs_policy_evaluation?
|
||||
|
||||
# Should evaluate if policies updated since last evaluation
|
||||
range.update!(policies_evaluated_at: 1.hour.ago)
|
||||
WafPolicy.create!(name: "Test Policy", policy_type: "country", targets: ["US"], policy_action: "deny", user: @user)
|
||||
assert range.needs_policy_evaluation?
|
||||
|
||||
# Should not evaluate if up to date
|
||||
range.update!(policies_evaluated_at: 5.minutes.ago)
|
||||
assert_not range.needs_policy_evaluation?
|
||||
end
|
||||
|
||||
# String Representations
|
||||
test "to_s returns cidr" do
|
||||
@ipv4_range.save!
|
||||
assert_equal @ipv4_range.cidr, @ipv4_range.to_s
|
||||
end
|
||||
|
||||
test "to_param parameterizes cidr" do
|
||||
@ipv4_range.save!
|
||||
assert_equal "192.168.1.0_24", @ipv4_range.to_param
|
||||
end
|
||||
|
||||
# Geographic Lookup
|
||||
test "geo_lookup_country! updates country when available" do
|
||||
range = NetworkRange.create!(network: "8.8.8.0/24") # Google's network
|
||||
|
||||
# Mock GeoIpService
|
||||
GeoIpService.expects(:lookup_country).with("8.8.8.0").returns("US")
|
||||
|
||||
range.geo_lookup_country!
|
||||
assert_equal "US", range.reload.country
|
||||
end
|
||||
|
||||
test "geo_lookup_country! handles errors gracefully" do
|
||||
range = NetworkRange.create!(network: "192.168.1.0/24")
|
||||
|
||||
# Mock GeoIpService to raise error
|
||||
GeoIpService.expects(:lookup_country).with("192.168.1.0").raises(StandardError.new("Service error"))
|
||||
|
||||
# Should not raise error but log it
|
||||
assert_nothing_raised do
|
||||
range.geo_lookup_country!
|
||||
end
|
||||
|
||||
assert_nil range.reload.country
|
||||
end
|
||||
|
||||
# Stats Methods
|
||||
test "import_stats_by_source returns statistics" do
|
||||
NetworkRange.create!(network: "10.0.0.0/8", source: "manual")
|
||||
NetworkRange.create!(network: "192.168.1.0/24", source: "api_imported")
|
||||
NetworkRange.create!(network: "172.16.0.0/12", source: "api_imported")
|
||||
|
||||
stats = NetworkRange.import_stats_by_source
|
||||
assert_equal 2, stats.count
|
||||
|
||||
api_stats = stats.find { |s| s.source == "api_imported" }
|
||||
assert_equal 2, api_stats.count
|
||||
end
|
||||
|
||||
test "geolite_coverage_stats returns detailed coverage information" do
|
||||
NetworkRange.create!(network: "10.0.0.0/8", source: "geolite_asn", asn: 12345)
|
||||
NetworkRange.create!(network: "192.168.1.0/24", source: "geolite_country", country: "US")
|
||||
NetworkRange.create!(network: "172.16.0.0/12", source: "geolite_asn", country: "BR")
|
||||
|
||||
stats = NetworkRange.geolite_coverage_stats
|
||||
assert_equal 3, stats[:total_networks]
|
||||
assert_equal 2, stats[:asn_networks]
|
||||
assert_equal 1, stats[:country_networks]
|
||||
assert_equal 2, stats[:with_asn_data]
|
||||
assert_equal 1, stats[:with_country_data]
|
||||
assert_equal 2, stats[:unique_countries]
|
||||
assert_equal 2, stats[:unique_asns]
|
||||
end
|
||||
end
|
||||
@@ -3,25 +3,30 @@
|
||||
require "test_helper"
|
||||
|
||||
class RuleTest < ActiveSupport::TestCase
|
||||
|
||||
# Validation tests
|
||||
test "should create valid network_v4 rule" do
|
||||
test "should create valid network rule" do
|
||||
network_range = NetworkRange.create!(cidr: "10.0.0.0/8")
|
||||
rule = Rule.new(
|
||||
rule_type: "network_v4",
|
||||
action: "deny",
|
||||
conditions: { cidr: "10.0.0.0/8" },
|
||||
source: "manual"
|
||||
waf_rule_type: "network",
|
||||
waf_action: "deny",
|
||||
network_range: network_range,
|
||||
source: "manual",
|
||||
user: users(:one)
|
||||
)
|
||||
assert rule.valid?
|
||||
rule.save!
|
||||
assert_equal 8, rule.priority # Auto-calculated from CIDR prefix
|
||||
end
|
||||
|
||||
test "should create valid network_v6 rule" do
|
||||
test "should create valid network rule with IPv6" do
|
||||
network_range = NetworkRange.create!(cidr: "2001:db8::/32")
|
||||
rule = Rule.new(
|
||||
rule_type: "network_v6",
|
||||
action: "deny",
|
||||
conditions: { cidr: "2001:db8::/32" },
|
||||
source: "manual"
|
||||
waf_rule_type: "network",
|
||||
waf_action: "deny",
|
||||
network_range: network_range,
|
||||
source: "manual",
|
||||
user: users(:one)
|
||||
)
|
||||
assert rule.valid?
|
||||
rule.save!
|
||||
@@ -30,53 +35,58 @@ class RuleTest < ActiveSupport::TestCase
|
||||
|
||||
test "should create valid rate_limit rule" do
|
||||
rule = Rule.new(
|
||||
rule_type: "rate_limit",
|
||||
action: "rate_limit",
|
||||
waf_rule_type: "rate_limit",
|
||||
waf_action: "rate_limit",
|
||||
conditions: { cidr: "0.0.0.0/0", scope: "global" },
|
||||
metadata: { limit: 100, window: 60 },
|
||||
source: "manual"
|
||||
source: "manual",
|
||||
user: users(:one)
|
||||
)
|
||||
assert rule.valid?
|
||||
end
|
||||
|
||||
test "should create valid path_pattern rule" do
|
||||
rule = Rule.new(
|
||||
rule_type: "path_pattern",
|
||||
action: "log",
|
||||
waf_rule_type: "path_pattern",
|
||||
waf_action: "log",
|
||||
conditions: { patterns: ["/.env", "/.git"] },
|
||||
source: "default"
|
||||
source: "default",
|
||||
user: users(:one)
|
||||
)
|
||||
assert rule.valid?
|
||||
end
|
||||
|
||||
test "should require rule_type" do
|
||||
rule = Rule.new(action: "deny", conditions: { cidr: "10.0.0.0/8" })
|
||||
test "should require waf_rule_type" do
|
||||
rule = Rule.new(waf_action: "deny", waf_rule_type: nil, conditions: { patterns: ["/test"] }, user: users(:one))
|
||||
assert_not rule.valid?
|
||||
assert_includes rule.errors[:rule_type], "can't be blank"
|
||||
assert_includes rule.errors[:waf_rule_type], "can't be blank"
|
||||
end
|
||||
|
||||
test "should require action" do
|
||||
rule = Rule.new(rule_type: "network_v4", conditions: { cidr: "10.0.0.0/8" })
|
||||
test "should require waf_action" do
|
||||
rule = Rule.new(waf_rule_type: "path_pattern", waf_action: nil, conditions: { patterns: ["/test"] }, user: users(:one))
|
||||
assert_not rule.valid?
|
||||
assert_includes rule.errors[:action], "can't be blank"
|
||||
assert_includes rule.errors[:waf_action], "can't be blank"
|
||||
end
|
||||
|
||||
test "should validate network_v4 has valid IPv4 CIDR" do
|
||||
test "should validate network has valid CIDR" do
|
||||
rule = Rule.new(
|
||||
rule_type: "network_v4",
|
||||
action: "deny",
|
||||
conditions: { cidr: "2001:db8::/32" } # IPv6 in IPv4 rule
|
||||
waf_rule_type: "network",
|
||||
waf_action: "deny",
|
||||
conditions: { cidr: "invalid-cidr" }, # Invalid CIDR
|
||||
user: users(:one)
|
||||
)
|
||||
assert_not rule.valid?
|
||||
assert_includes rule.errors[:conditions], "cidr must be IPv4 for network_v4 rules"
|
||||
# Network rules now validate differently - they need a network_range
|
||||
assert_includes rule.errors[:network_range], "is required for network rules"
|
||||
end
|
||||
|
||||
test "should validate rate_limit has limit and window in metadata" do
|
||||
rule = Rule.new(
|
||||
rule_type: "rate_limit",
|
||||
action: "rate_limit",
|
||||
waf_rule_type: "rate_limit",
|
||||
waf_action: "rate_limit",
|
||||
conditions: { cidr: "0.0.0.0/0", scope: "global" },
|
||||
metadata: { limit: 100 } # Missing window
|
||||
metadata: { limit: 100 }, # Missing window
|
||||
user: users(:one)
|
||||
)
|
||||
assert_not rule.valid?
|
||||
assert_includes rule.errors[:metadata], "must include 'limit' and 'window' for rate_limit rules"
|
||||
@@ -84,46 +94,56 @@ class RuleTest < ActiveSupport::TestCase
|
||||
|
||||
# Default value tests
|
||||
test "should default enabled to true" do
|
||||
network_range = NetworkRange.create!(cidr: "10.0.0.0/8")
|
||||
rule = Rule.create!(
|
||||
rule_type: "network_v4",
|
||||
action: "deny",
|
||||
conditions: { cidr: "10.0.0.0/8" }
|
||||
waf_rule_type: "network",
|
||||
waf_action: "deny",
|
||||
network_range: network_range,
|
||||
user: users(:one)
|
||||
)
|
||||
assert rule.enabled?
|
||||
end
|
||||
|
||||
# Priority calculation tests
|
||||
test "should calculate priority from IPv4 CIDR prefix" do
|
||||
network_range = NetworkRange.create!(cidr: "192.168.1.0/24")
|
||||
rule = Rule.create!(
|
||||
rule_type: "network_v4",
|
||||
action: "deny",
|
||||
conditions: { cidr: "192.168.1.0/24" }
|
||||
waf_rule_type: "network",
|
||||
waf_action: "deny",
|
||||
network_range: network_range,
|
||||
user: users(:one)
|
||||
)
|
||||
assert_equal 24, rule.priority
|
||||
end
|
||||
|
||||
# Scope tests
|
||||
test "active scope returns enabled and non-expired rules" do
|
||||
active_range = NetworkRange.create!(cidr: "10.0.0.0/8")
|
||||
active = Rule.create!(
|
||||
rule_type: "network_v4",
|
||||
action: "deny",
|
||||
conditions: { cidr: "10.0.0.0/8" },
|
||||
enabled: true
|
||||
)
|
||||
|
||||
disabled = Rule.create!(
|
||||
rule_type: "network_v4",
|
||||
action: "deny",
|
||||
conditions: { cidr: "192.168.0.0/16" },
|
||||
enabled: false
|
||||
)
|
||||
|
||||
expired = Rule.create!(
|
||||
rule_type: "network_v4",
|
||||
action: "deny",
|
||||
conditions: { cidr: "172.16.0.0/12" },
|
||||
waf_rule_type: "network",
|
||||
waf_action: "deny",
|
||||
network_range: active_range,
|
||||
enabled: true,
|
||||
expires_at: 1.hour.ago
|
||||
user: users(:one)
|
||||
)
|
||||
|
||||
disabled_range = NetworkRange.create!(cidr: "192.168.0.0/16")
|
||||
disabled = Rule.create!(
|
||||
waf_rule_type: "network",
|
||||
waf_action: "deny",
|
||||
network_range: disabled_range,
|
||||
enabled: false,
|
||||
user: users(:one)
|
||||
)
|
||||
|
||||
expired_range = NetworkRange.create!(cidr: "172.16.0.0/12")
|
||||
expired = Rule.create!(
|
||||
waf_rule_type: "network",
|
||||
waf_action: "deny",
|
||||
network_range: expired_range,
|
||||
enabled: true,
|
||||
expires_at: 1.hour.ago,
|
||||
user: users(:one)
|
||||
)
|
||||
|
||||
results = Rule.active.to_a
|
||||
@@ -134,20 +154,24 @@ class RuleTest < ActiveSupport::TestCase
|
||||
|
||||
# Instance method tests
|
||||
test "active? returns true for enabled non-expired rule" do
|
||||
network_range = NetworkRange.create!(cidr: "10.0.0.0/8")
|
||||
rule = Rule.create!(
|
||||
rule_type: "network_v4",
|
||||
action: "deny",
|
||||
conditions: { cidr: "10.0.0.0/8" },
|
||||
enabled: true
|
||||
waf_rule_type: "network",
|
||||
waf_action: "deny",
|
||||
network_range: network_range,
|
||||
enabled: true,
|
||||
user: users(:one)
|
||||
)
|
||||
assert rule.active?
|
||||
end
|
||||
|
||||
test "disable! sets enabled to false and adds metadata" do
|
||||
network_range = NetworkRange.create!(cidr: "10.0.0.0/8")
|
||||
rule = Rule.create!(
|
||||
rule_type: "network_v4",
|
||||
action: "deny",
|
||||
conditions: { cidr: "10.0.0.0/8" }
|
||||
waf_rule_type: "network",
|
||||
waf_action: "deny",
|
||||
network_range: network_range,
|
||||
user: users(:one)
|
||||
)
|
||||
|
||||
rule.disable!(reason: "False positive")
|
||||
@@ -159,20 +183,22 @@ class RuleTest < ActiveSupport::TestCase
|
||||
|
||||
# Agent format tests
|
||||
test "to_agent_format returns correct structure" do
|
||||
network_range = NetworkRange.create!(cidr: "10.0.0.0/8")
|
||||
rule = Rule.create!(
|
||||
rule_type: "network_v4",
|
||||
action: "deny",
|
||||
conditions: { cidr: "10.0.0.0/8" },
|
||||
waf_rule_type: "network",
|
||||
waf_action: "deny",
|
||||
network_range: network_range,
|
||||
expires_at: 1.day.from_now,
|
||||
source: "manual",
|
||||
metadata: { reason: "Test" }
|
||||
metadata: { reason: "Test" },
|
||||
user: users(:one)
|
||||
)
|
||||
|
||||
format = rule.to_agent_format
|
||||
|
||||
assert_equal rule.id, format[:id]
|
||||
assert_equal "network_v4", format[:rule_type]
|
||||
assert_equal "deny", format[:action]
|
||||
assert_equal "network", format[:waf_rule_type]
|
||||
assert_equal "deny", format[:waf_action]
|
||||
assert_equal 8, format[:priority]
|
||||
assert_equal true, format[:enabled]
|
||||
end
|
||||
|
||||
7
test/models/setting_test.rb
Normal file
7
test/models/setting_test.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
require "test_helper"
|
||||
|
||||
class SettingTest < ActiveSupport::TestCase
|
||||
# test "the truth" do
|
||||
# assert true
|
||||
# end
|
||||
end
|
||||
474
test/models/waf_policy_test.rb
Normal file
474
test/models/waf_policy_test.rb
Normal file
@@ -0,0 +1,474 @@
|
||||
require "test_helper"
|
||||
|
||||
class WafPolicyTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@user = users(:jason)
|
||||
@policy = WafPolicy.new(
|
||||
name: "Block Malicious IPs",
|
||||
policy_type: "country",
|
||||
targets: ["BR", "CN"],
|
||||
policy_action: "deny",
|
||||
user: @user
|
||||
)
|
||||
end
|
||||
|
||||
# Validations
|
||||
test "should be valid with all required attributes" do
|
||||
assert @policy.valid?
|
||||
end
|
||||
|
||||
test "should not be valid without name" do
|
||||
@policy.name = nil
|
||||
assert_not @policy.valid?
|
||||
assert_includes @policy.errors[:name], "can't be blank"
|
||||
end
|
||||
|
||||
test "should not be valid without unique name" do
|
||||
@policy.name = waf_policies(:one).name
|
||||
assert_not @policy.valid?
|
||||
assert_includes @policy.errors[:name], "has already been taken"
|
||||
end
|
||||
|
||||
test "should validate policy_type inclusion" do
|
||||
@policy.policy_type = "invalid_type"
|
||||
assert_not @policy.valid?
|
||||
assert_includes @policy.errors[:policy_type], "is not included in the list"
|
||||
end
|
||||
|
||||
test "should validate policy_action inclusion" do
|
||||
@policy.policy_action = "invalid_action"
|
||||
assert_not @policy.valid?
|
||||
assert_includes @policy.errors[:policy_action], "is not included in the list"
|
||||
end
|
||||
|
||||
test "should not be valid without targets" do
|
||||
@policy.targets = []
|
||||
assert_not @policy.valid?
|
||||
assert_includes @policy.errors[:targets], "can't be blank"
|
||||
end
|
||||
|
||||
test "should validate targets is an array" do
|
||||
@policy.targets = "not an array"
|
||||
assert_not @policy.valid?
|
||||
assert_includes @policy.errors[:targets], "must be an array"
|
||||
end
|
||||
|
||||
test "should validate country targets format" do
|
||||
@policy.policy_type = "country"
|
||||
|
||||
# Valid country codes
|
||||
@policy.targets = ["US", "BR", "CN"]
|
||||
assert @policy.valid?
|
||||
|
||||
# Invalid country codes
|
||||
@policy.targets = ["USA", "123", "B"]
|
||||
assert_not @policy.valid?
|
||||
assert_includes @policy.errors[:targets], "must be valid ISO country codes"
|
||||
end
|
||||
|
||||
test "should validate ASN targets format" do
|
||||
@policy.policy_type = "asn"
|
||||
|
||||
# Valid ASNs
|
||||
@policy.targets = [12345, 67890]
|
||||
assert @policy.valid?
|
||||
|
||||
# Invalid ASNs
|
||||
@policy.targets = ["AS12345", -1, 0]
|
||||
assert_not @policy.valid?
|
||||
assert_includes @policy.errors[:targets], "must be valid ASNs"
|
||||
end
|
||||
|
||||
test "should validate company targets format" do
|
||||
@policy.policy_type = "company"
|
||||
|
||||
# Valid company names
|
||||
@policy.targets = ["Google", "Amazon Web Services", "Microsoft"]
|
||||
assert @policy.valid?
|
||||
|
||||
# Invalid company names
|
||||
@policy.targets = ["", nil, " "]
|
||||
assert_not @policy.valid?
|
||||
assert_includes @policy.errors[:targets], "must be valid company names"
|
||||
end
|
||||
|
||||
test "should validate network_type targets format" do
|
||||
@policy.policy_type = "network_type"
|
||||
|
||||
# Valid network types
|
||||
@policy.targets = ["datacenter", "proxy", "vpn", "standard"]
|
||||
assert @policy.valid?
|
||||
|
||||
# Invalid network types
|
||||
@policy.targets = ["invalid", "malicious", "botnet"]
|
||||
assert_not @policy.valid?
|
||||
assert_includes @policy.errors[:targets], "must be one of: datacenter, proxy, vpn, standard"
|
||||
end
|
||||
|
||||
test "should validate redirect configuration" do
|
||||
@policy.policy_action = "redirect"
|
||||
|
||||
# Valid redirect config
|
||||
@policy.additional_data = { "redirect_url" => "https://example.com/blocked" }
|
||||
assert @policy.valid?
|
||||
|
||||
# Missing redirect URL
|
||||
@policy.additional_data = { "other_config" => "value" }
|
||||
assert_not @policy.valid?
|
||||
assert_includes @policy.errors[:additional_data], "must include 'redirect_url' for redirect action"
|
||||
end
|
||||
|
||||
test "should validate challenge configuration" do
|
||||
@policy.policy_action = "challenge"
|
||||
|
||||
# Valid challenge types
|
||||
@policy.additional_data = { "challenge_type" => "captcha" }
|
||||
assert @policy.valid?
|
||||
|
||||
@policy.additional_data = { "challenge_type" => "javascript" }
|
||||
assert @policy.valid?
|
||||
|
||||
# Invalid challenge type
|
||||
@policy.additional_data = { "challenge_type" => "invalid" }
|
||||
assert_not @policy.valid?
|
||||
assert_includes @policy.errors[:additional_data], "challenge_type must be one of: captcha, javascript, proof_of_work"
|
||||
|
||||
# No challenge type (should be valid, uses defaults)
|
||||
@policy.additional_data = {}
|
||||
assert @policy.valid?
|
||||
end
|
||||
|
||||
# Defaults and Callbacks
|
||||
test "should default to enabled" do
|
||||
@policy.enabled = nil
|
||||
@policy.save!
|
||||
assert @policy.enabled?
|
||||
end
|
||||
|
||||
test "should default targets to empty array" do
|
||||
policy = WafPolicy.new(
|
||||
name: "Test Policy",
|
||||
policy_type: "country",
|
||||
policy_action: "deny",
|
||||
user: @user
|
||||
)
|
||||
# before_validation should set defaults
|
||||
policy.valid?
|
||||
assert_equal [], policy.targets
|
||||
end
|
||||
|
||||
test "should default additional_data to empty hash" do
|
||||
policy = WafPolicy.new(
|
||||
name: "Test Policy",
|
||||
policy_type: "country",
|
||||
targets: ["US"],
|
||||
policy_action: "deny",
|
||||
user: @user
|
||||
)
|
||||
policy.valid?
|
||||
assert_equal({}, policy.additional_data)
|
||||
end
|
||||
|
||||
# Policy Type Methods
|
||||
test "policy type predicate methods work correctly" do
|
||||
country_policy = WafPolicy.new(policy_type: "country")
|
||||
assert country_policy.country_policy?
|
||||
assert_not country_policy.asn_policy?
|
||||
assert_not country_policy.company_policy?
|
||||
assert_not country_policy.network_type_policy?
|
||||
|
||||
asn_policy = WafPolicy.new(policy_type: "asn")
|
||||
assert_not asn_policy.country_policy?
|
||||
assert asn_policy.asn_policy?
|
||||
assert_not asn_policy.company_policy?
|
||||
assert_not asn_policy.network_type_policy?
|
||||
|
||||
company_policy = WafPolicy.new(policy_type: "company")
|
||||
assert_not company_policy.country_policy?
|
||||
assert_not company_policy.asn_policy?
|
||||
assert company_policy.company_policy?
|
||||
assert_not company_policy.network_type_policy?
|
||||
|
||||
network_type_policy = WafPolicy.new(policy_type: "network_type")
|
||||
assert_not network_type_policy.country_policy?
|
||||
assert_not network_type_policy.asn_policy?
|
||||
assert_not network_type_policy.company_policy?
|
||||
assert network_type_policy.network_type_policy?
|
||||
end
|
||||
|
||||
# Action Methods
|
||||
test "action predicate methods work correctly" do
|
||||
allow_policy = WafPolicy.new(policy_action: "allow")
|
||||
assert allow_policy.allow_action?
|
||||
assert_not allow_policy.deny_action?
|
||||
assert_not allow_policy.redirect_action?
|
||||
assert_not allow_policy.challenge_action?
|
||||
|
||||
deny_policy = WafPolicy.new(policy_action: "deny")
|
||||
assert_not deny_policy.allow_action?
|
||||
assert deny_policy.deny_action?
|
||||
assert_not deny_policy.redirect_action?
|
||||
assert_not deny_policy.challenge_action?
|
||||
|
||||
redirect_policy = WafPolicy.new(policy_action: "redirect")
|
||||
assert_not redirect_policy.allow_action?
|
||||
assert_not redirect_policy.deny_action?
|
||||
assert redirect_policy.redirect_action?
|
||||
assert_not redirect_policy.challenge_action?
|
||||
|
||||
challenge_policy = WafPolicy.new(policy_action: "challenge")
|
||||
assert_not challenge_policy.allow_action?
|
||||
assert_not challenge_policy.deny_action?
|
||||
assert_not challenge_policy.redirect_action?
|
||||
assert challenge_policy.challenge_action?
|
||||
end
|
||||
|
||||
# Policy action methods (to avoid Rails conflicts)
|
||||
test "policy action predicate methods work correctly" do
|
||||
policy = WafPolicy.new(policy_action: "deny")
|
||||
assert policy.deny_policy_action?
|
||||
assert_not policy.allow_policy_action?
|
||||
assert_not policy.redirect_policy_action?
|
||||
assert_not policy.challenge_policy_action?
|
||||
end
|
||||
|
||||
# Lifecycle Methods
|
||||
test "active? works correctly" do
|
||||
# Active policy
|
||||
active_policy = WafPolicy.new(enabled: true, expires_at: nil)
|
||||
assert active_policy.active?
|
||||
|
||||
# Enabled but expired
|
||||
expired_policy = WafPolicy.new(enabled: true, expires_at: 1.day.ago)
|
||||
assert_not expired_policy.active?
|
||||
|
||||
# Disabled with future expiration
|
||||
disabled_policy = WafPolicy.new(enabled: false, expires_at: 1.day.from_now)
|
||||
assert_not disabled_policy.active?
|
||||
|
||||
# Disabled with no expiration
|
||||
disabled_no_exp = WafPolicy.new(enabled: false, expires_at: nil)
|
||||
assert_not disabled_no_exp.active?
|
||||
|
||||
# Enabled with future expiration
|
||||
future_exp = WafPolicy.new(enabled: true, expires_at: 1.day.from_now)
|
||||
assert future_exp.active?
|
||||
end
|
||||
|
||||
test "expired? works correctly" do
|
||||
assert_not WafPolicy.new(expires_at: nil).expired?
|
||||
assert_not WafPolicy.new(expires_at: 1.day.from_now).expired?
|
||||
assert WafPolicy.new(expires_at: 1.day.ago).expired?
|
||||
assert WafPolicy.new(expires_at: Time.current).expired?
|
||||
end
|
||||
|
||||
test "activate! enables policy" do
|
||||
@policy.enabled = false
|
||||
@policy.save!
|
||||
|
||||
@policy.activate!
|
||||
assert @policy.reload.enabled?
|
||||
end
|
||||
|
||||
test "deactivate! disables policy" do
|
||||
@policy.enabled = true
|
||||
@policy.save!
|
||||
|
||||
@policy.deactivate!
|
||||
assert_not @policy.reload.enabled?
|
||||
end
|
||||
|
||||
test "expire! sets expiration to now" do
|
||||
@policy.expire!
|
||||
assert @policy.reload.expires_at <= Time.current
|
||||
end
|
||||
|
||||
# Scopes
|
||||
test "enabled scope returns only enabled policies" do
|
||||
enabled_policy = WafPolicy.create!(
|
||||
name: "Enabled Policy",
|
||||
policy_type: "country",
|
||||
targets: ["US"],
|
||||
policy_action: "deny",
|
||||
user: @user,
|
||||
enabled: true
|
||||
)
|
||||
disabled_policy = WafPolicy.create!(
|
||||
name: "Disabled Policy",
|
||||
policy_type: "country",
|
||||
targets: ["US"],
|
||||
policy_action: "deny",
|
||||
user: @user,
|
||||
enabled: false
|
||||
)
|
||||
|
||||
enabled_policies = WafPolicy.enabled
|
||||
assert_includes enabled_policies, enabled_policy
|
||||
assert_not_includes enabled_policies, disabled_policy
|
||||
end
|
||||
|
||||
test "active scope returns only active policies" do
|
||||
active_policy = WafPolicy.create!(
|
||||
name: "Active Policy",
|
||||
policy_type: "country",
|
||||
targets: ["US"],
|
||||
policy_action: "deny",
|
||||
user: @user,
|
||||
enabled: true,
|
||||
expires_at: 1.day.from_now
|
||||
)
|
||||
expired_policy = WafPolicy.create!(
|
||||
name: "Expired Policy",
|
||||
policy_type: "country",
|
||||
targets: ["US"],
|
||||
policy_action: "deny",
|
||||
user: @user,
|
||||
enabled: true,
|
||||
expires_at: 1.day.ago
|
||||
)
|
||||
disabled_policy = WafPolicy.create!(
|
||||
name: "Disabled Policy",
|
||||
policy_type: "country",
|
||||
targets: ["US"],
|
||||
policy_action: "deny",
|
||||
user: @user,
|
||||
enabled: false
|
||||
)
|
||||
|
||||
active_policies = WafPolicy.active
|
||||
assert_includes active_policies, active_policy
|
||||
assert_not_includes active_policies, expired_policy
|
||||
assert_not_includes active_policies, disabled_policy
|
||||
end
|
||||
|
||||
# Class Factory Methods
|
||||
test "create_country_policy works correctly" do
|
||||
policy = WafPolicy.create_country_policy(
|
||||
["US", "CA"],
|
||||
policy_action: "deny",
|
||||
user: @user,
|
||||
name: "Custom Name"
|
||||
)
|
||||
|
||||
assert policy.persisted?
|
||||
assert_equal "Custom Name", policy.name
|
||||
assert_equal "country", policy.policy_type
|
||||
assert_equal "deny", policy.policy_action
|
||||
assert_equal ["US", "CA"], policy.targets
|
||||
assert_equal @user, policy.user
|
||||
end
|
||||
|
||||
test "create_asn_policy works correctly" do
|
||||
policy = WafPolicy.create_asn_policy(
|
||||
[12345, 67890],
|
||||
policy_action: "challenge",
|
||||
user: @user
|
||||
)
|
||||
|
||||
assert policy.persisted?
|
||||
assert_equal "challenge ASNs 12345, 67890", policy.name
|
||||
assert_equal "asn", policy.policy_type
|
||||
assert_equal "challenge", policy.policy_action
|
||||
assert_equal [12345, 67890], policy.targets
|
||||
end
|
||||
|
||||
test "create_company_policy works correctly" do
|
||||
policy = WafPolicy.create_company_policy(
|
||||
["Google", "Amazon"],
|
||||
policy_action: "deny",
|
||||
user: @user
|
||||
)
|
||||
|
||||
assert policy.persisted?
|
||||
assert_equal "deny Google, Amazon", policy.name
|
||||
assert_equal "company", policy.policy_type
|
||||
assert_equal ["Google", "Amazon"], policy.targets
|
||||
end
|
||||
|
||||
test "create_network_type_policy works correctly" do
|
||||
policy = WafPolicy.create_network_type_policy(
|
||||
["datacenter", "proxy"],
|
||||
policy_action: "redirect",
|
||||
user: @user,
|
||||
additional_data: { redirect_url: "https://example.com/blocked" }
|
||||
)
|
||||
|
||||
assert policy.persisted?
|
||||
assert_equal "redirect datacenter, proxy", policy.name
|
||||
assert_equal "network_type", policy.policy_type
|
||||
assert_equal ["datacenter", "proxy"], policy.targets
|
||||
end
|
||||
|
||||
# Redirect/Challenge Specific Methods
|
||||
test "redirect_url and redirect_status methods work" do
|
||||
policy = WafPolicy.new(
|
||||
policy_action: "redirect",
|
||||
additional_data: {
|
||||
"redirect_url" => "https://example.com/blocked",
|
||||
"redirect_status" => 301
|
||||
}
|
||||
)
|
||||
|
||||
assert_equal "https://example.com/blocked", policy.redirect_url
|
||||
assert_equal 301, policy.redirect_status
|
||||
|
||||
# Default status
|
||||
policy.additional_data = { "redirect_url" => "https://example.com/blocked" }
|
||||
assert_equal 302, policy.redirect_status
|
||||
end
|
||||
|
||||
test "challenge_type and challenge_message methods work" do
|
||||
policy = WafPolicy.new(
|
||||
policy_action: "challenge",
|
||||
additional_data: {
|
||||
"challenge_type" => "javascript",
|
||||
"challenge_message" => "Please verify you are human"
|
||||
}
|
||||
)
|
||||
|
||||
assert_equal "javascript", policy.challenge_type
|
||||
assert_equal "Please verify you are human", policy.challenge_message
|
||||
|
||||
# Default challenge type
|
||||
policy.additional_data = {}
|
||||
assert_equal "captcha", policy.challenge_type
|
||||
end
|
||||
|
||||
# Statistics
|
||||
test "generated_rules_count works" do
|
||||
@policy.save!
|
||||
|
||||
# Initially no rules
|
||||
assert_equal 0, @policy.generated_rules_count
|
||||
|
||||
# Create some rules
|
||||
network_range = NetworkRange.create!(ip_range: "192.168.1.0/24")
|
||||
@policy.create_rule_for_network_range(network_range)
|
||||
|
||||
assert_equal 1, @policy.generated_rules_count
|
||||
end
|
||||
|
||||
test "effectiveness_stats returns correct data" do
|
||||
@policy.save!
|
||||
|
||||
stats = @policy.effectiveness_stats
|
||||
|
||||
assert_equal 0, stats[:total_rules_generated]
|
||||
assert_equal 0, stats[:active_rules]
|
||||
assert_equal 0, stats[:rules_last_7_days]
|
||||
assert_equal "country", stats[:policy_type]
|
||||
assert_equal "deny", stats[:policy_action]
|
||||
assert_equal 2, stats[:targets_count]
|
||||
end
|
||||
|
||||
# String representations
|
||||
test "to_s returns name" do
|
||||
assert_equal @policy.name, @policy.to_s
|
||||
end
|
||||
|
||||
test "to_param parameterizes name" do
|
||||
@policy.name = "Block Brazil & China"
|
||||
expected = "block-brazil-china"
|
||||
assert_equal expected, @policy.to_param
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user