# frozen_string_literal: true require "test_helper" module Api class RulesControllerTest < ActionDispatch::IntegrationTest setup do @project = Project.create!( name: "Test Project", slug: "test-project", public_key: "test-key-#{SecureRandom.hex(8)}" ) @rule1 = Rule.create!( rule_type: "network_v4", action: "deny", conditions: { cidr: "10.0.0.0/8" }, source: "manual" ) @rule2 = Rule.create!( rule_type: "rate_limit", action: "rate_limit", conditions: { cidr: "0.0.0.0/0", scope: "global" }, metadata: { limit: 100, window: 60 } ) end test "version endpoint returns correct structure" do get "/api/#{@project.public_key}/rules/version" assert_response :success json = JSON.parse(response.body) assert json["version"].present? assert_equal 2, json["count"] assert json["sampling"].present? assert json["sampling"]["allowed_requests"].present? assert json["sampling"]["blocked_requests"].present? assert json["sampling"]["load_level"].present? end test "version endpoint requires valid project key" do get "/api/invalid-key/rules/version" assert_response :unauthorized json = JSON.parse(response.body) assert_equal "Invalid project key", json["error"] end test "version endpoint rejects disabled projects" do @project.update!(enabled: false) get "/api/#{@project.public_key}/rules/version" assert_response :forbidden json = JSON.parse(response.body) assert_equal "Project is disabled", json["error"] end test "index endpoint returns all active rules" do get "/api/#{@project.public_key}/rules" assert_response :success json = JSON.parse(response.body) assert json["version"].present? assert json["sampling"].present? assert_equal 2, json["rules"].length rule = json["rules"].find { |r| r["id"] == @rule1.id } assert_equal "network_v4", rule["rule_type"] assert_equal "deny", rule["action"] assert_equal({ "cidr" => "10.0.0.0/8" }, rule["conditions"]) assert_equal 8, rule["priority"] end test "index endpoint excludes disabled rules" do @rule1.update!(enabled: false) get "/api/#{@project.public_key}/rules" assert_response :success json = JSON.parse(response.body) assert_equal 1, json["rules"].length assert_equal @rule2.id, json["rules"].first["id"] end test "index endpoint excludes expired rules" do @rule1.update!(expires_at: 1.hour.ago) get "/api/#{@project.public_key}/rules" assert_response :success json = JSON.parse(response.body) assert_equal 1, json["rules"].length assert_equal @rule2.id, json["rules"].first["id"] end test "index endpoint with since parameter returns recent rules" do # Update rule1 to be older @rule1.update_column(:updated_at, 2.hours.ago) since_time = 1.hour.ago.iso8601 get "/api/#{@project.public_key}/rules?since=#{since_time}" assert_response :success json = JSON.parse(response.body) assert_equal 1, json["rules"].length assert_equal @rule2.id, json["rules"].first["id"] end test "index endpoint with since parameter includes disabled rules" do @rule1.update!(enabled: false) # This updates updated_at since_time = 1.minute.ago.iso8601 get "/api/#{@project.public_key}/rules?since=#{since_time}" assert_response :success json = JSON.parse(response.body) # Should include the disabled rule for agent to remove it disabled_rule = json["rules"].find { |r| r["id"] == @rule1.id } assert disabled_rule.present? assert_equal false, disabled_rule["enabled"] end test "index endpoint with invalid timestamp returns error" do get "/api/#{@project.public_key}/rules?since=invalid-timestamp" assert_response :bad_request json = JSON.parse(response.body) assert json["error"].include?("Invalid timestamp format") end test "index endpoint requires authentication" do get "/api/invalid-key/rules" assert_response :unauthorized end test "index endpoint includes sampling information" do get "/api/#{@project.public_key}/rules" assert_response :success json = JSON.parse(response.body) sampling = json["sampling"] assert_equal 1.0, sampling["allowed_requests"] assert_equal 1.0, sampling["blocked_requests"] assert_equal 1.0, sampling["rate_limited_requests"] assert sampling["effective_until"].present? assert_equal "normal", sampling["load_level"] end test "rules are ordered by updated_at for sync" do # Create rules with different timestamps oldest = Rule.create!( rule_type: "network_v4", action: "deny", conditions: { cidr: "192.168.1.0/24" } ) oldest.update_column(:updated_at, 3.hours.ago) middle = Rule.create!( rule_type: "network_v4", action: "deny", conditions: { cidr: "192.168.2.0/24" } ) middle.update_column(:updated_at, 2.hours.ago) newest = Rule.create!( rule_type: "network_v4", action: "deny", conditions: { cidr: "192.168.3.0/24" } ) get "/api/#{@project.public_key}/rules?since=#{4.hours.ago.iso8601}" assert_response :success json = JSON.parse(response.body) ids = json["rules"].map { |r| r["id"] } # Should be ordered oldest to newest by updated_at assert_equal [oldest.id, middle.id], ids.first(2) assert_equal newest.id, ids.last end end end