# frozen_string_literal: true require "test_helper" class RulePathPatternTest < ActiveSupport::TestCase setup do @user = User.create!(email_address: "test@example.com", password: "password123") end test "create_path_pattern_rule creates valid rule" do rule = Rule.create_path_pattern_rule( pattern: "/admin/users", match_type: "exact", action: "deny", user: @user ) assert rule.persisted?, "Rule should be persisted" assert_equal "path_pattern", rule.waf_rule_type assert_equal "deny", rule.waf_action assert_equal "exact", rule.path_match_type assert_equal 2, rule.path_segment_ids.length end test "create_path_pattern_rule auto-creates PathSegments" do initial_count = PathSegment.count rule = Rule.create_path_pattern_rule( pattern: "/new/path/here", match_type: "prefix", user: @user ) assert_equal initial_count + 3, PathSegment.count, "Should create 3 new segments" assert_equal 3, rule.path_segment_ids.length end test "create_path_pattern_rule normalizes to lowercase" do rule = Rule.create_path_pattern_rule( pattern: "/Admin/Users", match_type: "exact", user: @user ) segments = rule.path_segments_text assert_equal ["admin", "users"], segments, "Segments should be lowercase" end test "create_path_pattern_rule reuses existing PathSegments" do # Create segment first PathSegment.find_or_create_segment("admin") initial_count = PathSegment.count rule = Rule.create_path_pattern_rule( pattern: "/admin", match_type: "exact", user: @user ) assert_equal initial_count, PathSegment.count, "Should not create duplicate segment" assert_equal 1, rule.path_segment_ids.length end test "create_path_pattern_rule validates match_type" do assert_raises(ArgumentError, "Should raise for invalid match_type") do Rule.create_path_pattern_rule( pattern: "/admin", match_type: "invalid", user: @user ) end end test "create_path_pattern_rule validates pattern not empty" do assert_raises(ArgumentError, "Should raise for empty pattern") do Rule.create_path_pattern_rule( pattern: "/", match_type: "exact", user: @user ) end end test "validation requires segment_ids for path_pattern rules" do rule = Rule.new( waf_rule_type: "path_pattern", waf_action: "deny", conditions: { match_type: "exact" }, # Missing segment_ids user: @user ) refute rule.valid?, "Rule should be invalid without segment_ids" assert_includes rule.errors[:conditions], "must include 'segment_ids' array for path_pattern rules" end test "validation requires match_type for path_pattern rules" do admin_seg = PathSegment.find_or_create_segment("admin") rule = Rule.new( waf_rule_type: "path_pattern", waf_action: "deny", conditions: { segment_ids: [admin_seg.id] }, # Missing match_type user: @user ) refute rule.valid?, "Rule should be invalid without match_type" assert_includes rule.errors[:conditions], "match_type must be one of: exact, prefix, suffix, contains" end test "validation checks match_type is valid" do admin_seg = PathSegment.find_or_create_segment("admin") rule = Rule.new( waf_rule_type: "path_pattern", waf_action: "deny", conditions: { segment_ids: [admin_seg.id], match_type: "invalid" }, user: @user ) refute rule.valid?, "Rule should be invalid with invalid match_type" assert_includes rule.errors[:conditions], "match_type must be one of: exact, prefix, suffix, contains" end test "validation checks segment IDs exist" do rule = Rule.new( waf_rule_type: "path_pattern", waf_action: "deny", conditions: { segment_ids: [99999], match_type: "exact" }, # Non-existent ID user: @user ) refute rule.valid?, "Rule should be invalid with non-existent segment IDs" assert_match(/non-existent path segment IDs/, rule.errors[:conditions].first) end test "path_pattern_display returns human-readable path" do rule = Rule.create_path_pattern_rule( pattern: "/admin/users", match_type: "exact", user: @user ) assert_equal "/admin/users", rule.path_pattern_display end test "path_segments_text returns segment text array" do rule = Rule.create_path_pattern_rule( pattern: "/api/v1/users", match_type: "prefix", user: @user ) assert_equal ["api", "v1", "users"], rule.path_segments_text end test "to_agent_format includes segment_ids and match_type for path rules" do rule = Rule.create_path_pattern_rule( pattern: "/admin", match_type: "prefix", action: "deny", user: @user ) agent_format = rule.to_agent_format assert_equal "path_pattern", agent_format[:waf_rule_type] assert_equal "deny", agent_format[:waf_action] assert agent_format[:conditions].key?(:segment_ids), "Should include segment_ids" assert_equal "prefix", agent_format[:conditions][:match_type] assert_kind_of Array, agent_format[:conditions][:segment_ids] end test "supports all four match types" do %w[exact prefix suffix contains].each do |match_type| rule = Rule.create_path_pattern_rule( pattern: "/admin", match_type: match_type, user: @user ) assert rule.persisted?, "Should create rule with #{match_type} match type" assert_equal match_type, rule.path_match_type end end test "supports all action types" do %w[allow deny challenge].each do |action| rule = Rule.create_path_pattern_rule( pattern: "/admin", match_type: "exact", action: action, user: @user ) assert rule.persisted?, "Should create rule with #{action} action" assert_equal action, rule.waf_action end end test "supports redirect action with metadata" do rule = Rule.create_path_pattern_rule( pattern: "/admin", match_type: "exact", action: "redirect", user: @user, metadata: { redirect_url: "https://example.com" } ) assert rule.persisted?, "Should create rule with redirect action" assert_equal "redirect", rule.waf_action end test "stores metadata with human-readable segments" do rule = Rule.create_path_pattern_rule( pattern: "/admin/users", match_type: "exact", user: @user ) assert_equal ["admin", "users"], rule.metadata["segments"] assert_equal "/admin/users", rule.metadata["pattern_display"] end test "stores original pattern in conditions" do rule = Rule.create_path_pattern_rule( pattern: "/Admin/Users", # Mixed case match_type: "exact", user: @user ) assert_equal "/Admin/Users", rule.conditions["original_pattern"] end end