# frozen_string_literal: true

require 'spec_helper'
require 'json'
require 'stringio'

describe RspecPuppetFacts do
  let(:metadata_file) do
    'spec/fixtures/metadata.json'
  end

  describe '.stringify_keys' do
    it {
      expect(described_class.stringify_keys({ os: { family: 'RedHat' } })).to eq({ 'os' => { 'family' => 'RedHat' } })
    }
  end

  describe '.facter_version_for_puppet_version' do
    subject(:facter_version) do
      described_class.facter_version_for_puppet_version(puppet_version)
    end

    let(:component_json_path) do
      File.expand_path(File.join(__dir__, '..', 'ext', 'puppet_agent_facter_versions.json'))
    end

    let(:puppet_version) { Puppet.version }

    context 'when the component JSON file does not exist' do
      before do
        allow(File).to receive(:file?).with(component_json_path).and_return(false)
        allow(described_class).to receive(:warning)
      end

      it 'defaults to the currently installed Facter version' do
        expect(facter_version).to eq(Facter.version)
      end

      it 'warns the user' do
        msg = /does not exist or is not readable, defaulting to/i
        expect(described_class).to receive(:warning).with(a_string_matching(msg))
        facter_version
      end
    end

    context 'when the component JSON file is unreadable' do
      before do
        allow(File).to receive(:readable?).with(component_json_path).and_return(false)
        allow(described_class).to receive(:warning)
      end

      it 'defaults to the currently installed Facter version' do
        expect(facter_version).to eq(Facter.version)
      end

      it 'warns the user' do
        msg = /does not exist or is not readable, defaulting to/i
        expect(described_class).to receive(:warning).with(a_string_matching(msg))
        facter_version
      end
    end

    context 'when the component JSON file is unparseable' do
      before do
        allow(File).to receive(:read).with(component_json_path).and_return('not json')
        allow(described_class).to receive(:warning)
      end

      it 'defaults to the currently installed Facter version' do
        expect(facter_version).to eq(Facter.version)
      end

      it 'warns the user' do
        msg = /contains invalid json, defaulting to/i
        expect(described_class).to receive(:warning).with(a_string_matching(msg))
        facter_version
      end
    end

    context 'when the passed puppet_version is nil' do
      let(:puppet_version) { nil }

      it 'defaults to the currently installed Facter version' do
        expect(facter_version).to eq(Facter.version)
      end
    end

    context 'when passed a Puppet version greater than any known version' do
      let(:puppet_version) { '999.0.0' }

      it 'returns the Facter version for the highest known Puppet version' do
        expected = JSON.parse(File.read(component_json_path)).values.max_by do |version|
          Gem::Version.new(version)
        end

        expect(facter_version).to eq(expected)
      end
    end

    context 'when passed a known Puppet version' do
      let(:puppet_version) { '5.2.0' }

      it 'returns the Facter version for that Puppet version' do
        expect(facter_version).to eq('3.9.0')
      end
    end

    context 'when passed a Puppet version between two known versions' do
      let(:puppet_version) { '5.2.5' }

      it 'returns the Facter version for the lower Puppet version' do
        expect(facter_version).to eq('3.9.0')
      end
    end

    context 'when passed a Puppet version lower than any known version' do
      let(:puppet_version) { '1.0.0' }

      before do
        allow(described_class).to receive(:warning)
      end

      it 'returns the currently installed Facter version' do
        expect(facter_version).to eq(Facter.version)
      end

      it 'warns the user' do
        msg = /unable to find puppet #{Regexp.escape(puppet_version)}.+?, defaulting to/i
        expect(described_class).to receive(:warning).with(a_string_matching(msg))
        facter_version
      end
    end
  end

  describe '#on_supported_os' do
    context 'With RSpec.configuration.facterdb_string_keys' do
      subject(:result) do
        on_supported_os(
          {
            supported_os: [
              {
                'operatingsystem' => 'Debian',
                'operatingsystemrelease' => ['12'],
              },
            ],
          },
        )
      end

      let(:get_keys) do
        proc { |r| r.keys + r.select { |_, v| v.is_a?(Hash) }.map { |_, v| get_keys.call(v) }.flatten }
      end

      context 'set to true' do
        before do
          RSpec.configuration.facterdb_string_keys = true
        end

        after do
          RSpec.configuration.facterdb_string_keys = false
        end

        it 'returns a fact set with all the keys as Strings' do
          expect(get_keys.call(result['debian-12-x86_64'])).to all(be_a(String))
        end
      end

      context 'set to false' do
        before do
          RSpec.configuration.facterdb_string_keys = false
        end

        it 'returns a fact set with all the keys as Symbols or Strings' do
          expect(get_keys.call(result['debian-12-x86_64'])).to all(be_a(Symbol).or(be_a(String)))
        end
      end
    end

    context 'Without specifying supported_os' do
      subject { on_supported_os }

      context 'Without metadata.json' do
        before do
          expect(File).to receive(:file?).with('metadata.json').and_return false
        end

        it { expect { subject }.to raise_error(StandardError, /Can't find metadata.json/) }
      end

      context 'With a metadata.json' do
        it 'can load the metadata file' do
          allow(described_class).to receive(:metadata_file).and_return(metadata_file)
          described_class.reset
          expect(described_class.metadata).to be_a Hash
          expect(described_class.metadata['name']).to eq 'mcanevet-mymodule'
        end

        context 'With a valid metadata.json' do
          let(:metadata) do
            fixture = File.read(metadata_file)
            JSON.parse fixture
          end

          before do
            allow(described_class).to receive(:metadata).and_return(metadata)
          end

          it 'returns a hash' do
            expect(subject).to be_a Hash
          end

          it 'returns supported OS' do
            expect(subject.keys).to contain_exactly(
              'debian-11-x86_64',
              'debian-12-x86_64',
              'redhat-8-x86_64',
              'redhat-9-x86_64',
            )
          end

          it 'is able to filter the received OS facts' do
            allow(described_class).to receive(:spec_facts_os_filter).and_return('redhat')
            expect(subject.keys).to contain_exactly(
              'redhat-8-x86_64',
              'redhat-9-x86_64',
            )
          end
        end

        context 'With a broken metadata.json' do
          before do
            allow(described_class).to receive(:metadata).and_return(metadata)
          end

          context 'With a missing operatingsystem_support section' do
            let(:metadata) do
              {}
            end

            it { expect { subject }.to raise_error(StandardError, /Unknown operatingsystem support/) }
          end

          context 'With a wrong operatingsystem_support section' do
            let(:metadata) do
              {
                'operatingsystem_support' => 'Ubuntu',
              }
            end

            it { expect { subject }.to raise_error(StandardError, /Unknown operatingsystem support/) }
          end
        end
      end
    end

    context 'When specifying supported_os' do
      subject do
        on_supported_os(
          {
            supported_os: [
              {
                'operatingsystem' => 'Debian',
                'operatingsystemrelease' => %w[
                  11
                  12
                ],
              },
              {
                'operatingsystem' => 'RedHat',
                'operatingsystemrelease' => %w[
                  8
                  9
                ],
              },
            ],
          },
        )
      end

      it 'returns a hash' do
        expect(subject).to be_a Hash
      end

      it 'returns supported OS' do
        expect(subject.keys).to contain_exactly(
          'debian-11-x86_64',
          'debian-12-x86_64',
          'redhat-8-x86_64',
          'redhat-9-x86_64',
        )
      end

      it 'is able to filter the received OS facts' do
        allow(described_class).to receive(:spec_facts_os_filter).and_return('redhat')
        expect(subject.keys).to contain_exactly(
          'redhat-8-x86_64',
          'redhat-9-x86_64',
        )
      end
    end

    context 'When specifying a supported_os with a single release as a String' do
      subject(:factsets) do
        on_supported_os(
          {
            supported_os: [
              { 'operatingsystem' => 'RedHat', 'operatingsystemrelease' => '9' },
            ],
          },
        )
      end

      it 'returns a Hash' do
        expect(factsets).to be_a(Hash)
      end

      it 'returns a fact set for the specified release' do
        expect(factsets).to a_hash_including('redhat-9-x86_64' => hash_including({ os: hash_including({ 'release' => hash_including({ 'major' => '9' }) }) }))
      end
    end

    context 'When testing Ubuntu' do
      subject do
        on_supported_os(
          {
            supported_os: [
              {
                'operatingsystem' => 'Ubuntu',
                'operatingsystemrelease' => [
                  '18.04',
                  '20.04',
                  '22.04',
                ],
              },
            ],
          },
        )
      end

      it 'returns a hash' do
        expect(subject).to be_a Hash
      end

      it 'returns supported OS' do
        expect(subject.keys).to contain_exactly(
          'ubuntu-18.04-x86_64',
          'ubuntu-20.04-x86_64',
          'ubuntu-22.04-x86_64',
        )
      end
    end

    context 'When testing FreeBSD 10' do
      subject do
        on_supported_os(
          {
            supported_os: [
              {
                'operatingsystem' => 'FreeBSD',
                'operatingsystemrelease' => [
                  '13',
                ],
              },
            ],
            facterversion: '4.5',
          },
        )
      end

      it 'returns a hash' do
        expect(subject).to be_a Hash
      end

      it 'returns supported OS' do
        expect(subject.keys).to contain_exactly(
          'freebsd-13-amd64',
        )
      end
    end

    context 'When testing OpenBSD' do
      subject do
        on_supported_os(
          {
            supported_os: [
              {
                'operatingsystem' => 'OpenBSD',
                'operatingsystemrelease' => [
                  '7.5',
                ],
              },
            ],
            facterversion: '4.7',
          },
        )
      end

      it 'returns a hash' do
        expect(subject).to be_a Hash
      end

      it 'returns supported OS' do
        expect(subject.keys).to contain_exactly(
          'openbsd-7-amd64',
        )
      end
    end

    context 'When testing Solaris 11' do
      subject do
        on_supported_os(
          {
            supported_os: [
              {
                'operatingsystem' => 'Solaris',
                'operatingsystemrelease' => [
                  '11',
                ],
              },
            ],
            facterversion: '4.0',
          },
        )
      end

      it 'returns a hash' do
        expect(subject).to be_a Hash
      end

      it 'returns supported OS' do
        pending('2024-06-07: we dont have a suitable solaris 11 factset in facterdb')
        expect(subject.keys).to contain_exactly(
          'solaris-11-i86pc',
        )
      end
    end

    context 'When testing AIX 7.1' do
      subject do
        on_supported_os(
          {
            supported_os: [
              {
                'operatingsystem' => 'AIX',
                'operatingsystemrelease' => [
                  '7.1', '7100',
                ],
              },
            ],
            facterversion: '3.9',
          },
        )
      end

      it 'returns a hash' do
        expect(subject).to be_a Hash
      end

      it 'returns supported OS' do
        # NOTE: See FACT-1827 for details on the IBM,8284-22A part
        # That has to match whatever hardware generated the facts file.
        pending('2024-06-07: we dont have a suitable solaris 11 factset in facterdb')
        expect(subject.keys).to contain_exactly(
          'aix-7100-IBM,8284-22A',
        )
      end
    end

    context 'When testing Windows' do
      subject do
        on_supported_os(
          {
            supported_os: [
              {
                'operatingsystem' => 'Windows',
                'operatingsystemrelease' => release,
              },
            ],
            facterversion: facterversion,
          },
        )
      end

      let(:facterversion) { '4.2' }

      context 'with a standard release' do
        let(:release) { ['10'] }

        it { is_expected.to be_a(Hash) }
        it { is_expected.to match('windows-10-x86_64' => an_instance_of(Hash)) }
      end

      context 'with a revision release' do
        let(:release) { ['2012 R2'] }

        it { is_expected.to be_a(Hash) }
        it { is_expected.to match('windows-2012 R2-x86_64' => an_instance_of(Hash)) }
      end

      context 'with a Server prefixed release' do
        let(:release) { ['Server 2012'] }

        it { is_expected.to be_a(Hash) }
        it { is_expected.to match('windows-2012-x86_64' => an_instance_of(Hash)) }
      end

      context 'with a 2016 release' do
        let(:release) { ['2016'] }

        it { is_expected.to be_a(Hash) }
        it { is_expected.to match('windows-2016-x86_64' => an_instance_of(Hash)) }
      end
    end

    context 'When operatingsystemrelease has space' do
      subject do
        on_supported_os(
          {
            supported_os: [
              {
                'operatingsystem' => 'SLES',
                'operatingsystemrelease' => [
                  '11 SP1',
                ],
              },
            ],
          },
        )
      end

      it 'returns a hash' do
        expect(subject).to be_a Hash
      end

      it 'returns supported OS' do
        pending('2024-06-7: facterdb has no factset with space in the system release')
        expect(subject.keys).to contain_exactly('sles-11-x86_64')
      end
    end

    context 'When specifying wrong supported_os' do
      subject do
        on_supported_os(
          {
            supported_os: [
              {
                'operatingsystem' => 'Debian',
                'operatingsystemrelease' => [
                  '4',
                ],
              },
            ],
          },
        )
      end

      it 'outputs warning message' do
        expect(described_class).to receive(:warning).with(/No facts were found in the FacterDB/)
        subject
      end
    end

    context 'When specifying rolling release operating system' do
      subject do
        on_supported_os(
          {
            supported_os: [
              {
                'operatingsystem' => 'Debian',
                'operatingsystemrelease' => [
                  '12',
                ],
              },
              {
                'operatingsystem' => 'Gentoo',
              },
            ],
            facterversion: '4.6',
          },
        )
      end

      it 'returns a hash' do
        expect(subject).to be_a Hash
      end

      it 'returns supported OS' do
        expect(subject.keys).to contain_exactly(a_string_matching(/gentoo-\d+-x86_64/), 'debian-12-x86_64')
      end
    end

    context 'When the operatingsystemrelease contains parens' do
      subject do
        on_supported_os(
          {
            supported_os: [
              {
                'operatingsystem' => 'IOS',
                'operatingsystemrelease' => ['12.2(25)EWA9'],
              },
            ],
          },
        )
      end

      before do
        allow(described_class).to receive(:warning).with(a_string_matching(/no facts were found/i))
      end

      it 'escapes the parens in the filter' do
        filter = {
          'os.name' => 'IOS',
          'os.release.full' => '/^12\\.2\\(25\\)EWA9/',
          'os.hardware' => 'x86_64',
        }

        expect(FacterDB).to receive(:get_facts).with(filter, symbolize_keys: true).once
        subject
      end

      it 'does not raise an error' do
        expect { subject }.not_to raise_error
      end
    end

    context 'With a default Facter version specified in the RSpec configuration' do
      subject do
        on_supported_os(
          supported_os: [
            { 'operatingsystem' => 'CentOS', 'operatingsystemrelease' => %w[9] },
          ],
        )
      end

      before do
        RSpec.configuration.default_facter_version = '4.6.1'
      end

      after do
        RSpec.configuration.default_facter_version = Facter.version
      end

      it 'returns facts from the specified default Facter version' do
        expect(subject).to match(
          'centos-9-x86_64' => include(
            facterversion: /\A4\.6\./,
          ),
        )
      end
    end

    context 'With a version that is above the current gem' do
      subject do
        on_supported_os(
          supported_os: [
            { 'operatingsystem' => 'CentOS', 'operatingsystemrelease' => %w[9] },
          ],
          facterversion: '4.7.99',
        )
      end

      before do
        allow(Facter).to receive(:version).and_return('4.6')
      end

      it 'returns facts from a facter version matching version and below' do
        expect(subject).to match(
          'centos-9-x86_64' => include(
            facterversion: /\A4\.[0-7]\./,
          ),
        )
      end

      context 'With SPEC_FACTS_STRICT set to `yes`' do
        before do
          allow(described_class).to receive(:spec_facts_strict?).and_return(true)
        end

        it 'errors' do
          expect { subject }.to raise_error ArgumentError, /No facts were found in the FacterDB.*aborting/
        end
      end
    end

    context 'With a custom facterversion (4.6) in the options hash' do
      subject do
        on_supported_os(
          supported_os: [
            { 'operatingsystem' => 'CentOS', 'operatingsystemrelease' => %w[9] },
          ],
          facterversion: '4.6',
        )
      end

      it 'returns facts from a facter version matching 4.6' do
        expect(subject).to match('centos-9-x86_64' => include(facterversion: '4.6.1'))
      end
    end

    context 'With a custom facterversion (4.6.1) in the options hash' do
      subject do
        on_supported_os(
          supported_os: [
            { 'operatingsystem' => 'CentOS', 'operatingsystemrelease' => %w[9] },
          ],
          facterversion: '4.6.1',
        )
      end

      it 'returns facts from a facter version matching 4.6.1' do
        expect(subject).to match('centos-9-x86_64' => include(facterversion: '4.6.1'))
      end
    end

    context 'With an invalid facterversion in the options hash' do
      let(:method_call) do
        on_supported_os(
          supported_os: [
            { 'operatingsystem' => 'CentOS', 'operatingsystemrelease' => %w[7] },
          ],
          facterversion: '3',
        )
      end

      it 'raises an error' do
        expect { method_call }.to raise_error(ArgumentError,
                                              /:facterversion must be in the /)
      end
    end

    context 'Downgrades to a facter version with facts per OS' do
      subject do
        on_supported_os(
          supported_os: [
            { 'operatingsystem' => 'CentOS', 'operatingsystemrelease' => %w[9] },
            { 'operatingsystem' => 'Debian', 'operatingsystemrelease' => %w[12] },
          ],
          facterversion: '4.6.1',
        )
      end

      before do
        allow(FacterDB).to receive(:get_facts).and_call_original
        allow(FacterDB).to receive(:get_facts).with(
          { 'os.name' => 'CentOS', 'os.release.full' => '/^9/', 'os.hardware' => 'x86_64' }, symbolize_keys: true
        ).and_wrap_original do |m, *args|
          m.call(*args).reject { |facts| facts[:facterversion].start_with?('4.6.') }
        end
      end

      it 'returns CentOS facts from a facter version matching 4.5' do
        expect(subject).to include('centos-9-x86_64' => include(facterversion: '4.5.2'))
      end

      it 'returns Debian facts from a facter version matching 4.6.1' do
        expect(subject).to include('debian-12-x86_64' => include(facterversion: '4.6.1'))
      end
    end
  end

  describe '#add_custom_fact' do
    subject do
      on_supported_os(
        {
          supported_os: [
            {
              'operatingsystem' => 'RedHat',
              'operatingsystemrelease' => %w[
                8
                9
              ],
            },
          ],
        },
      )
    end

    before do
      described_class.reset
    end

    it 'adds a simple fact and value' do
      add_custom_fact 'root_home', '/root'
      expect(subject['redhat-9-x86_64'][:root_home]).to eq '/root'
    end

    it 'merges a fact value into fact when merge_facts passed' do
      add_custom_fact :identity, { 'user' => 'test_user' }, merge_facts: true
      expect(subject['redhat-9-x86_64'][:identity]).to eq(
        {
          'gid' => 0,
          'group' => 'root',
          'privileged' => true,
          'uid' => 0,
          'user' => 'test_user',
        },
      )
    end

    it 'overwrites fact' do
      add_custom_fact :identity, { 'user' => 'other_user' }
      expect(subject['redhat-9-x86_64'][:identity]).to eq(
        {
          'user' => 'other_user',
        },
      )
    end

    it 'confines a fact to a particular operating system' do
      add_custom_fact 'root_home', '/root', confine: 'redhat-9-x86_64'
      expect(subject['redhat-9-x86_64'][:root_home]).to eq '/root'
      expect(subject['redhat-8-x86_64'][:root_home]).to be_nil
    end

    it 'excludes a fact from a particular operating system' do
      add_custom_fact 'root_home', '/root', exclude: 'redhat-9-x86_64'
      expect(subject['redhat-9-x86_64'][:root_home]).to be_nil
      expect(subject['redhat-8-x86_64'][:root_home]).to eq '/root'
    end

    it 'takes a proc as a value' do
      add_custom_fact 'root_home', ->(_os, _facts) { '/root' }
      expect(subject['redhat-9-x86_64'][:root_home]).to eq '/root'
    end

    it 'accepts sym fact key and stores fact key as sym' do
      add_custom_fact :root_home, ->(_os, _facts) { '/root' }
      expect(subject['redhat-9-x86_64'][:root_home]).to eq '/root'
    end
  end

  describe '#misc' do
    it 'has a common facts structure' do
      described_class.reset
      expect(subject.common_facts).to be_a Hash
      expect(subject.common_facts).not_to be_empty
    end

    it 'does not add "augeasversion" if Augeas is supported' do
      allow(described_class).to receive(:augeas?).and_return(false)
      described_class.reset
      expect(subject.common_facts).not_to include(:augeasversion)
    end

    it 'determines the Augeas version if Augeas is supported' do
      module AugeasStub # rubocop:todo Lint/ConstantDefinitionInBlock
        NO_MODL_AUTOLOAD = true
        def self.open(*_args)
          self
        end

        def self.get(*_args)
          'my_version'
        end
      end

      allow(described_class).to receive(:augeas?).and_return(true)
      stub_const('Augeas', AugeasStub)
      described_class.reset
      expect(subject.common_facts[:augeasversion]).to eq 'my_version'
    end

    context 'when mcollective is available' do
      module MCollectiveStub # rubocop:todo Lint/ConstantDefinitionInBlock
        VERSION = 'my_version'
      end

      before do
        allow(described_class).to receive(:mcollective?).and_return(true)
        stub_const('MCollective', MCollectiveStub)
        described_class.reset
      end

      it 'includes an "mco_version" fact' do
        expect(subject.common_facts).to include(mco_version: 'my_version')
      end
    end

    context 'when mcollective is not available' do
      before do
        allow(described_class).to receive(:mcollective?).and_return(false)
        described_class.reset
      end

      it 'does not include an "mco_version" fact' do
        expect(subject.common_facts).not_to include(:mco_version)
      end
    end
  end

  describe '.facter_version_to_strict_requirement' do
    subject { described_class.facter_version_to_strict_requirement(version) }

    context 'when passed a version that is a complex requirement' do
      let(:version) { '~> 2.4' }

      it { is_expected.to be_instance_of(Gem::Requirement) }
    end

    context 'when passed a version that is major' do
      let(:version) { '1' }

      it { is_expected.to be_instance_of(Gem::Requirement) }
    end
  end

  describe '.facter_version_to_strict_requirement_string' do
    subject { described_class.facter_version_to_strict_requirement_string(version) }

    context 'when passed a version that is a complex requirement' do
      let(:version) { '~> 2.4' }

      it { is_expected.to eq('~> 2.4') }
    end

    context 'when passed a version that is major' do
      let(:version) { '1' }

      it { is_expected.to eq('~> 1.0') }
    end

    context 'when passed a version that is major.minor' do
      let(:version) { '1.2' }

      it { is_expected.to eq('~> 1.2.0') }
    end

    context 'when passed a version that is major.minor.patch' do
      let(:version) { '1.2.3' }

      it { is_expected.to eq('~> 1.2.3.0') }
    end
  end

  describe '.facter_version_to_loose_requirement' do
    subject { described_class.facter_version_to_loose_requirement(version) }

    context 'when passed a version that is a complex requirement' do
      let(:version) { '~> 2.4' }

      it { is_expected.to be_nil }
    end

    context 'when passed a version that is major' do
      let(:version) { '1' }

      it { is_expected.to be_instance_of(Gem::Requirement) }
    end
  end

  describe '.facter_version_to_loose_requirement_string' do
    subject { described_class.facter_version_to_loose_requirement_string(version) }

    context 'when passed a version that is a complex requirement (1)' do
      let(:version) { '~> 2.4' }

      it { is_expected.to be_nil }
    end

    context 'when passed a version that is a complex requirement (2)' do
      let(:version) { '>= 3 < 5' }

      it { is_expected.to be_nil }
    end

    context 'when passed a version that is major (1)' do
      let(:version) { '1' }

      it { is_expected.to eq('< 2') }
    end

    context 'when passed a version that is major (2)' do
      let(:version) { '9' }

      it { is_expected.to eq('< 10') }
    end

    context 'when passed a version that is major (3)' do
      let(:version) { '10' }

      it { is_expected.to eq('< 11') }
    end

    context 'when passed a version that is major.minor (1)' do
      let(:version) { '1.2' }

      it { is_expected.to eq('< 1.3') }
    end

    context 'when passed a version that is major.minor (2)' do
      let(:version) { '10.2' }

      it { is_expected.to eq('< 10.3') }
    end

    context 'when passed a version that is major.minor (3)' do
      let(:version) { '1.20' }

      it { is_expected.to eq('< 1.21') }
    end

    context 'when passed a version that is major.minor (4)' do
      let(:version) { '10.20' }

      it { is_expected.to eq('< 10.21') }
    end

    context 'when passed a version that is major.minor.patch (1)' do
      let(:version) { '1.2.3' }

      it { is_expected.to eq('< 1.3') }
    end

    context 'when passed a version that is major.minor.patch (2)' do
      let(:version) { '10.2.3' }

      it { is_expected.to eq('< 10.3') }
    end

    context 'when passed a version that is major.minor.patch (3)' do
      let(:version) { '1.20.3' }

      it { is_expected.to eq('< 1.21') }
    end

    context 'when passed a version that is major.minor.patch (4)' do
      let(:version) { '10.20.3' }

      it { is_expected.to eq('< 10.21') }
    end
  end
end
