diff --git a/CHANGELOG.md b/CHANGELOG.md index b37f63e..abaac68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.6.0] - 2024-01-05 + +### Added + +- Added ability to mock SRV records. Thanks [@Siphonay](https://github.com/Siphonay) for feature suggestion + +### Updated + +- Updated gem version +- Updated gem documentation + ## [1.5.18] - 2024-01-03 ### Updated diff --git a/README.md b/README.md index 6d2dcb2..8856fa6 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,16 @@ records = { }, '1.2.3.4' => { # You can define RDNS host address without lookup prefix. It will be converted to 4.3.2.1.in-addr.arpa automatically ptr: %w[domain_1.com domain_2.com] + }, + '_sip._tcp.example.com' => { # Please use {_service._proto.domain} pattern to follow the valid RFC-2782 SRV host pattern representation + srv: [ + { + priority: 0, + weight: 10, + port: 5_060, + target: 'domain.com' + } + ] } } diff --git a/lib/dns_mock/core.rb b/lib/dns_mock/core.rb index 0bb00fe..9b0e8ce 100644 --- a/lib/dns_mock/core.rb +++ b/lib/dns_mock/core.rb @@ -4,7 +4,7 @@ require 'socket' module DnsMock - AVAILABLE_DNS_RECORD_TYPES = %i[a aaaa cname mx ns ptr soa txt].freeze + AVAILABLE_DNS_RECORD_TYPES = %i[a aaaa cname mx ns ptr soa srv txt].freeze module Error require_relative '../dns_mock/error/argument_type' @@ -33,6 +33,7 @@ module Factory require_relative '../dns_mock/record/factory/ns' require_relative '../dns_mock/record/factory/ptr' require_relative '../dns_mock/record/factory/soa' + require_relative '../dns_mock/record/factory/srv' require_relative '../dns_mock/record/factory/txt' end end @@ -47,6 +48,7 @@ module Builder require_relative '../dns_mock/record/builder/ns' require_relative '../dns_mock/record/builder/ptr' require_relative '../dns_mock/record/builder/soa' + require_relative '../dns_mock/record/builder/srv' require_relative '../dns_mock/record/builder/txt' end end diff --git a/lib/dns_mock/record/builder/srv.rb b/lib/dns_mock/record/builder/srv.rb new file mode 100644 index 0000000..f195127 --- /dev/null +++ b/lib/dns_mock/record/builder/srv.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module DnsMock + module Record + module Builder + class Srv < DnsMock::Record::Builder::Base + FACTORY_ARGS_ORDER = %i[priority weight port target].freeze + + def build + records_data.map do |record_data| + target_factory.new( + record_data: record_data.values_at(*DnsMock::Record::Builder::Srv::FACTORY_ARGS_ORDER) + ).create + end + end + end + end + end +end diff --git a/lib/dns_mock/record/factory/srv.rb b/lib/dns_mock/record/factory/srv.rb new file mode 100644 index 0000000..6a4dfb0 --- /dev/null +++ b/lib/dns_mock/record/factory/srv.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module DnsMock + module Record + module Factory + Srv = ::Class.new(DnsMock::Record::Factory::Base) do + record_type :srv + + def instance_params + record_data[0..-2] << create_dns_name(record_data.last) + end + end + end + end +end diff --git a/lib/dns_mock/server/records_dictionary_builder.rb b/lib/dns_mock/server/records_dictionary_builder.rb index 85d0747..bab0d87 100644 --- a/lib/dns_mock/server/records_dictionary_builder.rb +++ b/lib/dns_mock/server/records_dictionary_builder.rb @@ -15,6 +15,7 @@ class RecordsDictionaryBuilder [DnsMock::Record::Builder::Ns, DnsMock::Record::Factory::Ns, ::Array], [DnsMock::Record::Builder::Ptr, DnsMock::Record::Factory::Ptr, ::Array], [DnsMock::Record::Builder::Soa, DnsMock::Record::Factory::Soa, ::Array], + [DnsMock::Record::Builder::Srv, DnsMock::Record::Factory::Srv, ::Array], [DnsMock::Record::Builder::Txt, DnsMock::Record::Factory::Txt, ::Array] ] ).to_h.freeze diff --git a/lib/dns_mock/version.rb b/lib/dns_mock/version.rb index f28f382..77f081d 100644 --- a/lib/dns_mock/version.rb +++ b/lib/dns_mock/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module DnsMock - VERSION = '1.5.18' + VERSION = '1.6.0' end diff --git a/spec/dns_mock/record/builder/srv_spec.rb b/spec/dns_mock/record/builder/srv_spec.rb new file mode 100644 index 0000000..4c8b8dd --- /dev/null +++ b/spec/dns_mock/record/builder/srv_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +RSpec.describe DnsMock::Record::Builder::Srv do + describe 'class dependencies' do + subject(:builder_class) { described_class } + + it { is_expected.to be < DnsMock::Record::Builder::Base } + it { is_expected.to be_const_defined(:FACTORY_ARGS_ORDER) } + end + + describe '.call' do + subject(:builder) { described_class.call(target_factory, records_data) } + + let(:target_factory) { class_double('TargetFactory') } + let(:target_class_instance) { instance_double('TargetClass') } + let(:target_factory_instance) { instance_double('TargetFactory', create: target_class_instance) } + let(:records_data) do + (1..8).each_slice(4).map do |chunk| + DnsMock::Record::Builder::Srv::FACTORY_ARGS_ORDER.zip(chunk).to_h + end + end + + it 'returns array of target class instances' do + records_data.each do |record_data| + expect(target_factory) + .to receive(:new) + .with(record_data: record_data.values) + .and_return(target_factory_instance) + end + expect(builder).to eq(::Array.new(records_data.size) { target_class_instance }) + end + end +end diff --git a/spec/dns_mock/record/factory/srv_spec.rb b/spec/dns_mock/record/factory/srv_spec.rb new file mode 100644 index 0000000..27e74ac --- /dev/null +++ b/spec/dns_mock/record/factory/srv_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +RSpec.describe DnsMock::Record::Factory::Srv do + it { expect(described_class).to be < DnsMock::Record::Factory::Base } + + describe '#instance_params' do + subject(:instance_params) { described_class.new(record_data: record_data).instance_params } + + let(:int_params) { (0..3).to_a } + let(:dns_name) { random_hostname } + let(:record_data) { int_params + [dns_name] } + let(:expected_data) do + [ + *int_params, + create_dns_name(dns_name) + ] + end + + it 'returns prepared target class instance params' do + expect(instance_params).to eq(expected_data) + end + end + + describe '#create' do + subject(:create_factory) { described_class.new(record_data: record_data.values).create } + + let(:record_data) do + { + priority: 0, + weight: 10, + port: 5_060, + target: target + } + end + + context 'when valid record context' do + shared_examples 'returns instance of target class' do + it 'returns instance of target class' do + expect(DnsMock::Representer::Punycode).to receive(:call).with(target).and_call_original + expect(create_factory).to be_an_instance_of(described_class.target_class) + dns_names = record_data.slice(:target).transform_values do |value| + create_dns_name(ascii_hostname ? value : to_punycode_hostname(value)) + end + record_data.merge(dns_names).each { |key, value| expect(create_factory.public_send(key)).to eq(value) } + end + end + + context 'when ASCII hostname' do + let(:ascii_hostname) { true } + let(:target) { random_hostname } + + include_examples 'returns instance of target class' + end + + context 'when non ASCII hostname' do + let(:ascii_hostname) { false } + let(:target) { random_non_ascii_hostname } + + include_examples 'returns instance of target class' + end + end + + context 'when invalid record context' do + context 'when invalid mname' do + let(:target) { 42 } + let(:error_context) { "cannot interpret as DNS name: #{target}. Invalid SRV record context" } + + it_behaves_like 'target class exception wrapper' + end + end + end +end diff --git a/spec/dns_mock_spec.rb b/spec/dns_mock_spec.rb index 2021595..2c2d580 100644 --- a/spec/dns_mock_spec.rb +++ b/spec/dns_mock_spec.rb @@ -173,6 +173,27 @@ .and_minimum(soa_record[:minimum]) .config(**rspec_dns_config) end + + it 'returns predefined SRV record' do + srv_record = records_by_domain[:srv].first + + expect(domain).to have_dns + .with_type('SRV') + .and_priority(srv_record[:priority]) + .config(**rspec_dns_config) + expect(domain).to have_dns + .with_type('SRV') + .and_weight(srv_record[:weight]) + .config(**rspec_dns_config) + expect(domain).to have_dns + .with_type('SRV') + .and_port(srv_record[:port]) + .config(**rspec_dns_config) + expect(domain).to have_dns + .with_type('SRV') + .and_target(srv_record[:target]) + .config(**rspec_dns_config) + end end context 'when internationalized records' do @@ -269,6 +290,27 @@ .and_minimum(soa_record[:minimum]) .config(**rspec_dns_config) end + + it 'returns predefined SRV record' do + srv_record = records_by_domain[:srv].first + + expect(domain).to have_dns + .with_type('SRV') + .and_priority(srv_record[:priority]) + .config(**rspec_dns_config) + expect(domain).to have_dns + .with_type('SRV') + .and_weight(srv_record[:weight]) + .config(**rspec_dns_config) + expect(domain).to have_dns + .with_type('SRV') + .and_port(srv_record[:port]) + .config(**rspec_dns_config) + expect(domain).to have_dns + .with_type('SRV') + .and_target(to_punycode_hostname(srv_record[:target])) + .config(**rspec_dns_config) + end end context 'when records not found' do diff --git a/spec/support/helpers/context_generator.rb b/spec/support/helpers/context_generator.rb index 3bf2815..53f60e2 100644 --- a/spec/support/helpers/context_generator.rb +++ b/spec/support/helpers/context_generator.rb @@ -61,6 +61,14 @@ def create_records(hostname = DnsMock::RspecHelper::ContextGenerator.random_host expire: 604_800, minimum: 3_600 } + ], + srv: [ + { + priority: 0, + weight: 10, + port: 5_060, + target: random_hostname_by_ascii(hostname) + } ] }.slice(*(records.empty? ? DnsMock::AVAILABLE_DNS_RECORD_TYPES : records)) } diff --git a/spec/support/helpers/records_dictionary.rb b/spec/support/helpers/records_dictionary.rb index 62dc4be..b6db2bd 100644 --- a/spec/support/helpers/records_dictionary.rb +++ b/spec/support/helpers/records_dictionary.rb @@ -24,6 +24,14 @@ def create_records_dictionary(hostname = DnsMock::RspecHelper::ContextGenerator. expire: 604_800, minimum: 3_600 } + ], + srv: [ + { + priority: 0, + weight: 10, + port: 5_060, + target: random_hostname + } ] }.slice(*(options.empty? ? DnsMock::AVAILABLE_DNS_RECORD_TYPES : options)) }