require 'support/spec_support'
require 'cheffish/rspec/chef_run_support'

describe Chef::Resource::ChefMirror do
  extend Cheffish::RSpec::ChefRunSupport

  when_the_chef_12_server 'is in multi-org mode' do
    organization 'foo'

    before :each do
      Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, '/organizations/foo').to_s
    end

    describe 'basic download and upload' do
      when_the_repository 'is full of stuff' do
        file 'nodes/x.json', {}
        file 'roles/x.json', {}
        directory 'cookbooks/x' do
          file 'metadata.rb', 'name "x"; version "2.0.0"'
        end

        it "Download grabs defaults" do
          expect_recipe {
            chef_mirror '' do
              action :download
            end
          }.to have_updated('chef_mirror[]', :download)
          expect(File.exist?(path_to('groups/admins.json'))).to be true
          expect(File.exist?(path_to('environments/_default.json'))).to be true
        end

        it "Upload uploads everything" do
          expect_recipe {
            chef_mirror '' do
              action :upload
            end
          }.to have_updated('chef_mirror[]', :upload)
          expect { get('nodes/x') }.not_to raise_error
          expect { get('roles/x') }.not_to raise_error
          expect { get('cookbooks/x/2.0.0') }.not_to raise_error
        end

        it 'chef_mirror with concurrency 0 fails with a reasonable message' do
          expect {
            converge {
              chef_mirror '' do
                concurrency 0
                action :download
              end
            }
          }.to raise_error /chef_mirror.concurrency must be above 0/
        end
      end
    end

    context 'and the Chef server has a node and role in it' do
      node 'x', {}
      role 'x', {}

      when_the_repository 'is empty' do
        it "Download grabs the node and role" do
          expect_recipe {
            chef_mirror '' do
              action :download
            end
          }.to have_updated('chef_mirror[]', :download)
          expect(File.exist?(path_to('nodes/x.json'))).to be true
          expect(File.exist?(path_to('roles/x.json'))).to be true
        end

        it "Upload uploads nothing" do
          expect_recipe {
            chef_mirror '' do
              action :upload
            end
          }.not_to have_updated('chef_mirror[]', :upload)
        end
      end
    end

    context 'and the Chef server has nodes and roles named x' do
      node 'x', {}
      role 'x', {}

      when_the_repository 'has nodes and roles named y' do
        file 'nodes/y.json', {}
        file 'roles/y.json', {}

        it "Download grabs the x's" do
          expect_recipe {
            chef_mirror '' do
              action :download
            end
          }.to have_updated('chef_mirror[]', :download)
          expect(File.exist?(path_to('nodes/x.json'))).to be true
          expect(File.exist?(path_to('roles/x.json'))).to be true
          expect(File.exist?(path_to('nodes/y.json'))).to be true
          expect(File.exist?(path_to('roles/y.json'))).to be true
        end

        it "Upload uploads the y's" do
          expect_recipe {
            chef_mirror '' do
              action :upload
            end
          }.to have_updated('chef_mirror[]', :upload)
          expect { get('nodes/x') }.not_to raise_error
          expect { get('roles/x') }.not_to raise_error
          expect { get('nodes/y') }.not_to raise_error
          expect { get('roles/y') }.not_to raise_error
        end

        it "Download with purge grabs the x's and deletes the y's" do
          expect_recipe {
            chef_mirror '' do
              purge true
              action :download
            end
          }.to have_updated('chef_mirror[]', :download)
          expect(File.exist?(path_to('nodes/x.json'))).to be true
          expect(File.exist?(path_to('roles/x.json'))).to be true
        end

        it "Upload with :purge uploads the y's and deletes the x's" do
          expect_recipe {
            chef_mirror '*/*.json' do
              purge true
              action :upload
            end
          }.to have_updated('chef_mirror[*/*.json]', :upload)
          expect { get('nodes/y') }.not_to raise_error
          expect { get('roles/y') }.not_to raise_error
        end
      end
    end

    describe "chef_repo_path" do
      when_the_repository 'has stuff but no chef_repo_path' do
        file 'repo/nodes/x.json', {}
        file 'repo/roles/x.json', {}
        file 'repo2/nodes/y.json', {}
        file 'repo2/roles/y.json', {}

        before do
          Chef::Config.delete(:chef_repo_path)
          Chef::Config.delete(:node_path)
          Chef::Config.delete(:cookbook_path)
          Chef::Config.delete(:role_path)
        end

        it "Upload with chef_repo_path('repo') uploads everything" do
          repo_path = path_to('repo')
          expect_recipe {
            chef_mirror '' do
              chef_repo_path repo_path
              action :upload
            end
          }.to have_updated('chef_mirror[]', :upload)
          expect { get('nodes/x') }.not_to raise_error
          expect { get('roles/x') }.not_to raise_error
          expect { get('nodes/y') }.to raise_error /404/
          expect { get('roles/y') }.to raise_error /404/
        end

        it "Upload with chef_repo_path(:chef_repo_path) with multiple paths uploads everything" do
          repo_path = path_to('repo')
          repo2_path = path_to('repo2')
          expect_recipe {
            chef_mirror '' do
              chef_repo_path :chef_repo_path => [ repo_path, repo2_path ]
              action :upload
            end
          }.to have_updated('chef_mirror[]', :upload)
          expect { get('nodes/x') }.not_to raise_error
          expect { get('roles/x') }.not_to raise_error
          expect { get('nodes/y') }.not_to raise_error
          expect { get('roles/y') }.not_to raise_error
        end

        it "Upload with chef_repo_path(:node_path, :role_path) uploads everything" do
          repo_path = path_to('repo')
          repo2_path = path_to('repo2')

          expect_recipe {
            chef_mirror '' do
              chef_repo_path :chef_repo_path => '/blahblah',
                             :node_path => "#{repo_path}/nodes",
                             :role_path => "#{repo2_path}/roles"
              action :upload
            end
          }.to have_updated('chef_mirror[]', :upload)
          expect { get('nodes/x') }.not_to raise_error
          expect { get('roles/x') }.to raise_error /404/
          expect { get('nodes/y') }.to raise_error /404/
          expect { get('roles/y') }.not_to raise_error
        end

        it "Upload with chef_repo_path(:chef_repo_path, :role_path) uploads everything" do
          repo_path = path_to('repo')
          repo2_path = path_to('repo2')

          expect_recipe {
            chef_mirror '' do
              chef_repo_path :chef_repo_path => repo_path,
                             :role_path => "#{repo2_path}/roles"
              action :upload
            end
          }.to have_updated('chef_mirror[]', :upload)
          expect { get('nodes/x') }.not_to raise_error
          expect { get('roles/x') }.to raise_error /404/
          expect { get('nodes/y') }.to raise_error /404/
          expect { get('roles/y') }.not_to raise_error
        end

        it "Upload with chef_repo_path(:node_path, :role_path) with multiple paths uploads everything" do
          repo_path = path_to('repo')
          repo2_path = path_to('repo2')

          expect_recipe {
            chef_mirror '' do
              chef_repo_path :chef_repo_path => [ 'foo', 'bar' ],
                             :node_path => [ "#{repo_path}/nodes", "#{repo2_path}/nodes" ],
                             :role_path => [ "#{repo_path}/roles", "#{repo2_path}/roles" ]
              action :upload
            end
          }.to have_updated('chef_mirror[]', :upload)
          expect { get('nodes/x') }.not_to raise_error
          expect { get('roles/x') }.not_to raise_error
          expect { get('nodes/y') }.not_to raise_error
          expect { get('roles/y') }.not_to raise_error
        end
      end
    end

    describe "cookbook upload, chef_repo_path and versioned_cookbooks" do
      when_the_repository 'has cookbooks in non-versioned format' do
        file 'cookbooks/x-1.0.0/metadata.rb', 'name "x-1.0.0"; version "2.0.0"'
        file 'cookbooks/y-1.0.0/metadata.rb', 'name "y-3.0.0"; version "4.0.0"'

        it "chef_mirror :upload uploads everything" do
          expect_recipe {
            chef_mirror '' do
              action :upload
            end
          }.to have_updated('chef_mirror[]', :upload)
          expect { get('cookbooks/x-1.0.0/2.0.0') }.not_to raise_error
          expect { get('cookbooks/y-3.0.0/4.0.0') }.not_to raise_error
        end

        context 'and Chef::Config.versioned_cookbooks is false' do
          before do
            Chef::Config.versioned_cookbooks false
          end
          it "chef_mirror :upload uploads everything" do
            expect_recipe {
              chef_mirror '' do
                action :upload
              end
            }.to have_updated('chef_mirror[]', :upload)
            expect { get('cookbooks/x-1.0.0/2.0.0') }.not_to raise_error
            expect { get('cookbooks/y-3.0.0/4.0.0') }.not_to raise_error
          end
        end

        context 'and Chef::Config.chef_repo_path is not set but versioned_cookbooks is false' do
          before do
            Chef::Config.delete(:chef_repo_path)
            Chef::Config.versioned_cookbooks false
          end

          it "chef_mirror :upload with chef_repo_path and versioned_cookbooks false uploads cookbooks with name including version" do
            repository_dir = @repository_dir
            expect_recipe {
              chef_mirror '' do
                chef_repo_path repository_dir
                versioned_cookbooks false
                action :upload
              end
            }.to have_updated('chef_mirror[]', :upload)
            expect { get('cookbooks/x-1.0.0/2.0.0') }.not_to raise_error
            expect { get('cookbooks/y-3.0.0/4.0.0') }.not_to raise_error
          end
        end
      end

      when_the_repository 'has cookbooks in versioned_cookbook format' do
        file 'cookbooks/x-1.0.0/metadata.rb', 'name "x"; version "1.0.0"'
        file 'cookbooks/x-2.0.0/metadata.rb', 'name "x"; version "2.0.0"'

        context 'and Chef::Config.versioned_cookbooks is true' do
          before do
            Chef::Config.versioned_cookbooks true
          end
          it "chef_mirror :upload uploads everything" do
            expect_recipe {
              chef_mirror '' do
                action :upload
              end
            }.to have_updated('chef_mirror[]', :upload)
            expect { get('cookbooks/x/1.0.0') }.not_to raise_error
            expect { get('cookbooks/x/2.0.0') }.not_to raise_error
          end
        end

        context 'and Chef::Config.chef_repo_path set somewhere else' do
          before do
            Chef::Config.chef_repo_path = '/x/y/z'
          end
          it "chef_mirror :upload with chef_repo_path uploads cookbooks" do
            repository_dir = @repository_dir
            expect_recipe {
              chef_mirror '' do
                chef_repo_path repository_dir
                action :upload
              end
            }.to have_updated('chef_mirror[]', :upload)
            expect { get('cookbooks/x/1.0.0') }.not_to raise_error
            expect { get('cookbooks/x/2.0.0') }.not_to raise_error
          end
        end

        context 'and Chef::Config.chef_repo_path is not set but versioned_cookbooks is false' do
          before do
            Chef::Config.delete(:chef_repo_path)
            Chef::Config.versioned_cookbooks false
          end

          it "chef_mirror :upload with chef_repo_path uploads cookbooks with name split from version" do
            repository_dir = @repository_dir
            expect_recipe {
              chef_mirror '' do
                chef_repo_path repository_dir
                action :upload
              end
            }.to have_updated('chef_mirror[]', :upload)
            expect { get('cookbooks/x/1.0.0') }.not_to raise_error
            expect { get('cookbooks/x/2.0.0') }.not_to raise_error
          end

          it "chef_mirror :upload with chef_repo_path and versioned_cookbooks uploads cookbooks with name split from version" do
            repository_dir = @repository_dir
            expect_recipe {
              chef_mirror '' do
                chef_repo_path repository_dir
                versioned_cookbooks true
                action :upload
              end
            }.to have_updated('chef_mirror[]', :upload)
            expect { get('cookbooks/x/1.0.0') }.not_to raise_error
            expect { get('cookbooks/x/2.0.0') }.not_to raise_error
          end
        end

        context 'and Chef::Config.chef_repo_path is not set but versioned_cookbooks is true' do
          before do
            Chef::Config.delete(:chef_repo_path)
            Chef::Config.versioned_cookbooks true
          end
          it "chef_mirror :upload with chef_repo_path uploads cookbooks with name split from version" do
            repository_dir = @repository_dir
            expect_recipe {
              chef_mirror '' do
                chef_repo_path repository_dir
                action :upload
              end
            }.to have_updated('chef_mirror[]', :upload)
            expect { get('cookbooks/x/1.0.0') }.not_to raise_error
            expect { get('cookbooks/x/2.0.0') }.not_to raise_error
          end
        end
      end
    end

    describe 'cookbook download, chef_repo_path, and versioned_cookbooks' do
      context 'when the Chef server has a cookbook with multiple versions' do
        cookbook 'x', '1.0.0', 'metadata.rb' => 'name "x"; version "1.0.0"'
        cookbook 'x', '2.0.0', 'metadata.rb' => 'name "x"; version "2.0.0"'

        when_the_repository 'is empty' do
          it 'chef_mirror :download downloads the latest version of the cookbook' do
            expect_recipe {
              chef_mirror '' do
                action :download
              end
            }.to have_updated('chef_mirror[]', :download)
            expect(File.read(path_to('cookbooks/x/metadata.rb'))).to eq('name "x"; version "2.0.0"')
          end

          it 'chef_mirror :download with versioned_cookbooks = true downloads all versions of the cookbook' do
            expect_recipe {
              chef_mirror '' do
                versioned_cookbooks true
                action :download
              end
            }.to have_updated('chef_mirror[]', :download)
            expect(File.read(path_to('cookbooks/x-1.0.0/metadata.rb'))).to eq('name "x"; version "1.0.0"')
            expect(File.read(path_to('cookbooks/x-2.0.0/metadata.rb'))).to eq('name "x"; version "2.0.0"')
          end

          context 'and Chef::Config.chef_repo_path is set elsewhere' do
            before do
              Chef::Config.chef_repo_path = '/x/y/z'
            end

            it 'chef_mirror :download with chef_repo_path downloads all versions of the cookbook' do
              repository_dir = @repository_dir
              expect_recipe {
                chef_mirror '' do
                  chef_repo_path repository_dir
                  action :download
                end
              }.to have_updated('chef_mirror[]', :download)
              expect(File.read(path_to('cookbooks/x-1.0.0/metadata.rb'))).to eq('name "x"; version "1.0.0"')
              expect(File.read(path_to('cookbooks/x-2.0.0/metadata.rb'))).to eq('name "x"; version "2.0.0"')
            end

            it 'chef_mirror :download with chef_repo_path and versioned_cookbooks = false downloads the latest version of the cookbook' do
              repository_dir = @repository_dir
              expect_recipe {
                chef_mirror '' do
                  chef_repo_path repository_dir
                  versioned_cookbooks false
                  action :download
                end
              }.to have_updated('chef_mirror[]', :download)
              expect(File.read(path_to('cookbooks/x/metadata.rb'))).to eq('name "x"; version "2.0.0"')
            end
          end

          context 'and Chef::Config.versioned_cookbooks is true' do
            before do
              Chef::Config.versioned_cookbooks = true
            end

            it 'chef_mirror :download downloads all versions of the cookbook' do
              expect_recipe {
                chef_mirror '' do
                  action :download
                end
              }.to have_updated('chef_mirror[]', :download)
              expect(File.read(path_to('cookbooks/x-1.0.0/metadata.rb'))).to eq('name "x"; version "1.0.0"')
              expect(File.read(path_to('cookbooks/x-2.0.0/metadata.rb'))).to eq('name "x"; version "2.0.0"')
            end

            it 'chef_mirror :download with versioned_cookbooks = false downloads the latest version of the cookbook' do
              expect_recipe {
                chef_mirror '' do
                  versioned_cookbooks false
                  action :download
                end
              }.to have_updated('chef_mirror[]', :download)
              expect(File.read(path_to('cookbooks/x/metadata.rb'))).to eq('name "x"; version "2.0.0"')
            end

            context 'and Chef::Config.chef_repo_path is set elsewhere' do
              before do
                Chef::Config.chef_repo_path = '/x/y/z'
              end

              it 'chef_mirror :download with chef_repo_path downloads all versions of the cookbook' do
                repository_dir = @repository_dir
                expect_recipe {
                  chef_mirror '' do
                    chef_repo_path repository_dir
                    action :download
                  end
                }.to have_updated('chef_mirror[]', :download)
                expect(File.read(path_to('cookbooks/x-1.0.0/metadata.rb'))).to eq('name "x"; version "1.0.0"')
                expect(File.read(path_to('cookbooks/x-2.0.0/metadata.rb'))).to eq('name "x"; version "2.0.0"')
              end

              it 'chef_mirror :download with chef_repo_path and versioned_cookbooks = false downloads the latest version of the cookbook' do
                repository_dir = @repository_dir
                expect_recipe {
                  chef_mirror '' do
                    chef_repo_path repository_dir
                    versioned_cookbooks false
                    action :download
                  end
                }.to have_updated('chef_mirror[]', :download)
                expect(File.read(path_to('cookbooks/x/metadata.rb'))).to eq('name "x"; version "2.0.0"')
              end
            end
          end
        end
      end
    end
  end
end
