323 lines
10 KiB
Ruby
323 lines
10 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "test_helper"
|
|
|
|
class EventTest < ActiveSupport::TestCase
|
|
def setup
|
|
@sample_payload = {
|
|
"request_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.request_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["request_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
|
|
# Updated enum values: deny:0, allow:1, redirect:2, challenge:3, log:4
|
|
test_actions = [
|
|
["deny", :deny, 0],
|
|
["block", :deny, 0],
|
|
["allow", :allow, 1],
|
|
["pass", :allow, 1],
|
|
["redirect", :redirect, 2],
|
|
["challenge", :challenge, 3],
|
|
["log", :log, 4],
|
|
["monitor", :log, 4],
|
|
["unknown", :allow, 1] # Default fallback
|
|
]
|
|
|
|
test_actions.each do |action, expected_enum, expected_int|
|
|
payload = @sample_payload.dup
|
|
payload["waf_action"] = action
|
|
payload["request_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 (updated enum: deny:0, allow:1)
|
|
assert_equal "get", event.request_method
|
|
assert_equal "allow", event.waf_action
|
|
assert_equal 0, event.request_method_before_type_cast
|
|
assert_equal 1, event.waf_action_before_type_cast # allow is now 1
|
|
|
|
# Reload from database
|
|
event.reload
|
|
|
|
# Values should still be correct (allow is now 1)
|
|
assert_equal "get", event.request_method
|
|
assert_equal "allow", event.waf_action
|
|
assert_equal 0, event.request_method_before_type_cast
|
|
assert_equal 1, 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", {
|
|
"request_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", {
|
|
"request_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", {
|
|
"request_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,
|
|
request_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.logged? # Changed from rate_limited? to logged?
|
|
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["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"
|
|
assert_not event.timestamp.nil?
|
|
end
|
|
end
|
|
|
|
test "handles missing optional fields gracefully" do
|
|
minimal_payload = {
|
|
"request_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 |