Many updates

This commit is contained in:
Dan Milne
2025-11-13 14:42:43 +11:00
parent 5e5198f113
commit df94ac9720
41 changed files with 4760 additions and 516 deletions

View 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

View 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
View 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

View File

@@ -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",

View 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

View File

@@ -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

View File

@@ -0,0 +1,7 @@
require "test_helper"
class SettingTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View 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