require 'spec_helper'

module Omnibus
  describe BuildVersion do
    let(:git_describe) { '11.0.0-alpha1-207-g694b062' }
    let(:valid_semver_regex) { /^\d+\.\d+\.\d+(\-[\dA-Za-z\-\.]+)?(\+[\dA-Za-z\-\.]+)?$/ }
    let(:valid_git_describe_regex) { /^\d+\.\d+\.\d+(\-[A-Za-z0-9\-\.]+)?(\-\d+\-g[0-9a-f]+)?$/ }

    subject(:build_version) { described_class.new }

    before do
      allow_any_instance_of(described_class).to receive(:shellout)
        .and_return(double('ouput', stdout: git_describe, exitstatus: 0))
    end

    describe 'git describe parsing' do

      # we prefer our git tags to be SemVer compliant

      # release version
      context '11.0.1' do
        let(:git_describe) { '11.0.1' }
        its(:version_tag) { should == '11.0.1' }
        its(:prerelease_tag) { should be_nil }
        its(:git_sha_tag) { should be_nil }
        its(:commits_since_tag) { should == 0 }
        its(:development_version?) { should be_truthy }
        its(:prerelease_version?) { should be_falsey }
      end

      # SemVer compliant prerelease version
      context '11.0.0-alpha.2' do
        let(:git_describe) { '11.0.0-alpha.2' }
        its(:version_tag) { should == '11.0.0' }
        its(:prerelease_tag) { should == 'alpha.2' }
        its(:git_sha_tag) { should be_nil }
        its(:commits_since_tag) { should == 0 }
        its(:development_version?) { should be_falsey }
        its(:prerelease_version?) { should be_truthy }
      end

      # full git describe string
      context '11.0.0-alpha.3-59-gf55b180' do
        let(:git_describe) { '11.0.0-alpha.3-59-gf55b180' }
        its(:version_tag) { should == '11.0.0' }
        its(:prerelease_tag) { should == 'alpha.3' }
        its(:git_sha_tag) { should == 'f55b180' }
        its(:commits_since_tag) { should == 59 }
        its(:development_version?) { should be_falsey }
        its(:prerelease_version?) { should be_truthy }
      end

      # Degenerate git tag formats

      # RubyGems compliant git tag
      context '10.16.0.rc.0' do
        let(:git_describe) { '10.16.0.rc.0' }
        its(:version_tag) { should == '10.16.0' }
        its(:prerelease_tag) { should == 'rc.0' }
        its(:git_sha_tag) { should be_nil }
        its(:commits_since_tag) { should == 0 }
        its(:development_version?) { should be_falsey }
        its(:prerelease_version?) { should be_truthy }
      end

      # dash seperated prerelease
      context '11.0.0-alpha-2' do
        let(:git_describe) { '11.0.0-alpha-2' }
        its(:version_tag) { should == '11.0.0' }
        its(:prerelease_tag) { should == 'alpha-2' }
        its(:git_sha_tag) { should be_nil }
        its(:commits_since_tag) { should == 0 }
        its(:development_version?) { should be_falsey }
        its(:prerelease_version?) { should be_truthy }
      end

      # dash seperated prerelease full git describe string
      context '11.0.0-alpha-2-59-gf55b180' do
        let(:git_describe) { '11.0.0-alpha-2-59-gf55b180' }
        its(:version_tag) { should == '11.0.0' }
        its(:prerelease_tag) { should == 'alpha-2' }
        its(:git_sha_tag) { should == 'f55b180' }
        its(:commits_since_tag) { should == 59 }
        its(:development_version?) { should be_falsey }
        its(:prerelease_version?) { should be_truthy }
      end

      # WTF git tag
      context '11.0.0-alpha2' do
        let(:git_describe) { '11.0.0-alpha2' }
        its(:version_tag) { should == '11.0.0' }
        its(:prerelease_tag) { should == 'alpha2' }
        its(:git_sha_tag) { should be_nil }
        its(:commits_since_tag) { should == 0 }
        its(:development_version?) { should be_falsey }
        its(:prerelease_version?) { should be_truthy }
      end
    end

    describe 'semver output' do
      let(:today_string) { Time.now.utc.strftime('%Y%m%d') }

      it 'generates a valid semver version' do
        expect(build_version.semver).to match(valid_semver_regex)
      end

      it "generates a version matching format 'MAJOR.MINOR.PATCH-PRERELEASE+TIMESTAMP.git.COMMITS_SINCE.GIT_SHA'" do
        expect(build_version.semver).to match(/11.0.0-alpha1\+#{today_string}[0-9]+.git.207.694b062/)
      end

      it "uses ENV['BUILD_ID'] to generate timestamp if set" do
        stub_env('BUILD_ID', '2012-12-25_16-41-40')
        expect(build_version.semver).to eq('11.0.0-alpha1+20121225164140.git.207.694b062')
      end

      it "fails on invalid ENV['BUILD_ID'] values" do
        stub_env('BUILD_ID', 'AAAA')
        expect { build_version.semver }.to raise_error(ArgumentError)
      end

      context 'prerelease version with dashes' do
        let(:git_describe) { '11.0.0-alpha-3-207-g694b062' }

        it 'converts all dashes to dots' do
          expect(build_version.semver).to match(/11.0.0-alpha.3\+#{today_string}[0-9]+.git.207.694b062/)
        end
      end

      context 'exact version' do
        let(:git_describe) { '11.0.0-alpha2' }

        it 'appends a timestamp with no git info' do
          expect(build_version.semver).to match(/11.0.0-alpha2\+#{today_string}[0-9]+/)
        end
      end

      describe 'appending a timestamp' do
        let(:git_describe) { '11.0.0-alpha-3-207-g694b062' }

        context 'by default' do
          it 'appends a timestamp' do
            expect(build_version.semver).to match(/11.0.0-alpha.3\+#{today_string}[0-9]+.git.207.694b062/)
          end
        end

        context 'when Config.append_timestamp is true' do
          before { Config.append_timestamp(true) }

          it 'appends a timestamp' do
            expect(build_version.semver).to match(/11.0.0-alpha.3\+#{today_string}[0-9]+.git.207.694b062/)
          end
        end

        context 'when Config.append_timestamp is false' do
          before { Config.append_timestamp(false) }

          it 'does not append a timestamp' do
            expect(build_version.semver).to match(/11.0.0-alpha.3\+git.207.694b062/)
          end
        end
      end
    end

    describe 'git describe output' do
      it 'generates a valid git describe version' do
        expect(build_version.git_describe).to match(valid_git_describe_regex)
      end

      it "generates a version matching format 'MAJOR.MINOR.PATCH-PRELEASE.COMMITS_SINCE-gGIT_SHA'" do
        expect(build_version.git_describe).to eq(git_describe)
      end
    end

    describe 'deprecated full output' do
      it 'generates a valid git describe version' do
        expect(BuildVersion.full).to match(valid_git_describe_regex)
      end

      it 'outputs a deprecation message' do
        output = capture_logging { BuildVersion.full }
        expect(output).to include('DEPRECATED')
      end
    end

    describe '`git describe` command failure' do
      before do
        stderr = <<-STDERR
  fatal: No tags can describe '809ea1afcce67e1148c1bf0822d40a7ef12c380e'.
  Try --always, or create some tags.
        STDERR
        allow(build_version).to receive(:shellout)
          .and_return(double('ouput', stderr: stderr, exitstatus: 128))
      end
      it 'sets the version to 0.0.0' do
        expect(build_version.git_describe).to eq('0.0.0')
      end
    end

    describe '#initialize `path` parameter' do
      let(:path) { '/some/fake/path' }
      subject(:build_version) { BuildVersion.new(path) }

      it 'runs `git describe` at an alternate path' do
        expect(build_version).to receive(:shellout)
          .with('git describe --tags', cwd: path)
          .and_return(double('ouput', stdout: git_describe, exitstatus: 0))
        build_version.git_describe
      end
    end
  end
end