Files
baffle-hub/test/models/event_test.rb
2025-11-09 20:58:13 +11:00

320 lines
10 KiB
Ruby

# frozen_string_literal: true
require "test_helper"
class EventTest < ActiveSupport::TestCase
def setup
@sample_payload = {
"event_id" => "test-event-123",
"timestamp" => Time.now.iso8601,
"request" => {
"ip" => "192.168.1.1",
"method" => "GET",
"path" => "/api/test",
"headers" => {
"host" => "example.com",
"user-agent" => "TestAgent/1.0",
"content-type" => "application/json"
},
"query" => { "param" => "value" }
},
"response" => {
"status_code" => 200,
"duration_ms" => 150,
"size" => 1024
},
"waf_action" => "allow",
"server_name" => "test-server",
"environment" => "test",
"geo" => {
"country_code" => "US",
"city" => "Test City"
},
"tags" => { "source" => "test" },
"agent" => {
"name" => "baffle-agent",
"version" => "1.0.0"
}
}
end
def teardown
Event.delete_all # Delete events first to avoid foreign key constraints
end
test "create_from_waf_payload! creates event with proper enum values" do
event = Event.create_from_waf_payload!("test-123", @sample_payload)
assert event.persisted?
assert_equal "test-123", event.event_id
assert_equal "192.168.1.1", event.ip_address
assert_equal "/api/test", event.request_path
assert_equal 200, event.response_status
assert_equal 150, event.response_time_ms
assert_equal "test-server", event.server_name
assert_equal "test", event.environment
assert_equal "US", event.country_code
assert_equal "Test City", event.city
assert_equal "baffle-agent", event.agent_name
assert_equal "1.0.0", event.agent_version
end
test "create_from_waf_payload! properly normalizes request_method enum" do
test_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
expected_enums = [:get, :post, :put, :patch, :delete, :head, :options]
test_methods.each_with_index do |method, index|
payload = @sample_payload.dup
payload["request"]["method"] = method
payload["event_id"] = "test-method-#{method.downcase}"
event = Event.create_from_waf_payload!("test-method-#{method.downcase}", payload)
assert_equal expected_enums[index].to_s, event.request_method,
"Method #{method} should map to enum #{expected_enums[index]}"
assert_equal index, event.request_method_before_type_cast,
"Method #{method} should be stored as integer #{index}"
end
end
test "create_from_waf_payload! properly normalizes waf_action enum" do
test_actions = [
["allow", :allow, 0],
["pass", :allow, 0],
["deny", :deny, 1],
["block", :deny, 1],
["redirect", :redirect, 2],
["challenge", :challenge, 3],
["unknown", :allow, 0] # Default fallback
]
test_actions.each do |action, expected_enum, expected_int|
payload = @sample_payload.dup
payload["waf_action"] = action
payload["event_id"] = "test-action-#{action}"
event = Event.create_from_waf_payload!("test-action-#{action}", payload)
assert_equal expected_enum.to_s, event.waf_action,
"Action #{action} should map to enum #{expected_enum}"
assert_equal expected_int, event.waf_action_before_type_cast,
"Action #{action} should be stored as integer #{expected_int}"
end
end
test "create_from_waf_payload! handles header case normalization" do
payload = @sample_payload.dup
payload["request"]["headers"] = {
"HOST" => "EXAMPLE.COM",
"User-Agent" => "TestAgent/1.0",
"CONTENT-TYPE" => "application/json"
}
event = Event.create_from_waf_payload!("test-headers", payload)
assert_equal "TestAgent/1.0", event.user_agent
# The normalize_payload_headers method should normalize header keys to lowercase
# but keep values as-is
assert_equal "EXAMPLE.COM", event.headers["host"]
assert_equal "application/json", event.headers["content-type"]
end
test "enum values persist after save and reload" do
event = Event.create_from_waf_payload!("test-persist", @sample_payload)
# Verify initial values
assert_equal "get", event.request_method
assert_equal "allow", event.waf_action
assert_equal 0, event.request_method_before_type_cast
assert_equal 0, event.waf_action_before_type_cast
# Reload from database
event.reload
# Values should still be correct
assert_equal "get", event.request_method
assert_equal "allow", event.waf_action
assert_equal 0, event.request_method_before_type_cast
assert_equal 0, event.waf_action_before_type_cast
end
test "enum scopes work correctly" do
# Create events with different methods and actions using completely separate payloads
# Event 1: GET + allow
Event.create_from_waf_payload!("get-allow", {
"event_id" => "get-allow",
"timestamp" => Time.now.iso8601,
"request" => {
"ip" => "192.168.1.1",
"method" => "GET",
"path" => "/test",
"headers" => { "host" => "example.com" }
},
"response" => { "status_code" => 200 },
"waf_action" => "allow"
}, @project)
# Event 2: POST + allow
Event.create_from_waf_payload!("post-allow", {
"event_id" => "post-allow",
"timestamp" => Time.now.iso8601,
"request" => {
"ip" => "192.168.1.1",
"method" => "POST",
"path" => "/test",
"headers" => { "host" => "example.com" }
},
"response" => { "status_code" => 200 },
"waf_action" => "allow"
}, @project)
# Event 3: GET + deny
Event.create_from_waf_payload!("get-deny", {
"event_id" => "get-deny",
"timestamp" => Time.now.iso8601,
"request" => {
"ip" => "192.168.1.1",
"method" => "GET",
"path" => "/test",
"headers" => { "host" => "example.com" }
},
"response" => { "status_code" => 200 },
"waf_action" => "deny"
}, @project)
# Test method scopes - use string values for enum queries
get_events = Event.where(request_method: "get")
post_events = Event.where(request_method: "post")
assert_equal 2, get_events.count
assert_equal 1, post_events.count
# Test action scopes - use string values for enum queries
allowed_events = Event.where(waf_action: "allow")
denied_events = Event.where(waf_action: "deny")
assert_equal 2, allowed_events.count
assert_equal 1, denied_events.count
end
test "event normalization is triggered when needed" do
# Create event without enum values (simulating old data)
event = Event.create!(
project: @project,
event_id: "normalization-test",
timestamp: Time.current,
payload: @sample_payload,
ip_address: "192.168.1.1",
request_path: "/test",
# Don't set request_method or waf_action to trigger normalization
request_method: nil,
waf_action: nil
)
# Manually set the raw values that would normally be extracted
event.instance_variable_set(:@raw_request_method, "POST")
event.instance_variable_set(:@raw_action, "deny")
# Trigger normalization
event.send(:normalize_event_fields)
event.save!
# Verify normalization worked
event.reload
assert_equal "post", event.request_method
assert_equal "deny", event.waf_action
assert_equal 1, event.request_method_before_type_cast # POST = 1
assert_equal 1, event.waf_action_before_type_cast # DENY = 1
end
test "payload extraction methods work correctly" do
event = Event.create_from_waf_payload!("extraction-test", @sample_payload)
# Test request_details
request_details = event.request_details
assert_equal "192.168.1.1", request_details[:ip]
assert_equal "GET", request_details[:method]
assert_equal "/api/test", request_details[:path]
assert_equal "example.com", request_details[:headers]["host"]
# Test response_details
response_details = event.response_details
assert_equal 200, response_details[:status_code]
assert_equal 150, response_details[:duration_ms]
assert_equal 1024, response_details[:size]
# Test geo_details
geo_details = event.geo_details
assert_equal "US", geo_details["country_code"]
assert_equal "Test City", geo_details["city"]
# Test tags
tags = event.tags
assert_equal "test", tags["source"]
end
test "helper methods work correctly" do
event = Event.create_from_waf_payload!("helper-test", @sample_payload)
# Test boolean methods
assert event.allowed?
assert_not event.blocked?
assert_not event.rate_limited?
assert_not event.challenged?
assert_not event.rule_matched?
# Test path methods
assert_equal ["api", "test"], event.path_segments
assert_equal 2, event.path_depth
end
test "timestamp parsing works with various formats" do
timestamps = [
Time.now.iso8601,
(Time.now.to_f * 1000).to_i, # Unix timestamp in milliseconds
Time.now.utc # Time object
]
timestamps.each_with_index do |timestamp, index|
payload = @sample_payload.dup
payload["timestamp"] = timestamp
payload["event_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"
assert_not event.timestamp.nil?
end
end
test "handles missing optional fields gracefully" do
minimal_payload = {
"event_id" => "minimal-test",
"timestamp" => Time.now.iso8601,
"request" => {
"ip" => "10.0.0.1",
"method" => "GET",
"path" => "/simple"
},
"response" => {
"status_code" => 404
}
}
event = Event.create_from_waf_payload!("minimal-test", minimal_payload)
assert event.persisted?
assert_equal "10.0.0.1", event.ip_address
assert_equal "get", event.request_method
assert_equal "/simple", event.request_path
assert_equal 404, event.response_status
# Optional fields should be nil
assert_nil event.user_agent
assert_nil event.response_time_ms
assert_nil event.country_code
assert_nil event.city
assert_nil event.agent_name
assert_nil event.agent_version
end
end