Validating Domain Model Implementation in Postal Server
This comprehensive test suite validates the Domain model functionality in the Postal email server application, covering domain verification, DKIM key management, and DNS record handling. The tests ensure proper domain validation, relationship management, and security features implementation.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
postalserver/postal
spec/models/domain_spec.rb
# frozen_string_literal: true
# == Schema Information
#
# Table name: domains
#
# id :integer not null, primary key
# dkim_error :string(255)
# dkim_identifier_string :string(255)
# dkim_private_key :text(65535)
# dkim_status :string(255)
# dns_checked_at :datetime
# incoming :boolean default(TRUE)
# mx_error :string(255)
# mx_status :string(255)
# name :string(255)
# outgoing :boolean default(TRUE)
# owner_type :string(255)
# return_path_error :string(255)
# return_path_status :string(255)
# spf_error :string(255)
# spf_status :string(255)
# use_for_any :boolean
# uuid :string(255)
# verification_method :string(255)
# verification_token :string(255)
# verified_at :datetime
# created_at :datetime
# updated_at :datetime
# owner_id :integer
# server_id :integer
#
# Indexes
#
# index_domains_on_server_id (server_id)
# index_domains_on_uuid (uuid)
#
require "rails_helper"
describe Domain do
subject(:domain) { build(:domain) }
describe "relationships" do
it { is_expected.to belong_to(:server).optional }
it { is_expected.to belong_to(:owner).optional }
it { is_expected.to have_many(:routes) }
it { is_expected.to have_many(:track_domains) }
end
describe "validations" do
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:name).scoped_to([:owner_type, :owner_id]).case_insensitive.with_message("is already added") }
it { is_expected.to allow_value("example.com").for(:name) }
it { is_expected.to allow_value("example.co.uk").for(:name) }
it { is_expected.to_not allow_value("EXAMPLE.COM").for(:name) }
it { is_expected.to_not allow_value("example.com ").for(:name) }
it { is_expected.to_not allow_value("example com").for(:name) }
it { is_expected.to validate_inclusion_of(:verification_method).in_array(Domain::VERIFICATION_METHODS) }
end
describe "creation" do
it "creates a new dkim identifier string" do
expect { domain.save }.to change { domain.dkim_identifier_string }.from(nil).to(match(/\A[a-zA-Z0-9]{6}\z/))
end
it "generates a new dkim key" do
expect { domain.save }.to change { domain.dkim_private_key }.from(nil).to(match(/\A-+BEGIN RSA PRIVATE KEY-+/))
end
it "generates a UUID" do
expect { domain.save }.to change { domain.uuid }.from(nil).to(/[a-f0-9-]{36}/)
end
end
describe ".verified" do
it "returns verified domains only" do
verified_domain = create(:domain)
create(:domain, :unverified)
expect(described_class.verified).to eq [verified_domain]
end
end
context "when verification method changes" do
context "to DNS" do
let(:domain) { create(:domain, :unverified, verification_method: "Email") }
it "generates a DNS suitable verification token" do
domain.verification_method = "DNS"
expect { domain.save }.to change { domain.verification_token }.from(match(/\A\d{6}\z/)).to(match(/\A[A-Za-z0-9+]{32}\z/))
end
end
context "to Email" do
let(:domain) { create(:domain, :unverified, verification_method: "DNS") }
it "generates an email suitable verification token" do
domain.verification_method = "Email"
expect { domain.save }.to change { domain.verification_token }.from(match(/\A[A-Za-z0-9+]{32}\z/)).to(match(/\A\d{6}\z/))
end
end
end
describe "#verified?" do
context "when the domain is verified" do
it "returns true" do
expect(domain.verified?).to be true
end
end
context "when the domain is not verified" do
let(:domain) { build(:domain, :unverified) }
it "returns false" do
expect(domain.verified?).to be false
end
end
end
describe "#mark_as_verified" do
context "when already verified" do
it "returns false" do
expect(domain.mark_as_verified).to be false
end
end
context "when unverified" do
let(:domain) { create(:domain, :unverified) }
it "sets the verification time" do
expect { domain.mark_as_verified }.to change { domain.verified_at }.from(nil).to(kind_of(Time))
end
end
end
describe "#parent_domains" do
context "at level 1" do
let(:domain) { build(:domain, name: "example.com") }
it "returns the current domain only" do
expect(domain.parent_domains).to eq ["example.com"]
end
end
context "at level 2" do
let(:domain) { build(:domain, name: "test.example.com") }
it "returns the current domain plus its parent" do
expect(domain.parent_domains).to eq ["test.example.com", "example.com"]
end
end
context "at level 3 (and higher)" do
let(:domain) { build(:domain, name: "sub.test.example.com") }
it "returns the current domain plus its parents" do
expect(domain.parent_domains).to eq ["sub.test.example.com", "test.example.com", "example.com"]
end
end
end
describe "#generate_dkim_key" do
it "generates a new dkim key" do
expect { domain.generate_dkim_key }.to change { domain.dkim_private_key }.from(nil).to(match(/\A-+BEGIN RSA PRIVATE KEY-+/))
end
end
describe "#dkim_key" do
context "when the domain has a DKIM key" do
let(:domain) { create(:domain) }
it "returns the dkim key as a OpenSSL::PKey::RSA" do
expect(domain.dkim_key).to be_a OpenSSL::PKey::RSA
expect(domain.dkim_key.to_s).to eq domain.dkim_private_key
end
end
context "when the domain has no DKIM key" do
let(:domain) { build(:domain) }
it "returns nil" do
expect(domain.dkim_key).to be_nil
end
end
end
describe "#to_param" do
context "when the domain has not been saved" do
it "returns nil" do
expect(domain.to_param).to be_nil
end
end
context "when the domain has been saved" do
before do
domain.save
end
it "returns the UUID" do
expect(domain.to_param).to eq domain.uuid
end
end
end
describe "#verification_email_addresses" do
let(:domain) { build(:domain, name: "example.com") }
it "returns the verification email addresses" do
expect(domain.verification_email_addresses).to eq [
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
]
end
end
describe "#spf_record" do
it "returns the SPF record" do
expect(domain.spf_record).to eq "v=spf1 a mx include:#{Postal::Config.dns.spf_include} ~all"
end
end
describe "#dkim_record" do
context "when the domain has no DKIM key" do
it "returns nil" do
expect(domain.dkim_record).to be_nil
end
end
context "when the domain has a DKIM key" do
before do
domain.save
end
it "returns the DKIM record" do
expect(domain.dkim_record).to match(/\Av=DKIM1; t=s; h=sha256; p=.*;\z/)
end
end
end
describe "#dkim_identifier" do
context "when the domain has no dkim identifier string" do
it "returns nil" do
expect(domain.dkim_identifier).to be_nil
end
end
context "when the domain has a dkim identifier string" do
before do
domain.save
end
it "returns the DKIM identifier" do
expect(domain.dkim_identifier).to eq "#{Postal::Config.dns.dkim_identifier}-#{domain.dkim_identifier_string}"
end
end
end
describe "#dkim_record_name" do
context "when the domain has no dkim identifier string" do
it "returns nil" do
expect(domain.dkim_record_name).to be_nil
end
end
context "when the domain has a dkim identifier string" do
before do
domain.save
end
it "returns the DKIM identifier" do
expect(domain.dkim_record_name).to eq "#{Postal::Config.dns.dkim_identifier}-#{domain.dkim_identifier_string}._domainkey"
end
end
end
describe "#return_path_domain" do
it "returns the return path domain" do
expect(domain.return_path_domain).to eq "#{Postal::Config.dns.custom_return_path_prefix}.#{domain.name}"
end
end
describe "#dns_verification_string" do
let(:domain) { create(:domain, verification_method: "DNS") }
it "returns the DNS verification string" do
expect(domain.dns_verification_string).to eq "#{Postal::Config.dns.domain_verify_prefix} #{domain.verification_token}"
end
end
describe "#resolver" do
context "when the local nameservers should be used" do
before do
allow(Postal::Config.postal).to receive(:use_local_ns_for_domain_verification?).and_return(true)
end
it "uses the local DNS" do
expect(domain.resolver).to eq DNSResolver.local
end
end
context "when local nameservers should not be used" do
it "uses the a resolver for this domain" do
allow(DNSResolver).to receive(:for_domain).with(domain.name).and_return(DNSResolver.new(["1.2.3.4"]))
expect(domain.resolver).to be_a DNSResolver
expect(domain.resolver.nameservers).to eq ["1.2.3.4"]
end
end
end
describe "#verify_with_dns" do
context "when the verification method is not DNS" do
let(:domain) { build(:domain, verification_method: "Email") }
it "returns false" do
expect(domain.verify_with_dns).to be false
end
end
context "when a TXT record is found that matches" do
let(:domain) { create(:domain, :unverified) }
before do
allow(domain.resolver).to receive(:txt).with(domain.name).and_return([domain.dns_verification_string])
end
it "returns true" do
expect(domain.verify_with_dns).to be true
end
it "sets the verification time" do
expect { domain.verify_with_dns }.to change { domain.verified_at }.from(nil).to(kind_of(Time))
end
end
context "when no TXT record is found" do
let(:domain) { create(:domain, :unverified) }
before do
allow(domain.resolver).to receive(:txt).with(domain.name).and_return(["something", "something else"])
end
it "returns false" do
expect(domain.verify_with_dns).to be false
end
it "does not set the verification time" do
expect { domain.verify_with_dns }.to_not change { domain.verified_at } # rubocop:disable Lint/AmbiguousBlockAssociation
end
end
end
end