# frozen_string_literal: true require "test_helper" class Api::EventsControllerTest < ActionDispatch::IntegrationTest def setup @dsn = Dsn.create!(name: "Test DSN", key: "test-api-key-1234567890abcdef") @disabled_dsn = Dsn.create!(name: "Disabled DSN", key: "disabled-key-1234567890abcdef", enabled: false) @sample_event_data = { "timestamp" => Time.current.iso8601, "method" => "GET", "path" => "/api/test", "status" => 200, "ip" => "192.168.1.100", "user_agent" => "TestAgent/1.0" } end test "should create event with valid DSN via query parameter" do post api_events_path, params: @sample_event_data.merge(baffle_key: @dsn.key), as: :json assert_response :success json_response = JSON.parse(response.body) assert json_response["success"] assert_not_nil json_response["rule_version"] assert_not_nil response.headers['X-Rule-Version'] end test "should create event with valid DSN via Authorization header" do post api_events_path, headers: { "Authorization" => "Bearer #{@dsn.key}" }, params: @sample_event_data, as: :json assert_response :success json_response = JSON.parse(response.body) assert json_response["success"] end test "should create event with valid DSN via X-Baffle-Auth header" do post api_events_path, headers: { "X-Baffle-Auth" => "Baffle baffle_key=#{@dsn.key}, baffle_version=1" }, params: @sample_event_data, as: :json assert_response :success json_response = JSON.parse(response.body) assert json_response["success"] end test "should create event with valid DSN via Basic auth" do credentials = Base64.strict_encode64("#{@dsn.key}:password") post api_events_path, headers: { "Authorization" => "Basic #{credentials}" }, params: @sample_event_data, as: :json assert_response :success json_response = JSON.parse(response.body) assert json_response["success"] end test "should create event with form encoded data" do post api_events_path, headers: { "Authorization" => "Bearer #{@dsn.key}" }, params: @sample_event_data, as: :url_encoded assert_response :success end test "should include rules in response when agent has no version" do # Create some test rules Rule.create!(action: "block", pattern_type: "ip", pattern: "192.168.1.0/24", reason: "Test rule") Rule.create!(action: "allow", pattern_type: "ip", pattern: "10.0.0.0/8", reason: "Allow internal") post api_events_path, headers: { "Authorization" => "Bearer #{@dsn.key}" }, params: @sample_event_data, as: :json assert_response :success json_response = JSON.parse(response.body) assert json_response["success"] assert json_response["rules_changed"] assert_not_nil json_response["rules"] assert_equal 2, json_response["rules"].length end test "should include only new rules when agent has old version" do # Create rules with different versions old_rule = Rule.create!(action: "block", pattern_type: "ip", pattern: "192.168.1.0/24", reason: "Old rule", version: 1) new_rule = Rule.create!(action: "block", pattern_type: "ip", pattern: "203.0.113.0/24", reason: "New rule", version: 2) event_data_with_version = @sample_event_data.merge("last_rule_sync" => 1) post api_events_path, headers: { "Authorization" => "Bearer #{@dsn.key}" }, params: event_data_with_version, as: :json assert_response :success json_response = JSON.parse(response.body) assert json_response["success"] assert json_response["rules_changed"] assert_equal 1, json_response["rules"].length assert_equal "203.0.113.0/24", json_response["rules"].first["pattern"] end test "should not include rules when agent has latest version" do # Create a rule and get its version rule = Rule.create!(action: "block", pattern_type: "ip", pattern: "192.168.1.0/24", reason: "Test rule") latest_version = Rule.latest_version event_data_with_latest_version = @sample_event_data.merge("last_rule_sync" => latest_version) post api_events_path, headers: { "Authorization" => "Bearer #{@dsn.key}" }, params: event_data_with_latest_version, as: :json assert_response :success json_response = JSON.parse(response.body) assert json_response["success"] assert_not json_response["rules_changed"] assert_nil json_response["rules"] end test "should return unauthorized with invalid DSN key" do post api_events_path, headers: { "Authorization" => "Bearer invalid-key-1234567890abcdef" }, params: @sample_event_data, as: :json assert_response :unauthorized end test "should return unauthorized with disabled DSN" do post api_events_path, headers: { "Authorization" => "Bearer #{@disabled_dsn.key}" }, params: @sample_event_data, as: :json assert_response :unauthorized end test "should return unauthorized with no authentication" do post api_events_path, params: @sample_event_data, as: :json assert_response :unauthorized end test "should return bad request with invalid JSON" do post api_events_path, headers: { "Authorization" => "Bearer #{@dsn.key}" }, params: "invalid json {", as: :json assert_response :bad_request end test "should handle empty request body gracefully" do post api_events_path, headers: { "Authorization" => "Bearer #{@dsn.key}" }, params: {}, as: :json assert_response :success json_response = JSON.parse(response.body) assert json_response["success"] end test "should set sampling headers in response" do post api_events_path, headers: { "Authorization" => "Bearer #{@dsn.key}" }, params: @sample_event_data, as: :json assert_response :success assert_not_nil response.headers['X-Sample-Rate'] assert_not_nil response.headers['X-Sample-Until'] end test "should set rule version header in response" do post api_events_path, headers: { "Authorization" => "Bearer #{@dsn.key}" }, params: @sample_event_data, as: :json assert_response :success assert_not_nil response.headers['X-Rule-Version'] assert_match /^\d+$/, response.headers['X-Rule-Version'] end test "should handle large event payloads" do large_payload = @sample_event_data.merge( "large_field" => "x" * 10000, # 10KB of data "headers" => { "user-agent" => "TestAgent", "accept" => "*/*" }, "custom_data" => Hash[*(1..100).map { |i| ["key#{i}", "value#{i}"] }.flatten] ) post api_events_path, headers: { "Authorization" => "Bearer #{@dsn.key}" }, params: large_payload, as: :json assert_response :success end test "should process event asynchronously" do # Clear any existing jobs ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true ActiveJob::Base.queue_adapter.perform_enqueued_jobs = false assert_difference 'ProcessWafEventJob.jobs.count', 1 do post api_events_path, headers: { "Authorization" => "Bearer #{@dsn.key}" }, params: @sample_event_data, as: :json end assert_response :success end end