#
# Author:: John Keiser (<jkeiser@chef.io>)
# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require "knife_spec_helper"
require "support/shared/integration/integration_helper"
require "chef/knife/download"
require "chef/knife/diff"

describe "knife download", :workstation do
  include IntegrationSupport
  include KnifeSupport

  context "without versioned cookbooks" do
    when_the_chef_server "has one of each thing" do

      before do
        client "x", {}
        cookbook "x", "1.0.0"
        data_bag "x", { "y" => {} }
        environment "x", {}
        node "x", {}
        role "x", {}
        user "x", {}
      end

      when_the_repository "has only top-level directories" do
        before do
          directory "clients"
          directory "cookbooks"
          directory "data_bags"
          directory "environments"
          directory "nodes"
          directory "roles"
          directory "users"
        end

        it "knife download downloads everything" do
          knife("download /").should_succeed <<~EOM
            Created /clients/chef-validator.json
            Created /clients/chef-webui.json
            Created /clients/x.json
            Created /cookbooks/x
            Created /cookbooks/x/metadata.rb
            Created /data_bags/x
            Created /data_bags/x/y.json
            Created /environments/_default.json
            Created /environments/x.json
            Created /nodes/x.json
            Created /roles/x.json
            Created /users/admin.json
            Created /users/x.json
          EOM
          knife("diff --name-status /").should_succeed ""
        end
      end

      when_the_repository "has an identical copy of each thing" do
        before do
          file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
          file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
          file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
          file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
          file "data_bags/x/y.json", {}
          file "environments/_default.json", { "description" => "The default Chef environment" }
          file "environments/x.json", {}
          file "nodes/x.json", { "normal" => { "tags" => [] } }
          file "roles/x.json", {}
          file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
          file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
        end

        it "knife download makes no changes" do
          knife("download /").should_succeed ""
          knife("diff --name-status /").should_succeed ""
        end

        it "knife download --purge makes no changes" do
          knife("download --purge /").should_succeed ""
          knife("diff --name-status /").should_succeed ""
        end

        context "except the role file" do
          before do
            file "roles/x.json", <<~EOM
              {
                "chef_type": "role",
                "default_attributes": {
                },
                "description": "blarghle",
                "env_run_lists": {
                },
                "json_class": "Chef::Role",
                "name": "x",
                "override_attributes": {
                },
                "run_list": [

                ]
              }
            EOM
          end

          it "knife download changes the role" do
            knife("download /").should_succeed "Updated /roles/x.json\n"
            knife("diff --name-status /").should_succeed ""
          end

          it "knife download --no-diff does not change the role" do
            knife("download --no-diff /").should_succeed ""
            knife("diff --name-status /").should_succeed "M\t/roles/x.json\n"
          end
        end

        context "except the role file is textually different, but not ACTUALLY different" do
          before do
            file "roles/x.json", <<~EOM
              {
                "chef_type": "role",
                "default_attributes": {
                },
                "env_run_lists": {
                },
                "json_class": "Chef::Role",
                "name": "x",
                "description": "",
                "override_attributes": {
                },
                "run_list": [

                ]
              }
            EOM
          end

          it "knife download / does not change anything" do
            knife("download /").should_succeed ""
            knife("diff --name-status /").should_succeed ""
          end
        end

        context "as well as one extra copy of each thing" do
          before do
            file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
            file "cookbooks/x/blah.rb", ""
            file "cookbooks/y/metadata.rb", cb_metadata("x", "1.0.0")
            file "data_bags/x/z.json", {}
            file "data_bags/y/zz.json", {}
            file "environments/y.json", {}
            file "nodes/y.json", {}
            file "roles/y.json", {}
            file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
          end

          it "knife download does nothing" do
            knife("download /").should_succeed ""
            knife("diff --name-status /").should_succeed <<~EOM
              A\t/clients/y.json
              A\t/cookbooks/x/blah.rb
              A\t/cookbooks/y
              A\t/data_bags/x/z.json
              A\t/data_bags/y
              A\t/environments/y.json
              A\t/nodes/y.json
              A\t/roles/y.json
              A\t/users/y.json
            EOM
          end

          it "knife download --purge deletes the extra files" do
            knife("download --purge /").should_succeed <<~EOM
              Deleted extra entry /clients/y.json (purge is on)
              Deleted extra entry /cookbooks/x/blah.rb (purge is on)
              Deleted extra entry /cookbooks/y (purge is on)
              Deleted extra entry /data_bags/x/z.json (purge is on)
              Deleted extra entry /data_bags/y (purge is on)
              Deleted extra entry /environments/y.json (purge is on)
              Deleted extra entry /nodes/y.json (purge is on)
              Deleted extra entry /roles/y.json (purge is on)
              Deleted extra entry /users/y.json (purge is on)
            EOM
            knife("diff --name-status /").should_succeed ""
          end
        end
      end

      when_the_repository "is empty" do
        it "knife download creates the extra files" do
          knife("download /").should_succeed <<~EOM
            Created /clients
            Created /clients/chef-validator.json
            Created /clients/chef-webui.json
            Created /clients/x.json
            Created /cookbooks
            Created /cookbooks/x
            Created /cookbooks/x/metadata.rb
            Created /data_bags
            Created /data_bags/x
            Created /data_bags/x/y.json
            Created /environments
            Created /environments/_default.json
            Created /environments/x.json
            Created /nodes
            Created /nodes/x.json
            Created /roles
            Created /roles/x.json
            Created /users
            Created /users/admin.json
            Created /users/x.json
          EOM
          knife("diff --name-status /").should_succeed ""
        end

        it "knife download --no-diff creates the extra files" do
          knife("download --no-diff /").should_succeed <<~EOM
            Created /clients
            Created /clients/chef-validator.json
            Created /clients/chef-webui.json
            Created /clients/x.json
            Created /cookbooks
            Created /cookbooks/x
            Created /cookbooks/x/metadata.rb
            Created /data_bags
            Created /data_bags/x
            Created /data_bags/x/y.json
            Created /environments
            Created /environments/_default.json
            Created /environments/x.json
            Created /nodes
            Created /nodes/x.json
            Created /roles
            Created /roles/x.json
            Created /users
            Created /users/admin.json
            Created /users/x.json
          EOM
          knife("diff --name-status /").should_succeed ""
        end

        context "when current directory is top level" do
          before do
            cwd "."
          end

          it "knife download with no parameters reports an error" do
            knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/
          end
        end
      end
    end

    # Test download of an item when the other end doesn't even have the container
    when_the_repository "is empty" do
      when_the_chef_server "has two data bag items" do
        before do
          data_bag "x", { "y" => {}, "z" => {} }
        end

        it "knife download of one data bag item itself succeeds" do
          knife("download /data_bags/x/y.json").should_succeed <<~EOM
            Created /data_bags
            Created /data_bags/x
            Created /data_bags/x/y.json
          EOM
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            D\t/data_bags/x/z.json
          EOM
        end

        it "knife download /data_bags/x /data_bags/x/y.json downloads x once" do
          knife("download /data_bags/x /data_bags/x/y.json").should_succeed <<~EOM
            Created /data_bags
            Created /data_bags/x
            Created /data_bags/x/y.json
            Created /data_bags/x/z.json
          EOM
        end
      end
    end

    when_the_repository "has three data bag items" do
      before do
        file "data_bags/x/deleted.json", <<~EOM
          {
            "id": "deleted"
          }
        EOM
        file "data_bags/x/modified.json", <<~EOM
          {
            "id": "modified"
          }
        EOM
        file "data_bags/x/unmodified.json", <<~EOM
          {
            "id": "unmodified"
          }
        EOM
      end

      when_the_chef_server "has a modified, unmodified, added and deleted data bag item" do
        before do
          data_bag "x", {
            "added" => {},
            "modified" => { "foo" => "bar" },
            "unmodified" => {},
          }
        end

        it "knife download of the modified file succeeds" do
          knife("download /data_bags/x/modified.json").should_succeed <<~EOM
            Updated /data_bags/x/modified.json
          EOM
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            D\t/data_bags/x/added.json
            A\t/data_bags/x/deleted.json
          EOM
        end
        it "knife download of the unmodified file does nothing" do
          knife("download /data_bags/x/unmodified.json").should_succeed ""
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            D\t/data_bags/x/added.json
            M\t/data_bags/x/modified.json
            A\t/data_bags/x/deleted.json
          EOM
        end
        it "knife download of the added file succeeds" do
          knife("download /data_bags/x/added.json").should_succeed <<~EOM
            Created /data_bags/x/added.json
          EOM
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            M\t/data_bags/x/modified.json
            A\t/data_bags/x/deleted.json
          EOM
        end
        it "knife download of the deleted file does nothing" do
          knife("download /data_bags/x/deleted.json").should_succeed ""
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            D\t/data_bags/x/added.json
            M\t/data_bags/x/modified.json
            A\t/data_bags/x/deleted.json
          EOM
        end
        it "knife download --purge of the deleted file deletes it" do
          knife("download --purge /data_bags/x/deleted.json").should_succeed <<~EOM
            Deleted extra entry /data_bags/x/deleted.json (purge is on)
          EOM
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            D\t/data_bags/x/added.json
            M\t/data_bags/x/modified.json
          EOM
        end
        it "knife download of the entire data bag downloads everything" do
          knife("download /data_bags/x").should_succeed <<~EOM
            Created /data_bags/x/added.json
            Updated /data_bags/x/modified.json
          EOM
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            A\t/data_bags/x/deleted.json
          EOM
        end
        it "knife download --purge of the entire data bag downloads everything" do
          knife("download --purge /data_bags/x").should_succeed <<~EOM
            Created /data_bags/x/added.json
            Updated /data_bags/x/modified.json
            Deleted extra entry /data_bags/x/deleted.json (purge is on)
          EOM
          knife("diff --name-status /data_bags").should_succeed ""
        end
        context "when cwd is the /data_bags directory" do
          before do
            cwd "data_bags"
          end
          it "knife download fails" do
            knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/
          end
          it "knife download --purge . downloads everything" do
            knife("download --purge .").should_succeed <<~EOM
              Created x/added.json
              Updated x/modified.json
              Deleted extra entry x/deleted.json (purge is on)
            EOM
            knife("diff --name-status /data_bags").should_succeed ""
          end
          it "knife download --purge * downloads everything" do
            knife("download --purge *").should_succeed <<~EOM
              Created x/added.json
              Updated x/modified.json
              Deleted extra entry x/deleted.json (purge is on)
            EOM
            knife("diff --name-status /data_bags").should_succeed ""
          end
        end
      end
    end

    when_the_repository "has a cookbook" do
      before do
        file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
        file "cookbooks/x/z.rb", ""
      end

      when_the_chef_server "has a modified, added and deleted file for the cookbook" do
        before do
          cookbook "x", "1.0.0", { "metadata.rb" => cb_metadata("x", "1.0.0", "#extra content"), "y.rb" => "hi" }
        end

        it "knife download of a modified file succeeds" do
          knife("download /cookbooks/x/metadata.rb").should_succeed "Updated /cookbooks/x/metadata.rb\n"
          knife("diff --name-status /cookbooks").should_succeed <<~EOM
            D\t/cookbooks/x/y.rb
            A\t/cookbooks/x/z.rb
          EOM
        end
        it "knife download of a deleted file does nothing" do
          knife("download /cookbooks/x/z.rb").should_succeed ""
          knife("diff --name-status /cookbooks").should_succeed <<~EOM
            M\t/cookbooks/x/metadata.rb
            D\t/cookbooks/x/y.rb
            A\t/cookbooks/x/z.rb
          EOM
        end
        it "knife download --purge of a deleted file succeeds" do
          knife("download --purge /cookbooks/x/z.rb").should_succeed "Deleted extra entry /cookbooks/x/z.rb (purge is on)\n"
          knife("diff --name-status /cookbooks").should_succeed <<~EOM
            M\t/cookbooks/x/metadata.rb
            D\t/cookbooks/x/y.rb
          EOM
        end
        it "knife download of an added file succeeds" do
          knife("download /cookbooks/x/y.rb").should_succeed "Created /cookbooks/x/y.rb\n"
          knife("diff --name-status /cookbooks").should_succeed <<~EOM
            M\t/cookbooks/x/metadata.rb
            A\t/cookbooks/x/z.rb
          EOM
        end
        it "knife download of the cookbook itself succeeds" do
          knife("download /cookbooks/x").should_succeed <<~EOM
            Updated /cookbooks/x/metadata.rb
            Created /cookbooks/x/y.rb
          EOM
          knife("diff --name-status /cookbooks").should_succeed <<~EOM
            A\t/cookbooks/x/z.rb
          EOM
        end
        it "knife download --purge of the cookbook itself succeeds" do
          knife("download --purge /cookbooks/x").should_succeed <<~EOM
            Updated /cookbooks/x/metadata.rb
            Created /cookbooks/x/y.rb
            Deleted extra entry /cookbooks/x/z.rb (purge is on)
          EOM
          knife("diff --name-status /cookbooks").should_succeed ""
        end
      end
    end

    when_the_repository "has a cookbook" do
      before do
        file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
        file "cookbooks/x/onlyin1.0.0.rb", "old_text"
      end

      when_the_chef_server "has a later version for the cookbook" do
        before do
          cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
          cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
        end

        it "knife download /cookbooks/x downloads the latest version" do
          knife("download --purge /cookbooks/x").should_succeed <<~EOM
            Updated /cookbooks/x/metadata.rb
            Created /cookbooks/x/onlyin1.0.1.rb
            Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (purge is on)
          EOM
          knife("diff --name-status /cookbooks").should_succeed ""
        end
      end

      when_the_chef_server "has an earlier version for the cookbook" do
        before do
          cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
          cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
        end

        it "knife download /cookbooks/x downloads the updated file" do
          knife("download --purge /cookbooks/x").should_succeed <<~EOM
            Updated /cookbooks/x/onlyin1.0.0.rb
          EOM
          knife("diff --name-status /cookbooks").should_succeed ""
        end
      end

      when_the_chef_server "has a later version for the cookbook, and no current version" do
        before do
          cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
        end

        it "knife download /cookbooks/x downloads the latest version" do
          knife("download --purge /cookbooks/x").should_succeed <<~EOM
            Updated /cookbooks/x/metadata.rb
            Created /cookbooks/x/onlyin1.0.1.rb
            Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (purge is on)
          EOM
          knife("diff --name-status /cookbooks").should_succeed ""
        end
      end

      when_the_chef_server "has an earlier version for the cookbook, and no current version" do
        before do
          cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
        end

        it "knife download /cookbooks/x downloads the old version" do
          knife("download --purge /cookbooks/x").should_succeed <<~EOM
            Updated /cookbooks/x/metadata.rb
            Created /cookbooks/x/onlyin0.9.9.rb
            Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (purge is on)
          EOM
          knife("diff --name-status /cookbooks").should_succeed ""
        end
      end
    end

    when_the_chef_server "has a role" do
      before do
        role "x", {}
      end
      when_the_repository "has the role in ruby" do
        before do
          file "roles/x.rb", <<~EOM
            name "x"
            description "x"
          EOM
        end

        it "knife download refuses to change the role" do
          knife("download /roles/x.json").should_succeed "", stderr: "WARNING: /roles/x.rb cannot be updated (can't safely update ruby files).\n"
          knife("diff --name-status /roles/x.json").should_succeed "M\t/roles/x.rb\n"
        end
      end
    end

    when_the_chef_server "has an environment" do
      before do
        environment "x", {}
      end
      when_the_repository "has an environment with bad JSON" do
        before do
          file "environments/x.json", "{"
        end
        it "knife download succeeds" do
          warning = <<~EOH
            WARN: Parse error reading #{path_to("environments/x.json")} as JSON: parse error: premature EOF
                                                   {
                                 (right here) ------^

          EOH
          knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n", stderr: warning
          knife("diff --name-status /environments/x.json").should_succeed ""
        end
      end

      when_the_repository "has the same environment with the wrong name in the file" do
        before do
          file "environments/x.json", { "name" => "y" }
        end
        it "knife download succeeds" do
          knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n"
          knife("diff --name-status /environments/x.json").should_succeed ""
        end
      end

      when_the_repository "has the same environment with no name in the file" do
        before do
          file "environments/x.json", { "description" => "hi" }
        end
        it "knife download succeeds" do
          knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n"
          knife("diff --name-status /environments/x.json").should_succeed ""
        end
      end
    end
  end # without versioned cookbooks

  context "with versioned cookbooks" do
    before { Chef::Config[:versioned_cookbooks] = true }

    when_the_chef_server "has one of each thing" do
      before do
        client "x", {}
        cookbook "x", "1.0.0"
        data_bag "x", { "y" => {} }
        environment "x", {}
        node "x", {}
        role "x", {}
        user "x", {}
      end

      when_the_repository "has only top-level directories" do
        before do
          directory "clients"
          directory "cookbooks"
          directory "data_bags"
          directory "environments"
          directory "nodes"
          directory "roles"
          directory "users"
        end

        it "knife download downloads everything" do
          knife("download /").should_succeed <<~EOM
            Created /clients/chef-validator.json
            Created /clients/chef-webui.json
            Created /clients/x.json
            Created /cookbooks/x-1.0.0
            Created /cookbooks/x-1.0.0/metadata.rb
            Created /data_bags/x
            Created /data_bags/x/y.json
            Created /environments/_default.json
            Created /environments/x.json
            Created /nodes/x.json
            Created /roles/x.json
            Created /users/admin.json
            Created /users/x.json
          EOM
          knife("diff --name-status /").should_succeed ""
        end
      end

      when_the_repository "has an identical copy of each thing" do
        before do
          file "clients/chef-validator.json", { "validator" => true, "public_key" => ChefZero::PUBLIC_KEY }
          file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
          file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
          file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
          file "data_bags/x/y.json", {}
          file "environments/_default.json", { "description" => "The default Chef environment" }
          file "environments/x.json", {}
          file "nodes/x.json", { "normal" => { "tags" => [] } }
          file "roles/x.json", {}
          file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY }
          file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
        end

        it "knife download makes no changes" do
          knife("download /").should_succeed ""
          knife("diff --name-status /").should_succeed ""
        end

        it "knife download --purge makes no changes" do
          knife("download --purge /").should_succeed ""
          knife("diff --name-status /").should_succeed ""
        end

        context "except the role file" do
          before do
            file "roles/x.json", { "description" => "blarghle" }
          end

          it "knife download changes the role" do
            knife("download /").should_succeed "Updated /roles/x.json\n"
            knife("diff --name-status /").should_succeed ""
          end
        end

        context "except the role file is textually different, but not ACTUALLY different" do
          before do
            file "roles/x.json", <<~EOM
              {
                "chef_type": "role" ,
                "default_attributes": {
                },
                "env_run_lists": {
                },
                "json_class": "Chef::Role",
                "name": "x",
                "description": "",
                "override_attributes": {
                },
                "run_list": [

                ]
              }
            EOM
          end

          it "knife download / does not change anything" do
            knife("download /").should_succeed ""
            knife("diff --name-status /").should_succeed ""
          end
        end

        context "as well as one extra copy of each thing" do
          before do
            file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
            file "cookbooks/x-1.0.0/blah.rb", ""
            file "cookbooks/x-2.0.0/metadata.rb", 'version "2.0.0"'
            file "cookbooks/y-1.0.0/metadata.rb", 'version "1.0.0"'
            file "data_bags/x/z.json", {}
            file "data_bags/y/zz.json", {}
            file "environments/y.json", {}
            file "nodes/y.json", {}
            file "roles/y.json", {}
            file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY }
          end

          it "knife download does nothing" do
            knife("download /").should_succeed ""
            knife("diff --name-status /").should_succeed <<~EOM
              A\t/clients/y.json
              A\t/cookbooks/x-1.0.0/blah.rb
              A\t/cookbooks/x-2.0.0
              A\t/cookbooks/y-1.0.0
              A\t/data_bags/x/z.json
              A\t/data_bags/y
              A\t/environments/y.json
              A\t/nodes/y.json
              A\t/roles/y.json
              A\t/users/y.json
            EOM
          end

          it "knife download --purge deletes the extra files" do
            knife("download --purge /").should_succeed <<~EOM
              Deleted extra entry /clients/y.json (purge is on)
              Deleted extra entry /cookbooks/x-1.0.0/blah.rb (purge is on)
              Deleted extra entry /cookbooks/x-2.0.0 (purge is on)
              Deleted extra entry /cookbooks/y-1.0.0 (purge is on)
              Deleted extra entry /data_bags/x/z.json (purge is on)
              Deleted extra entry /data_bags/y (purge is on)
              Deleted extra entry /environments/y.json (purge is on)
              Deleted extra entry /nodes/y.json (purge is on)
              Deleted extra entry /roles/y.json (purge is on)
              Deleted extra entry /users/y.json (purge is on)
            EOM
            knife("diff --name-status /").should_succeed ""
          end
        end
      end

      when_the_repository "is empty" do
        it "knife download creates the extra files" do
          knife("download /").should_succeed <<~EOM
            Created /clients
            Created /clients/chef-validator.json
            Created /clients/chef-webui.json
            Created /clients/x.json
            Created /cookbooks
            Created /cookbooks/x-1.0.0
            Created /cookbooks/x-1.0.0/metadata.rb
            Created /data_bags
            Created /data_bags/x
            Created /data_bags/x/y.json
            Created /environments
            Created /environments/_default.json
            Created /environments/x.json
            Created /nodes
            Created /nodes/x.json
            Created /roles
            Created /roles/x.json
            Created /users
            Created /users/admin.json
            Created /users/x.json
          EOM
          knife("diff --name-status /").should_succeed ""
        end

        context "when current directory is top level" do
          before do
            cwd "."
          end
          it "knife download with no parameters reports an error" do
            knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/
          end
        end
      end
    end

    # Test download of an item when the other end doesn't even have the container
    when_the_repository "is empty" do
      when_the_chef_server "has two data bag items" do
        before do
          data_bag "x", { "y" => {}, "z" => {} }
        end

        it "knife download of one data bag item itself succeeds" do
          knife("download /data_bags/x/y.json").should_succeed <<~EOM
            Created /data_bags
            Created /data_bags/x
            Created /data_bags/x/y.json
          EOM
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            D\t/data_bags/x/z.json
          EOM
        end
      end
    end

    when_the_repository "has three data bag items" do
      before do
        file "data_bags/x/deleted.json", <<~EOM
          {
            "id": "deleted"
          }
        EOM
        file "data_bags/x/modified.json", <<~EOM
          {
            "id": "modified"
          }
        EOM
        file "data_bags/x/unmodified.json", <<~EOM
          {
            "id": "unmodified"
          }
        EOM
      end

      when_the_chef_server "has a modified, unmodified, added and deleted data bag item" do
        before do
          data_bag "x", {
            "added" => {},
            "modified" => { "foo" => "bar" },
            "unmodified" => {},
          }
        end

        it "knife download of the modified file succeeds" do
          knife("download /data_bags/x/modified.json").should_succeed <<~EOM
            Updated /data_bags/x/modified.json
          EOM
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            D\t/data_bags/x/added.json
            A\t/data_bags/x/deleted.json
          EOM
        end
        it "knife download of the unmodified file does nothing" do
          knife("download /data_bags/x/unmodified.json").should_succeed ""
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            D\t/data_bags/x/added.json
            M\t/data_bags/x/modified.json
            A\t/data_bags/x/deleted.json
          EOM
        end
        it "knife download of the added file succeeds" do
          knife("download /data_bags/x/added.json").should_succeed <<~EOM
            Created /data_bags/x/added.json
          EOM
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            M\t/data_bags/x/modified.json
            A\t/data_bags/x/deleted.json
          EOM
        end
        it "knife download of the deleted file does nothing" do
          knife("download /data_bags/x/deleted.json").should_succeed ""
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            D\t/data_bags/x/added.json
            M\t/data_bags/x/modified.json
            A\t/data_bags/x/deleted.json
          EOM
        end
        it "knife download --purge of the deleted file deletes it" do
          knife("download --purge /data_bags/x/deleted.json").should_succeed <<~EOM
            Deleted extra entry /data_bags/x/deleted.json (purge is on)
          EOM
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            D\t/data_bags/x/added.json
            M\t/data_bags/x/modified.json
          EOM
        end
        it "knife download of the entire data bag downloads everything" do
          knife("download /data_bags/x").should_succeed <<~EOM
            Created /data_bags/x/added.json
            Updated /data_bags/x/modified.json
          EOM
          knife("diff --name-status /data_bags").should_succeed <<~EOM
            A\t/data_bags/x/deleted.json
          EOM
        end
        it "knife download --purge of the entire data bag downloads everything" do
          knife("download --purge /data_bags/x").should_succeed <<~EOM
            Created /data_bags/x/added.json
            Updated /data_bags/x/modified.json
            Deleted extra entry /data_bags/x/deleted.json (purge is on)
          EOM
          knife("diff --name-status /data_bags").should_succeed ""
        end
        context "when cwd is the /data_bags directory" do
          before do
            cwd "data_bags"
          end
          it "knife download fails" do
            knife("download").should_fail "FATAL: You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"\n", stdout: /USAGE/
          end
          it "knife download --purge . downloads everything" do
            knife("download --purge .").should_succeed <<~EOM
              Created x/added.json
              Updated x/modified.json
              Deleted extra entry x/deleted.json (purge is on)
            EOM
            knife("diff --name-status /data_bags").should_succeed ""
          end
          it "knife download --purge * downloads everything" do
            knife("download --purge *").should_succeed <<~EOM
              Created x/added.json
              Updated x/modified.json
              Deleted extra entry x/deleted.json (purge is on)
            EOM
            knife("diff --name-status /data_bags").should_succeed ""
          end
        end
      end
    end

    when_the_repository "has a cookbook" do
      before do
        file "cookbooks/x-1.0.0/metadata.rb", 'name "x"; version "1.0.0"#unmodified'
        file "cookbooks/x-1.0.0/z.rb", ""
      end

      when_the_chef_server "has a modified, added and deleted file for the cookbook" do
        before do
          cookbook "x", "1.0.0", { "y.rb" => "hi" }
        end

        it "knife download of a modified file succeeds" do
          knife("download /cookbooks/x-1.0.0/metadata.rb").should_succeed "Updated /cookbooks/x-1.0.0/metadata.rb\n"
          knife("diff --name-status /cookbooks").should_succeed <<~EOM
            D\t/cookbooks/x-1.0.0/y.rb
            A\t/cookbooks/x-1.0.0/z.rb
          EOM
        end
        it "knife download of a deleted file does nothing" do
          knife("download /cookbooks/x-1.0.0/z.rb").should_succeed ""
          knife("diff --name-status /cookbooks").should_succeed <<~EOM
            M\t/cookbooks/x-1.0.0/metadata.rb
            D\t/cookbooks/x-1.0.0/y.rb
            A\t/cookbooks/x-1.0.0/z.rb
          EOM
        end
        it "knife download --purge of a deleted file succeeds" do
          knife("download --purge /cookbooks/x-1.0.0/z.rb").should_succeed "Deleted extra entry /cookbooks/x-1.0.0/z.rb (purge is on)\n"
          knife("diff --name-status /cookbooks").should_succeed <<~EOM
            M\t/cookbooks/x-1.0.0/metadata.rb
            D\t/cookbooks/x-1.0.0/y.rb
          EOM
        end
        it "knife download of an added file succeeds" do
          knife("download /cookbooks/x-1.0.0/y.rb").should_succeed "Created /cookbooks/x-1.0.0/y.rb\n"
          knife("diff --name-status /cookbooks").should_succeed <<~EOM
            M\t/cookbooks/x-1.0.0/metadata.rb
            A\t/cookbooks/x-1.0.0/z.rb
          EOM
        end
        it "knife download of the cookbook itself succeeds" do
          knife("download /cookbooks/x-1.0.0").should_succeed <<~EOM
            Updated /cookbooks/x-1.0.0/metadata.rb
            Created /cookbooks/x-1.0.0/y.rb
          EOM
          knife("diff --name-status /cookbooks").should_succeed <<~EOM
            A\t/cookbooks/x-1.0.0/z.rb
          EOM
        end
        it "knife download --purge of the cookbook itself succeeds" do
          knife("download --purge /cookbooks/x-1.0.0").should_succeed <<~EOM
            Updated /cookbooks/x-1.0.0/metadata.rb
            Created /cookbooks/x-1.0.0/y.rb
            Deleted extra entry /cookbooks/x-1.0.0/z.rb (purge is on)
          EOM
          knife("diff --name-status /cookbooks").should_succeed ""
        end
      end
    end

    when_the_repository "has a cookbook" do
      before do
        file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0")
        file "cookbooks/x-1.0.0/onlyin1.0.0.rb", "old_text"
      end

      when_the_chef_server "has a later version for the cookbook" do
        before do
          cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
          cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
        end

        it "knife download /cookbooks/x downloads the latest version" do
          knife("download --purge /cookbooks").should_succeed <<~EOM
            Updated /cookbooks/x-1.0.0/onlyin1.0.0.rb
            Created /cookbooks/x-1.0.1
            Created /cookbooks/x-1.0.1/metadata.rb
            Created /cookbooks/x-1.0.1/onlyin1.0.1.rb
          EOM
          knife("diff --name-status /cookbooks").should_succeed ""
        end
      end

      when_the_chef_server "has an earlier version for the cookbook" do
        before do
          cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" }
          cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
        end

        it "knife download /cookbooks downloads the updated file" do
          knife("download --purge /cookbooks").should_succeed <<~EOM
            Created /cookbooks/x-0.9.9
            Created /cookbooks/x-0.9.9/metadata.rb
            Created /cookbooks/x-0.9.9/onlyin0.9.9.rb
            Updated /cookbooks/x-1.0.0/onlyin1.0.0.rb
          EOM
          knife("diff --name-status /cookbooks").should_succeed ""
        end
      end

      when_the_chef_server "has a later version for the cookbook, and no current version" do
        before do
          cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" }
        end

        it "knife download /cookbooks/x downloads the latest version" do
          knife("download --purge /cookbooks").should_succeed <<~EOM
            Created /cookbooks/x-1.0.1
            Created /cookbooks/x-1.0.1/metadata.rb
            Created /cookbooks/x-1.0.1/onlyin1.0.1.rb
            Deleted extra entry /cookbooks/x-1.0.0 (purge is on)
          EOM
          knife("diff --name-status /cookbooks").should_succeed ""
        end
      end

      when_the_chef_server "has an earlier version for the cookbook, and no current version" do
        before do
          cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" }
        end

        it "knife download --purge /cookbooks downloads the old version and deletes the new version" do
          knife("download --purge /cookbooks").should_succeed <<~EOM
            Created /cookbooks/x-0.9.9
            Created /cookbooks/x-0.9.9/metadata.rb
            Created /cookbooks/x-0.9.9/onlyin0.9.9.rb
            Deleted extra entry /cookbooks/x-1.0.0 (purge is on)
          EOM
          knife("diff --name-status /cookbooks").should_succeed ""
        end
      end
    end

    when_the_chef_server "has an environment" do
      before do
        environment "x", {}
      end

      when_the_repository "has the same environment with the wrong name in the file" do
        before do
          file "environments/x.json", { "name" => "y" }
        end

        it "knife download succeeds" do
          knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n"
          knife("diff --name-status /environments/x.json").should_succeed ""
        end
      end

      when_the_repository "has the same environment with no name in the file" do
        before do
          file "environments/x.json", { "description" => "hi" }
        end

        it "knife download succeeds" do
          knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n"
          knife("diff --name-status /environments/x.json").should_succeed ""
        end
      end
    end
  end # with versioned cookbooks

  when_the_chef_server "has a cookbook" do
    before do
      cookbook "x", "1.0.0"
    end

    when_the_repository "is empty" do
      it "knife download /cookbooks/x signs all requests" do

        # Check that BasicClient.request() always gets called with X-OPS-USERID
        original_new = Chef::HTTP::BasicClient.method(:new)
        expect(Chef::HTTP::BasicClient).to receive(:new) { |args|
          new_result = original_new.call(*args)
          original_request = new_result.method(:request)
          expect(new_result).to receive(:request) { |method, url, body, headers, &response_handler|
            expect(headers["X-OPS-USERID"]).not_to be_nil
            original_request.call(method, url, body, headers, &response_handler)
          }.at_least(:once)
          new_result
        }.at_least(:once)

        knife("download /cookbooks/x").should_succeed <<~EOM
          Created /cookbooks
          Created /cookbooks/x
          Created /cookbooks/x/metadata.rb
        EOM
      end
    end
  end

  when_the_chef_server "is in Enterprise mode", osc_compat: false, single_org: false do
    before do
      user "foo", {}
      user "bar", {}
      user "foobar", {}
      organization "foo", { "full_name" => "Something" }
    end

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

    when_the_repository "has all the default stuff" do
      before do
        knife("download /").should_succeed <<~EOM
          Created /acls
          Created /acls/clients
          Created /acls/clients/foo-validator.json
          Created /acls/containers
          Created /acls/containers/clients.json
          Created /acls/containers/containers.json
          Created /acls/containers/cookbook_artifacts.json
          Created /acls/containers/cookbooks.json
          Created /acls/containers/data.json
          Created /acls/containers/environments.json
          Created /acls/containers/groups.json
          Created /acls/containers/nodes.json
          Created /acls/containers/policies.json
          Created /acls/containers/policy_groups.json
          Created /acls/containers/roles.json
          Created /acls/containers/sandboxes.json
          Created /acls/cookbook_artifacts
          Created /acls/cookbooks
          Created /acls/data_bags
          Created /acls/environments
          Created /acls/environments/_default.json
          Created /acls/groups
          Created /acls/groups/admins.json
          Created /acls/groups/billing-admins.json
          Created /acls/groups/clients.json
          Created /acls/groups/users.json
          Created /acls/nodes
          Created /acls/policies
          Created /acls/policy_groups
          Created /acls/roles
          Created /acls/organization.json
          Created /clients
          Created /clients/foo-validator.json
          Created /containers
          Created /containers/clients.json
          Created /containers/containers.json
          Created /containers/cookbook_artifacts.json
          Created /containers/cookbooks.json
          Created /containers/data.json
          Created /containers/environments.json
          Created /containers/groups.json
          Created /containers/nodes.json
          Created /containers/policies.json
          Created /containers/policy_groups.json
          Created /containers/roles.json
          Created /containers/sandboxes.json
          Created /cookbook_artifacts
          Created /cookbooks
          Created /data_bags
          Created /environments
          Created /environments/_default.json
          Created /groups
          Created /groups/admins.json
          Created /groups/billing-admins.json
          Created /groups/clients.json
          Created /groups/users.json
          Created /invitations.json
          Created /members.json
          Created /nodes
          Created /org.json
          Created /policies
          Created /policy_groups
          Created /roles
        EOM
      end

      context "and the server has one of each thing" do
        before do
          # acl_for %w(organizations foo groups blah)
          client "x", {}
          cookbook "x", "1.0.0"
          cookbook_artifact "x", "1x1", { "metadata.rb" => cb_metadata("x", "1.0.0") }
          container "x", {}
          data_bag "x", { "y" => {} }
          environment "x", {}
          group "x", {}
          org_invite "foo"
          org_member "bar"
          node "x", {}
          policy "x", "1.0.0", {}
          policy "blah", "1.0.0", {}
          policy_group "x", {
            "policies" => {
              "x" => { "revision_id" => "1.0.0" },
              "blah" => { "revision_id" => "1.0.0" },
            },
          }
          role "x", {}
        end

        before do
          knife("download /acls /groups/clients.json /groups/users.json").should_succeed <<~EOM
            Created /acls/clients/x.json
            Created /acls/containers/x.json
            Created /acls/cookbook_artifacts/x.json
            Created /acls/cookbooks/x.json
            Created /acls/data_bags/x.json
            Created /acls/environments/x.json
            Created /acls/groups/x.json
            Created /acls/nodes/x.json
            Created /acls/policies/blah.json
            Created /acls/policies/x.json
            Created /acls/policy_groups/x.json
            Created /acls/roles/x.json
            Updated /groups/clients.json
            Updated /groups/users.json
          EOM
        end

        it "knife download / downloads everything" do
          knife("download /").should_succeed <<~EOM
            Created /clients/x.json
            Created /containers/x.json
            Created /cookbook_artifacts/x-1x1
            Created /cookbook_artifacts/x-1x1/metadata.rb
            Created /cookbooks/x
            Created /cookbooks/x/metadata.rb
            Created /data_bags/x
            Created /data_bags/x/y.json
            Created /environments/x.json
            Created /groups/x.json
            Updated /invitations.json
            Updated /members.json
            Created /nodes/x.json
            Created /policies/blah-1.0.0.json
            Created /policies/x-1.0.0.json
            Created /policy_groups/x.json
            Created /roles/x.json
          EOM
          knife("diff --name-status /").should_succeed ""
        end

        context "and the repository has an identical copy of each thing" do
          before do
            # TODO We have to upload acls for an existing group due to a lack of
            # dependency detection during upload.  Fix that!
            file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY }
            file "containers/x.json", {}
            file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0")
            file "cookbook_artifacts/x-1x1/metadata.rb", cb_metadata("x", "1.0.0")
            file "data_bags/x/y.json", {}
            file "environments/x.json", {}
            file "groups/x.json", {}
            file "invitations.json", [ "foo" ]
            file "members.json", [ "bar" ]
            file "nodes/x.json", { "normal" => { "tags" => [] } }
            file "org.json", { "full_name" => "Something" }
            file "policies/x-1.0.0.json", { "policy_group_list" => [ "x" ] }
            file "policies/blah-1.0.0.json", { "policy_group_list" => [ "x" ] }
            file "policy_groups/x.json", { "policies" => { "x" => { "revision_id" => "1.0.0" }, "blah" => { "revision_id" => "1.0.0" } } }
            file "roles/x.json", {}
          end

          it "knife download makes no changes" do
            knife("download /").should_succeed ""
          end
        end

        context "and the repository has a slightly different copy of each thing" do
          before do
            # acl_for %w(organizations foo groups blah)
            file "clients/x.json", { "validator" => true }
            file "containers/x.json", {}
            file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.1")
            file "cookbook_artifacts/x-1x1/metadata.rb", cb_metadata("x", "1.0.1")
            file "data_bags/x/y.json", { "a" => "b" }
            file "environments/x.json", { "description" => "foo" }
            file "groups/x.json", { "description" => "foo" }
            file "groups/x.json", { "groups" => [ "admin" ] }
            file "nodes/x.json", { "normal" => { "tags" => [] }, "run_list" => [ "blah" ] }
            file "org.json", { "full_name" => "Something Else " }
            file "policies/x-1.0.0.json", { "run_list" => [ "blah" ] }
            file "policy_groups/x.json", {
              "policies" => {
                "x" => { "revision_id" => "1.0.1" },
                "y" => { "revision_id" => "1.0.0" },
              },
            }
            file "roles/x.json", { "run_list" => [ "blah" ] }
          end

          it "knife download updates everything" do
            knife("download /").should_succeed <<~EOM
              Updated /clients/x.json
              Updated /cookbook_artifacts/x-1x1/metadata.rb
              Updated /cookbooks/x/metadata.rb
              Updated /data_bags/x/y.json
              Updated /environments/x.json
              Updated /groups/x.json
              Updated /invitations.json
              Updated /members.json
              Updated /nodes/x.json
              Updated /org.json
              Created /policies/blah-1.0.0.json
              Updated /policies/x-1.0.0.json
              Updated /policy_groups/x.json
              Updated /roles/x.json
            EOM
            knife("diff --name-status /").should_succeed ""
          end
        end
      end
    end
  end
end
