mirror of
https://github.com/internetee/registry.git
synced 2025-08-15 22:13:54 +02:00
474 lines
No EOL
15 KiB
Ruby
474 lines
No EOL
15 KiB
Ruby
require 'test_helper'
|
|
|
|
class DNSValidatorTest < ActiveSupport::TestCase
|
|
|
|
setup do
|
|
@domain = domains(:shop)
|
|
@nameserver1 = nameservers(:shop_ns1)
|
|
@nameserver2 = nameservers(:shop_ns2)
|
|
@dnskey = dnskeys(:one)
|
|
|
|
# Ensure domain has fresh timestamps for validation
|
|
@domain.update(created_at: 1.day.ago)
|
|
@domain.reload
|
|
|
|
# Associate dnskey with domain for DNSSEC tests
|
|
@dnskey.update(domain: @domain) if @dnskey
|
|
end
|
|
|
|
# Basic functionality tests
|
|
|
|
test 'initializes with correct structure' do
|
|
validator = DNSValidator.new(@domain)
|
|
|
|
assert_equal @domain, validator.domain
|
|
assert_instance_of Hash, validator.results
|
|
|
|
expected_keys = [:nameservers, :dns_records, :dnssec, :csync, :errors, :warnings]
|
|
expected_keys.each do |key|
|
|
assert_includes validator.results.keys, key
|
|
end
|
|
end
|
|
|
|
test 'class method validate creates instance' do
|
|
validator = DNSValidator.new(@domain)
|
|
|
|
# Mock validate method to avoid DNS calls
|
|
validator.define_singleton_method(:validate) { { test: 'result' } }
|
|
|
|
# Temporarily override new method
|
|
original_new = DNSValidator.method(:new)
|
|
DNSValidator.define_singleton_method(:new) { |domain| validator }
|
|
|
|
result = DNSValidator.validate(@domain)
|
|
|
|
assert_equal({ test: 'result' }, result)
|
|
ensure
|
|
# Restore original new method
|
|
DNSValidator.define_singleton_method(:new, original_new)
|
|
end
|
|
|
|
# Story 1: Nameserver Validation Tests
|
|
|
|
test 'validates nameservers successfully' do
|
|
validator = DNSValidator.new(@domain)
|
|
|
|
# Mock successful nameserver validation
|
|
validator.define_singleton_method(:validate_single_nameserver) do |ns|
|
|
{ valid: true, authoritative: true, ns_records: [ns.hostname], reason: nil }
|
|
end
|
|
|
|
validator.send(:validate_nameservers)
|
|
|
|
@nameserver1.reload
|
|
assert_not_nil @nameserver1.validation_datetime
|
|
assert_equal 0, @nameserver1.validation_counter
|
|
assert_nil @nameserver1.failed_validation_reason
|
|
|
|
assert validator.results[:nameservers][@nameserver1.hostname][:valid]
|
|
assert_empty validator.results[:errors]
|
|
end
|
|
|
|
test 'handles failed nameserver validation' do
|
|
validator = DNSValidator.new(@domain)
|
|
|
|
# Mock failed nameserver validation
|
|
validator.define_singleton_method(:validate_single_nameserver) do |ns|
|
|
{ valid: false, authoritative: false, ns_records: [], reason: 'No SOA record found' }
|
|
end
|
|
|
|
validator.send(:validate_nameservers)
|
|
|
|
@nameserver1.reload
|
|
assert_not_nil @nameserver1.validation_datetime
|
|
assert_equal 1, @nameserver1.validation_counter
|
|
assert_equal 'No SOA record found', @nameserver1.failed_validation_reason
|
|
|
|
assert_not validator.results[:nameservers][@nameserver1.hostname][:valid]
|
|
assert_includes validator.results[:errors], "Nameserver #{@nameserver1.hostname} failed validation: No SOA record found"
|
|
end
|
|
|
|
test 'validates single nameserver with mocked DNS' do
|
|
validator = DNSValidator.new(@domain)
|
|
|
|
# Create mock resolver
|
|
resolver = create_mock_resolver
|
|
|
|
# Create references to helper methods in test context
|
|
test_context = self
|
|
nameserver_hostname = @nameserver1.hostname
|
|
|
|
# Mock SOA response (authoritative)
|
|
soa_response = create_mock_dns_response([create_mock_soa_record])
|
|
ns_response = create_mock_dns_response([create_mock_ns_record(nameserver_hostname)])
|
|
|
|
resolver.define_singleton_method(:query) do |domain, type|
|
|
if type == 'SOA'
|
|
soa_response
|
|
elsif type == 'NS'
|
|
ns_response
|
|
else
|
|
test_context.create_mock_dns_response([])
|
|
end
|
|
end
|
|
|
|
validator.define_singleton_method(:create_resolver) { |ip| resolver }
|
|
|
|
result = validator.send(:validate_single_nameserver, @nameserver1)
|
|
|
|
assert result[:valid]
|
|
assert result[:authoritative]
|
|
assert_includes result[:ns_records], @nameserver1.hostname.downcase
|
|
assert_nil result[:reason]
|
|
end
|
|
|
|
test 'detects nameserver not in NS records' do
|
|
validator = DNSValidator.new(@domain)
|
|
resolver = create_mock_resolver
|
|
|
|
# Pre-create responses
|
|
test_context = self
|
|
soa_response = create_mock_dns_response([create_mock_soa_record])
|
|
ns_response = create_mock_dns_response([create_mock_ns_record('other.nameserver.com')])
|
|
|
|
resolver.define_singleton_method(:query) do |domain, type|
|
|
if type == 'SOA'
|
|
soa_response
|
|
elsif type == 'NS'
|
|
ns_response
|
|
else
|
|
test_context.create_mock_dns_response([])
|
|
end
|
|
end
|
|
|
|
validator.define_singleton_method(:create_resolver) { |ip| resolver }
|
|
|
|
result = validator.send(:validate_single_nameserver, @nameserver1)
|
|
|
|
assert_not result[:valid]
|
|
assert_equal 'Nameserver not listed in zone NS records', result[:reason]
|
|
end
|
|
|
|
test 'detects CNAME at apex' do
|
|
validator = DNSValidator.new(@domain)
|
|
resolver = create_mock_resolver
|
|
|
|
# Pre-create CNAME response
|
|
test_context = self
|
|
cname_response = create_mock_dns_response([create_mock_cname_record('example.com')])
|
|
|
|
# Mock CNAME response for SOA query
|
|
resolver.define_singleton_method(:query) do |domain, type|
|
|
cname_response
|
|
end
|
|
|
|
validator.define_singleton_method(:create_resolver) { |ip| resolver }
|
|
|
|
result = validator.send(:validate_single_nameserver, @nameserver1)
|
|
|
|
assert_not result[:valid]
|
|
assert_equal 'Domain has CNAME record at apex (invalid)', result[:reason]
|
|
end
|
|
|
|
# Story 2: DNS Records Validation Tests
|
|
|
|
test 'validates DNS records from valid nameservers' do
|
|
validator = DNSValidator.new(@domain)
|
|
|
|
# Set up nameserver validation results
|
|
validator.instance_variable_set(:@results, {
|
|
nameservers: { @nameserver1.hostname => { valid: true } },
|
|
dns_records: { a_records: [], aaaa_records: [], cname_records: [] },
|
|
errors: [],
|
|
warnings: []
|
|
})
|
|
|
|
# Pre-create responses
|
|
test_context = self
|
|
a_response = create_mock_dns_response([create_mock_a_record('192.0.2.1')])
|
|
aaaa_response = create_mock_dns_response([create_mock_aaaa_record('2001:db8::1')])
|
|
empty_response = create_mock_dns_response([])
|
|
|
|
resolver = create_mock_resolver
|
|
resolver.define_singleton_method(:query) do |domain, type|
|
|
case type
|
|
when 'A'
|
|
a_response
|
|
when 'AAAA'
|
|
aaaa_response
|
|
when 'CNAME'
|
|
empty_response
|
|
else
|
|
empty_response
|
|
end
|
|
end
|
|
|
|
validator.define_singleton_method(:create_resolver) { |ip| resolver }
|
|
|
|
validator.send(:validate_dns_records)
|
|
|
|
assert_not_empty validator.results[:dns_records][:a_records]
|
|
assert_not_empty validator.results[:dns_records][:aaaa_records]
|
|
|
|
a_record = validator.results[:dns_records][:a_records].first
|
|
assert_equal '192.0.2.1', a_record[:address]
|
|
assert_equal @nameserver1.hostname, a_record[:nameserver]
|
|
end
|
|
|
|
test 'skips DNS validation when no valid nameservers' do
|
|
validator = DNSValidator.new(@domain)
|
|
|
|
# Set up scenario with no valid nameservers
|
|
validator.instance_variable_set(:@results, {
|
|
nameservers: { @nameserver1.hostname => { valid: false } },
|
|
dns_records: { a_records: [], aaaa_records: [], cname_records: [] },
|
|
errors: [],
|
|
warnings: []
|
|
})
|
|
|
|
validator.send(:validate_dns_records)
|
|
|
|
assert_includes validator.results[:warnings], 'No valid nameservers found for DNS record validation'
|
|
assert_empty validator.results[:dns_records][:a_records]
|
|
end
|
|
|
|
# Story 4: DNSSEC Tests
|
|
|
|
test 'processes CDS records for DNSSEC sync' do
|
|
validator = DNSValidator.new(@domain)
|
|
resolver = create_mock_resolver
|
|
|
|
# Pre-create responses
|
|
test_context = self
|
|
cds_response = create_mock_dns_response([create_mock_cds_record(12345, 7, 1, 'ABC123')])
|
|
empty_response = create_mock_dns_response([])
|
|
|
|
resolver.define_singleton_method(:query) do |domain, type|
|
|
case type
|
|
when 'CDS'
|
|
cds_response
|
|
when 'CDNSKEY'
|
|
empty_response
|
|
else
|
|
empty_response
|
|
end
|
|
end
|
|
|
|
validator.define_singleton_method(:create_resolver) { |ip| resolver }
|
|
|
|
validator.send(:check_dnssec_sync_records)
|
|
|
|
assert_not_empty validator.results[:dnssec][:cds_records]
|
|
|
|
cds_record = validator.results[:dnssec][:cds_records].first
|
|
assert_equal 12345, cds_record[:key_tag]
|
|
assert_equal 7, cds_record[:algorithm]
|
|
end
|
|
|
|
# Story 6: Enforcement Actions Tests
|
|
|
|
test 'removes failed nameservers after threshold' do
|
|
@nameserver1.update!(validation_counter: 3, failed_validation_reason: 'Failed validation')
|
|
|
|
# Ensure domain has enough nameservers
|
|
@domain.nameservers.create!(hostname: 'backup.ns.com')
|
|
|
|
validator = DNSValidator.new(@domain)
|
|
|
|
# Initialize complete results structure
|
|
validator.instance_variable_set(:@results, {
|
|
nameservers: {},
|
|
dns_records: {},
|
|
dnssec: { ds_updates_needed: [] },
|
|
csync: { delegation_updates_needed: [] },
|
|
errors: [],
|
|
warnings: []
|
|
})
|
|
|
|
validator.define_singleton_method(:should_auto_remove_nameserver?) { true }
|
|
validator.define_singleton_method(:create_notification) { |text| nil }
|
|
|
|
initial_count = @domain.nameservers.count
|
|
validator.send(:apply_enforcement_actions)
|
|
|
|
assert_equal initial_count - 1, @domain.nameservers.count
|
|
assert_not @domain.nameservers.exists?(@nameserver1.id)
|
|
assert_includes validator.results[:warnings], "Automatically removed nameserver #{@nameserver1.hostname}"
|
|
end
|
|
|
|
test 'does not remove nameserver if insufficient nameservers' do
|
|
@nameserver1.update!(validation_counter: 3, failed_validation_reason: 'Failed validation')
|
|
@nameserver2.destroy # Only one nameserver left
|
|
|
|
validator = DNSValidator.new(@domain)
|
|
|
|
validator.instance_variable_set(:@results, {
|
|
nameservers: {},
|
|
dns_records: {},
|
|
dnssec: { ds_updates_needed: [] },
|
|
csync: { delegation_updates_needed: [] },
|
|
errors: [],
|
|
warnings: []
|
|
})
|
|
|
|
validator.define_singleton_method(:should_auto_remove_nameserver?) { true }
|
|
|
|
initial_count = @domain.nameservers.count
|
|
validator.send(:apply_enforcement_actions)
|
|
|
|
assert_equal initial_count, @domain.nameservers.count
|
|
assert @domain.nameservers.exists?(@nameserver1.id)
|
|
end
|
|
|
|
# Integration Tests
|
|
|
|
test 'full validation workflow' do
|
|
validator = DNSValidator.new(@domain)
|
|
|
|
# Mock all validation methods to avoid DNS calls
|
|
validator.define_singleton_method(:validate_nameservers) { nil }
|
|
validator.define_singleton_method(:validate_dns_records) { nil }
|
|
validator.define_singleton_method(:check_dnssec_sync_records) { nil }
|
|
validator.define_singleton_method(:check_csync_records) { nil }
|
|
validator.define_singleton_method(:apply_enforcement_actions) { nil }
|
|
|
|
results = validator.validate
|
|
|
|
assert_instance_of Hash, results
|
|
assert_includes results.keys, :nameservers
|
|
assert_includes results.keys, :dns_records
|
|
assert_includes results.keys, :dnssec
|
|
assert_includes results.keys, :csync
|
|
end
|
|
|
|
test 'handles validation exceptions gracefully' do
|
|
validator = DNSValidator.new(@domain)
|
|
|
|
validator.define_singleton_method(:validate_nameservers) do
|
|
raise StandardError.new('DNS timeout')
|
|
end
|
|
|
|
results = validator.validate
|
|
|
|
assert_includes results[:errors], 'Validation failed: DNS timeout'
|
|
end
|
|
|
|
# Helper method tests
|
|
|
|
test 'helper methods work correctly' do
|
|
validator = DNSValidator.new(@domain)
|
|
|
|
# Test parse_type_bitmap
|
|
assert_equal ['NS', 'A'], validator.send(:parse_type_bitmap, ['NS', 'A'])
|
|
assert_equal ['NS'], validator.send(:parse_type_bitmap, 'NS')
|
|
assert_equal [], validator.send(:parse_type_bitmap, nil)
|
|
|
|
# Test should_auto_remove_nameserver? (default is false)
|
|
assert_not validator.send(:should_auto_remove_nameserver?)
|
|
|
|
# Test create_resolver
|
|
resolver = validator.send(:create_resolver, '192.0.2.1')
|
|
assert_instance_of Dnsruby::Resolver, resolver
|
|
end
|
|
|
|
test 'handles domains without nameservers' do
|
|
domain_without_ns = Domain.new(
|
|
name: 'empty.test',
|
|
registrar: @domain.registrar,
|
|
registrant: @domain.registrant
|
|
)
|
|
|
|
validator = DNSValidator.new(domain_without_ns)
|
|
|
|
assert_nothing_raised do
|
|
validator.send(:validate_nameservers)
|
|
end
|
|
|
|
assert_empty validator.results[:nameservers]
|
|
end
|
|
|
|
private
|
|
|
|
# Simple mock helpers using basic Ruby objects
|
|
|
|
def create_mock_resolver
|
|
resolver = Object.new
|
|
resolver.define_singleton_method(:nameserver=) { |value| }
|
|
resolver.define_singleton_method(:query_timeout=) { |value| }
|
|
resolver.define_singleton_method(:retry_times=) { |value| }
|
|
resolver.define_singleton_method(:recurse=) { |value| }
|
|
resolver.define_singleton_method(:do_caching=) { |value| }
|
|
resolver
|
|
end
|
|
|
|
def create_mock_dns_response(records)
|
|
response = Object.new
|
|
response.define_singleton_method(:answer) { records }
|
|
response
|
|
end
|
|
|
|
def create_mock_soa_record(serial = 123456)
|
|
record = Object.new
|
|
record.define_singleton_method(:type) { 'SOA' }
|
|
record.define_singleton_method(:serial) { serial }
|
|
record.define_singleton_method(:instance_variable_defined?) { |var| var == '@serial' }
|
|
record
|
|
end
|
|
|
|
def create_mock_ns_record(hostname)
|
|
record = Object.new
|
|
record.define_singleton_method(:type) { 'NS' }
|
|
|
|
nsdname = Object.new
|
|
nsdname.define_singleton_method(:to_s) { hostname }
|
|
record.define_singleton_method(:nsdname) { nsdname }
|
|
|
|
record
|
|
end
|
|
|
|
def create_mock_a_record(address)
|
|
record = Object.new
|
|
record.define_singleton_method(:type) { 'A' }
|
|
record.define_singleton_method(:ttl) { 3600 }
|
|
|
|
addr = Object.new
|
|
addr.define_singleton_method(:to_s) { address }
|
|
record.define_singleton_method(:address) { addr }
|
|
|
|
record
|
|
end
|
|
|
|
def create_mock_aaaa_record(address)
|
|
record = Object.new
|
|
record.define_singleton_method(:type) { 'AAAA' }
|
|
record.define_singleton_method(:ttl) { 3600 }
|
|
|
|
addr = Object.new
|
|
addr.define_singleton_method(:to_s) { address }
|
|
record.define_singleton_method(:address) { addr }
|
|
|
|
record
|
|
end
|
|
|
|
def create_mock_cname_record(target)
|
|
record = Object.new
|
|
record.define_singleton_method(:type) { 'CNAME' }
|
|
record.define_singleton_method(:ttl) { 3600 }
|
|
|
|
cname = Object.new
|
|
cname.define_singleton_method(:to_s) { target }
|
|
record.define_singleton_method(:cname) { cname }
|
|
|
|
record
|
|
end
|
|
|
|
def create_mock_cds_record(key_tag, algorithm, digest_type, digest)
|
|
record = Object.new
|
|
record.define_singleton_method(:type) { 'CDS' }
|
|
record.define_singleton_method(:key_tag) { key_tag }
|
|
record.define_singleton_method(:algorithm) { algorithm }
|
|
record.define_singleton_method(:digest_type) { digest_type }
|
|
record.define_singleton_method(:digest) { digest }
|
|
record
|
|
end
|
|
end |