require "spec_helper"

module Omnibus
  describe GitCache do
    let(:project) do
      Project.new("/path/to/demo.rb").evaluate do
        name "demo"
        install_dir "/opt/demo"

        build_version "1.0.0"

        maintainer "Chef Software, Inc"
        homepage "http://getchef.com"

        dependency "preparation"
        dependency "snoopy"
        dependency "zlib"
      end
    end

    let(:install_dir) { project.install_dir }

    let(:zlib) do
      Software.new(project, "zlib.rb").evaluate do
        name "zlib"
        default_version "1.7.2"
      end
    end

    let(:snoopy) do
      Software.new(project, "snoopy.rb").evaluate do
        name "snoopy"
        default_version "1.0.0"
      end
    end

    let(:preparation) do
      Software.new(project, "preparation.rb").evaluate do
        name "preparation"
        default_version "1.0.0"
      end
    end

    let(:cache_path) { File.join("/var/cache/omnibus/cache/git_cache", install_dir) }

    let(:cache_serial_number) { described_class::SERIAL_NUMBER }

    let(:ipc) do
      project.library.component_added(preparation)
      project.library.component_added(snoopy)
      project.library.component_added(zlib)
      described_class.new(zlib)
    end

    let(:git_flags) { %Q{-c core.autocrlf=false -c core.ignorecase=false --git-dir="#{cache_path}" --work-tree="#{install_dir}"} }

    describe "#cache_path" do
      it "returns the install path appended to the install_cache path" do
        expect(ipc.cache_path).to eq(cache_path)
      end
    end

    describe "#tag" do
      it "returns the correct tag" do
        expect(ipc.tag).to eql("zlib-24a8ec71da04059dcf7ed3c6e8e0fd9d155476abe4b5156d1f13c42e85478c2b-#{cache_serial_number}")
      end

      describe "with no deps" do
        let(:ipc) do
          described_class.new(zlib)
        end

        it "returns the correct tag" do
          expect(ipc.tag).to eql("zlib-ee71fc1a512f03b9dd46c1fd9b5ab71fcc51b638857bf328496a31abb2654c2b-#{cache_serial_number}")
        end
      end
    end

    describe "#create_cache_path" do
      it "runs git init if the cache path does not exist" do
        allow(File).to receive(:directory?)
          .with(ipc.cache_path)
          .and_return(false)
        allow(File).to receive(:directory?)
          .with(File.dirname(ipc.cache_path))
          .and_return(false)
        expect(FileUtils).to receive(:mkdir_p)
          .with(File.dirname(ipc.cache_path))
        expect(ipc).to receive(:shellout!)
          .with("git #{git_flags} init -q")
        expect(ipc).to receive(:shellout!)
          .with("git #{git_flags} config --local user.name \"Omnibus Git Cache\"")
        expect(ipc).to receive(:shellout!)
          .with("git #{git_flags} config --local user.email \"omnibus@localhost\"")
        ipc.create_cache_path
      end

      it "does not run git init if the cache path exists" do
        allow(File).to receive(:directory?)
          .with(ipc.cache_path)
          .and_return(true)
        allow(File).to receive(:directory?)
          .with(File.dirname(ipc.cache_path))
          .and_return(true)
        expect(ipc).to_not receive(:shellout!)
        ipc.create_cache_path
      end
    end

    describe "#incremental" do
      before(:each) do
        allow(ipc).to receive(:shellout!)
        allow(ipc).to receive(:create_cache_path)
      end

      it "creates the cache path" do
        expect(ipc).to receive(:create_cache_path)
        ipc.incremental
      end

      it "adds all the changes to git removing git directories" do
        expect(ipc).to receive(:remove_git_dirs)
        expect(ipc).to receive(:shellout!)
          .with("git #{git_flags} add -A -f")
        ipc.incremental
      end

      it "commits the backup for the software" do
        expect(ipc).to receive(:shellout!)
          .with(%Q{git #{git_flags} commit -q -m "Backup of #{ipc.tag}"})
        ipc.incremental
      end

      it "tags the software backup" do
        expect(ipc).to receive(:shellout!)
          .with(%Q{git #{git_flags} tag -f "#{ipc.tag}"})
        ipc.incremental
      end
    end

    describe "#remove_git_dirs" do
      let(:git_files) { ["git/HEAD", "git/description", "git/hooks", "git/info", "git/objects", "git/refs" ] }
      it "removes bare git directories" do
        allow(Dir).to receive(:glob).and_return(["git/config"])
        git_files.each do |git_file|
          expect(File).to receive(:exist?).with(git_file).and_return(true)
        end
        allow(File).to receive(:dirname).and_return("git")
        expect(FileUtils).to receive(:rm_rf).with("git")

        ipc.remove_git_dirs
      end

      it "does ignores non git directories" do
        allow(Dir).to receive(:glob).and_return(["not_git/config"])
        expect(File).to receive(:exist?).with("not_git/HEAD").and_return(false)
        allow(File).to receive(:dirname).and_return("not_git")
        expect(FileUtils).not_to receive(:rm_rf).with("not_git")

        ipc.remove_git_dirs
      end
    end

    describe "#restore" do
      let(:git_tag_output) { "#{ipc.tag}\n" }

      let(:tag_cmd) do
        cmd_double = double(Mixlib::ShellOut)
        allow(cmd_double).to receive(:stdout).and_return(git_tag_output)
        allow(cmd_double).to receive(:error!).and_return(cmd_double)
        cmd_double
      end

      before(:each) do
        allow(ipc).to receive(:shellout!)
          .with(%Q{git #{git_flags} tag -l "#{ipc.tag}"})
          .and_return(tag_cmd)
        allow(ipc).to receive(:shellout!)
          .with(%Q{git #{git_flags} tag -f restore_here "#{ipc.tag}"})
        allow(ipc).to receive(:create_cache_path)
      end

      it "creates the cache path" do
        expect(ipc).to receive(:create_cache_path)
        ipc.restore
      end

      it "checks for a tag with the software and version, and if it finds it, marks it as restoration point" do
        expect(ipc).to receive(:shellout!)
          .with(%Q{git #{git_flags} tag -l "#{ipc.tag}"})
          .and_return(tag_cmd)
        expect(ipc).to receive(:shellout!)
          .with(%Q{git #{git_flags} tag -f restore_here "#{ipc.tag}"})
        ipc.restore
      end

      describe "if the tag does not exist" do
        let(:git_tag_output) { "\n" }
        let(:restore_tag_cmd) do
          cmd_double = double(Mixlib::ShellOut)
          allow(cmd_double).to receive(:stdout).and_return(git_restore_tag_output)
          allow(cmd_double).to receive(:error!).and_return(cmd_double)
          cmd_double
        end

        describe "if the restore marker tag exists" do
          let(:git_restore_tag_output) { "restore_here\n" }

          it "checks out the last save restoration point and deletes the marker tag" do
            expect(ipc).to receive(:shellout!)
              .with(%Q{git #{git_flags} tag -l "restore_here"})
              .and_return(restore_tag_cmd)
            expect(ipc).to receive(:shellout!)
              .with(%Q{git #{git_flags} tag -l "#{ipc.tag}"})
              .and_return(tag_cmd)
            expect(ipc).to receive(:shellout!)
              .with(%Q{git #{git_flags} checkout -f restore_here})
            expect(ipc).to receive(:shellout!)
              .with(%Q{git #{git_flags} tag -d restore_here})
            ipc.restore
          end
        end

        describe "if the restore marker tag does not exist" do
          let(:git_restore_tag_output) { "\n" }

          it "does nothing" do
            expect(ipc).to receive(:shellout!)
              .with(%Q{git #{git_flags} tag -l "restore_here"})
              .and_return(restore_tag_cmd)
            expect(ipc).to receive(:shellout!)
              .with(%Q{git #{git_flags} tag -l "#{ipc.tag}"})
              .and_return(tag_cmd)
            ipc.restore
          end
        end
      end
    end

    describe "#git_cmd" do
      let(:terrible_install_dir) { %q{/opt/why  please don't do this} }

      before(:each) do
        allow(project).to receive(:install_dir)
          .and_return(terrible_install_dir)
        allow(ipc).to receive(:shellout!)
          .with(%Q{git #{git_flags} version})
          .and_return("git version 2.11.0")
      end

      it "doesn't mangle an #install_dir with spaces" do
        expect(ipc.send(:install_dir)).to eq(terrible_install_dir)
        expect(ipc).to receive(:shellout!)
          .with(%Q{git #{git_flags} version})
        ipc.send(:git_cmd, "version")
      end
    end
  end
end
