#
# Author:: John Keiser (<jkeiser@opscode.com>)
# Copyright:: Copyright (c) 2013 Opscode, 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 'support/shared/integration/integration_helper'
require 'chef/knife/diff'

describe 'knife diff', :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 diff reports everything as deleted' do
          knife('diff --name-status /').should_succeed <<EOM
D\t/clients/chef-validator.json
D\t/clients/chef-webui.json
D\t/clients/x.json
D\t/cookbooks/x
D\t/data_bags/x
D\t/environments/_default.json
D\t/environments/x.json
D\t/nodes/x.json
D\t/roles/x.json
D\t/users/admin.json
D\t/users/x.json
EOM
      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', {}
          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 diff reports no differences' do
          knife('diff /').should_succeed ''
        end

        it 'knife diff /environments/nonexistent.json reports an error' do
          knife('diff /environments/nonexistent.json').should_fail "ERROR: /environments/nonexistent.json: No such file or directory on remote or local\n"
        end

        it 'knife diff /environments/*.txt reports an error' do
          knife('diff /environments/*.txt').should_fail "ERROR: /environments/*.txt: No such file or directory on remote or local\n"
        end

        context 'except the role file' do
          before do
            file 'roles/x.json', <<EOM
{
  "foo": "bar"
}
EOM
          end

          it 'knife diff reports the role as different' do
            knife('diff --name-status /').should_succeed <<EOM
M\t/roles/x.json
EOM
          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("y", "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 diff reports the new files as added' do
            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

          context 'when cwd is the data_bags directory' do
            before { cwd 'data_bags' }
            it 'knife diff reports different data bags' do
              knife('diff --name-status').should_succeed <<EOM
A\tx/z.json
A\ty
EOM
            end
            it 'knife diff * reports different data bags' do
              knife('diff --name-status *').should_succeed <<EOM
A\tx/z.json
A\ty
EOM
            end
          end
        end
      end

      when_the_repository 'is empty' do
        it 'knife diff reports everything as deleted' do
          knife('diff --name-status /').should_succeed <<EOM
D\t/clients
D\t/cookbooks
D\t/data_bags
D\t/environments
D\t/nodes
D\t/roles
D\t/users
EOM
        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', ''
      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' => '' }
        end

        it 'knife diff /cookbooks/x shows differences' do
          knife('diff --name-status /cookbooks/x').should_succeed <<EOM
M\t/cookbooks/x/metadata.rb
D\t/cookbooks/x/onlyin1.0.1.rb
A\t/cookbooks/x/onlyin1.0.0.rb
EOM
        end

        it 'knife diff --diff-filter=MAT does not show deleted files' do
          knife('diff --diff-filter=MAT --name-status /cookbooks/x').should_succeed <<EOM
M\t/cookbooks/x/metadata.rb
A\t/cookbooks/x/onlyin1.0.0.rb
EOM
        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' => '' }
        end
        it 'knife diff /cookbooks/x shows no differences' do
          knife('diff --name-status /cookbooks/x').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' => '' }
        end

        it 'knife diff /cookbooks/x shows the differences' do
          knife('diff --name-status /cookbooks/x').should_succeed <<EOM
M\t/cookbooks/x/metadata.rb
D\t/cookbooks/x/onlyin1.0.1.rb
A\t/cookbooks/x/onlyin1.0.0.rb
EOM
        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' => '' }
        end

        it 'knife diff /cookbooks/x shows the differences' do
          knife('diff --name-status /cookbooks/x').should_succeed <<EOM
M\t/cookbooks/x/metadata.rb
D\t/cookbooks/x/onlyin0.9.9.rb
A\t/cookbooks/x/onlyin1.0.0.rb
EOM
        end
      end
    end

    context 'json diff tests' do
      when_the_repository 'has an empty environment file' do
        before do
          file 'environments/x.json', {}
        end

        when_the_chef_server 'has an empty environment' do
          before { environment 'x', {} }
          it 'knife diff returns no differences' do
            knife('diff /environments/x.json').should_succeed ''
          end
        end
        when_the_chef_server 'has an environment with a different value' do
          before { environment 'x', { 'description' => 'hi' } }
          it 'knife diff reports the difference', :pending => (RUBY_VERSION < "1.9") do
            knife('diff /environments/x.json').should_succeed(/
 {
-  "name": "x",
-  "description": "hi"
\+  "name": "x"
 }
/)
          end
        end
      end

      when_the_repository 'has an environment file with a value in it' do
        before do
          file 'environments/x.json', { 'description' => 'hi' }
        end

        when_the_chef_server 'has an environment with the same value' do
          before do
            environment 'x', { 'description' => 'hi' }
          end
          it 'knife diff returns no differences' do
            knife('diff /environments/x.json').should_succeed ''
          end
        end
        when_the_chef_server 'has an environment with no value' do
          before do
            environment 'x', {}
          end

          it 'knife diff reports the difference', :pending => (RUBY_VERSION < "1.9") do
            knife('diff /environments/x.json').should_succeed(/
 {
-  "name": "x"
\+  "name": "x",
\+  "description": "hi"
 }
/)
          end
        end
        when_the_chef_server 'has an environment with a different value' do
          before do
            environment 'x', { 'description' => 'lo' }
          end
          it 'knife diff reports the difference', :pending => (RUBY_VERSION < "1.9") do
            knife('diff /environments/x.json').should_succeed(/
 {
   "name": "x",
-  "description": "lo"
\+  "description": "hi"
 }
/)
          end
        end
      end
    end

    when_the_chef_server 'has an environment' do
      before { environment 'x', {} }
      when_the_repository 'has an environment with bad JSON' do
        before { file 'environments/x.json', '{' }
        it 'knife diff reports an error and does a textual diff' do
          error_text = "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: parse error: premature EOF"
          error_match = Regexp.new(Regexp.escape(error_text))
          knife('diff /environments/x.json').should_succeed(/-  "name": "x"/, :stderr => error_match)
        end
      end
    end
  end # without versioned cookbooks

  with_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 diff reports everything as deleted' do
          knife('diff --name-status /').should_succeed <<EOM
D\t/clients/chef-validator.json
D\t/clients/chef-webui.json
D\t/clients/x.json
D\t/cookbooks/x-1.0.0
D\t/data_bags/x
D\t/environments/_default.json
D\t/environments/x.json
D\t/nodes/x.json
D\t/roles/x.json
D\t/users/admin.json
D\t/users/x.json
EOM
      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', {}
          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 diff reports no differences' do
          knife('diff /').should_succeed ''
        end

        it 'knife diff /environments/nonexistent.json reports an error' do
          knife('diff /environments/nonexistent.json').should_fail "ERROR: /environments/nonexistent.json: No such file or directory on remote or local\n"
        end

        it 'knife diff /environments/*.txt reports an error' do
          knife('diff /environments/*.txt').should_fail "ERROR: /environments/*.txt: No such file or directory on remote or local\n"
        end

        context 'except the role file' do
          before do
            file 'roles/x.json', <<EOM
{
  "foo": "bar"
}
EOM
          end

          it 'knife diff reports the role as different' do
            knife('diff --name-status /').should_succeed <<EOM
M\t/roles/x.json
EOM
          end
        end

        context 'as well as one extra copy of each thing' do
          before do
            file 'clients/y.json', {}
            file 'cookbooks/x-1.0.0/blah.rb', ''
            file 'cookbooks/x-2.0.0/metadata.rb', cb_metadata("x", "2.0.0")
            file 'cookbooks/y-1.0.0/metadata.rb', cb_metadata("y", "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', {}
          end

          it 'knife diff reports the new files as added' do
            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

          context 'when cwd is the data_bags directory' do
            before { cwd 'data_bags' }
            it 'knife diff reports different data bags' do
              knife('diff --name-status').should_succeed <<EOM
A\tx/z.json
A\ty
EOM
            end
            it 'knife diff * reports different data bags' do
              knife('diff --name-status *').should_succeed <<EOM
A\tx/z.json
A\ty
EOM
            end
          end
        end
      end

      when_the_repository 'is empty' do
        it 'knife diff reports everything as deleted' do
          knife('diff --name-status /').should_succeed <<EOM
D\t/clients
D\t/cookbooks
D\t/data_bags
D\t/environments
D\t/nodes
D\t/roles
D\t/users
EOM
        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', ''
      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' => '' }
        end

        it 'knife diff /cookbooks shows differences' do
          knife('diff --name-status /cookbooks').should_succeed <<EOM
D\t/cookbooks/x-1.0.1
EOM
        end

        it 'knife diff --diff-filter=MAT does not show deleted files' do
          knife('diff --diff-filter=MAT --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' => '' }
        end
        it 'knife diff /cookbooks shows the differences' do
          knife('diff --name-status /cookbooks').should_succeed "D\t/cookbooks/x-0.9.9\n"
        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' => '' }
        end

        it 'knife diff /cookbooks shows the differences' do
          knife('diff --name-status /cookbooks').should_succeed <<EOM
D\t/cookbooks/x-1.0.1
A\t/cookbooks/x-1.0.0
EOM
        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' => '' }
        end

        it 'knife diff /cookbooks shows the differences' do
          knife('diff --name-status /cookbooks').should_succeed <<EOM
D\t/cookbooks/x-0.9.9
A\t/cookbooks/x-1.0.0
EOM
        end
      end
    end

    context 'json diff tests' do
      when_the_repository 'has an empty environment file' do
        before { file 'environments/x.json', {} }
        when_the_chef_server 'has an empty environment' do
          before { environment 'x', {} }
          it 'knife diff returns no differences' do
            knife('diff /environments/x.json').should_succeed ''
          end
        end
        when_the_chef_server 'has an environment with a different value' do
          before { environment 'x', { 'description' => 'hi' } }
          it 'knife diff reports the difference', :pending => (RUBY_VERSION < "1.9") do
            knife('diff /environments/x.json').should_succeed(/
 {
-  "name": "x",
-  "description": "hi"
\+  "name": "x"
 }
/)
          end
        end
      end

      when_the_repository 'has an environment file with a value in it' do
        before do
          file 'environments/x.json', { 'description' => 'hi' }
        end

        when_the_chef_server 'has an environment with the same value' do
          before do
            environment 'x', { 'description' => 'hi' }
          end
          it 'knife diff returns no differences' do
            knife('diff /environments/x.json').should_succeed ''
          end
        end
        when_the_chef_server 'has an environment with no value' do
          before { environment 'x', {} }
          it 'knife diff reports the difference', :pending => (RUBY_VERSION < "1.9") do
            knife('diff /environments/x.json').should_succeed(/
 {
-  "name": "x"
\+  "name": "x",
\+  "description": "hi"
 }
/)
          end
        end
        when_the_chef_server 'has an environment with a different value' do
          before do
            environment 'x', { 'description' => 'lo' }
          end
          it 'knife diff reports the difference', :pending => (RUBY_VERSION < "1.9") do
            knife('diff /environments/x.json').should_succeed(/
 {
   "name": "x",
-  "description": "lo"
\+  "description": "hi"
 }
/)
          end
        end
      end
    end

    when_the_chef_server 'has an environment' do
      before { environment 'x', {} }
      when_the_repository 'has an environment with bad JSON' do
        before { file 'environments/x.json', '{' }
        it 'knife diff reports an error and does a textual diff' do
          error_text = "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: parse error: premature EOF"
          error_match = Regexp.new(Regexp.escape(error_text))
          knife('diff /environments/x.json').should_succeed(/-  "name": "x"/, :stderr => error_match)
        end
      end
    end
  end # without versioned cookbooks
end