#
# Copyright:: 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 "spec_helper"
require "chef-cli/policyfile/solution_dependencies"

describe ChefCLI::Policyfile::SolutionDependencies do

  let(:dependency_data) { { "Policyfile" => [], "dependencies" => {} } }

  let(:solution_dependencies) do
    s = described_class.new
    s.consume_lock_data(dependency_data)
    s
  end

  it "has a list of dependencies declared in the Policyfile" do
    expect(solution_dependencies.policyfile_dependencies).to eq([])
  end

  it "has a map of dependencies declared by cookbooks" do
    expect(solution_dependencies.cookbook_dependencies).to eq({})
  end

  context "when populated with dependency data from a lockfile" do

    let(:dependency_data) do
      {
        "Policyfile" => [
          [ "nginx", "~> 1.0"], ["postgresql", ">= 0.0.0" ]
        ],
        "dependencies" => {
          "nginx (1.2.3)" => [ ["apt", "~> 2.3"], ["yum", "~>3.4"] ],
          "apt (2.5.6)" => [],
          "yum (3.4.1)" => [],
          "postgresql (5.0.0)" => [],
        },
      }
    end

    it "has a list of dependencies from the policyfile" do
      expected = [ "nginx", "~> 1.0"], ["postgresql", ">= 0.0.0" ]
      expect(solution_dependencies.policyfile_dependencies_for_lock).to eq(expected)
    end

    it "has a list of dependencies from cookbooks" do
      expected = {
        "nginx (1.2.3)" => [ ["apt", "~> 2.3"], ["yum", "~> 3.4"] ],
        "apt (2.5.6)" => [],
        "yum (3.4.1)" => [],
        "postgresql (5.0.0)" => [],
      }
      expect(solution_dependencies.cookbook_deps_for_lock).to eq(expected)
    end

  end

  context "when populated with dependency data from a complex lockfile" do
    let(:dependency_data) do
      {
        "Policyfile" => [
          [ "a", ">= 0.0.0"], ["b", ">= 0.0.0" ]
        ],
        "dependencies" => {
          "a (0.1.0)" => [ ["c", "~> 1.0.0"], ["d", "~> 0.0.0"] ],
          "b (1.0.0)" => [ ["f", ">= 0.0.1"] ],
          "c (1.0.1)" => [ ["e", ">= 0.0.1"] ],
          "d (0.0.1)" => [],
          "e (0.0.1)" => [],
          "f (0.0.1)" => [],
        },
      }
    end
    it "can compute list of transitive dependencies" do

      expect(solution_dependencies.transitive_deps(["e"])).to eq(["e"])
      expect(solution_dependencies.transitive_deps(["c"])).to eq(%w{c e})
      expect(solution_dependencies.transitive_deps(%w{c d})).to eq(%w{c d e})
      expect(solution_dependencies.transitive_deps(["a"])).to eq(%w{a c d e})
    end
  end

  context "when populated with dependency data" do

    let(:expected_deps_for_lock) do
      {
        "nginx (1.2.3)" => [ ["apt", "~> 2.3"], ["yum", "~> 3.4"] ],
        "apt (2.5.6)" => [],
        "yum (3.4.1)" => [],
        "postgresql (5.0.0)" => [],
      }
    end

    let(:expected_policyfile_deps_for_lock) do
      [ [ "nginx", "~> 1.0"], ["postgresql", ">= 0.0.0" ] ]
    end

    before do
      solution_dependencies.add_policyfile_dep("nginx", "~> 1.0")
      solution_dependencies.add_policyfile_dep("postgresql", ">= 0.0.0")
      solution_dependencies.add_cookbook_dep("nginx", "1.2.3", [ ["apt", "~> 2.3"], ["yum", "~>3.4"] ])
      solution_dependencies.add_cookbook_dep("apt", "2.5.6", [])
      solution_dependencies.add_cookbook_dep("yum", "3.4.1", [])
      solution_dependencies.add_cookbook_dep("postgresql", "5.0.0", [])
    end

    it "has a list of dependencies from the Policyfile" do
      expect(solution_dependencies.policyfile_dependencies_for_lock).to eq(expected_policyfile_deps_for_lock)
    end

    it "has a list of dependencies from cookbooks" do
      expect(solution_dependencies.cookbook_deps_for_lock).to eq(expected_deps_for_lock)
    end

    it "generates lock info containing both policyfile and cookbook dependencies" do
      expected = { "Policyfile" => expected_policyfile_deps_for_lock, "dependencies" => expected_deps_for_lock }
      expect(solution_dependencies.to_lock).to eq(expected)
    end

    describe "checking for dependency conflicts" do

      it "does not raise if a cookbook that's in the dependency set with a different version doesn't conflict" do
        solution_dependencies.update_cookbook_dep("yum", "3.5.0", [ ])
        expect(solution_dependencies.test_conflict!("yum", "3.5.0")).to be(false)
      end

      it "raises if a cookbook is not in the current solution set" do
        expected_message = "Cookbook foo (1.0.0) not in the working set, cannot test for conflicts"
        expect { solution_dependencies.test_conflict!("foo", "1.0.0") }.to raise_error(ChefCLI::CookbookNotInWorkingSet, expected_message)
      end

      it "raises when a cookbook conflicts with a Policyfile constraint" do
        solution_dependencies.update_cookbook_dep("nginx", "2.0.0", [])

        expected_message = "Cookbook nginx (2.0.0) conflicts with other dependencies:\nPolicyfile depends on nginx ~> 1.0"
        expect { solution_dependencies.test_conflict!("nginx", "2.0.0") }.to raise_error(ChefCLI::DependencyConflict, expected_message)
      end

      it "raises when a cookbook conflicts with another cookbook's dependency constraint" do
        solution_dependencies.update_cookbook_dep("apt", "3.0.0", [])

        expected_message = "Cookbook apt (3.0.0) conflicts with other dependencies:\nnginx (1.2.3) depends on apt ~> 2.3"
        expect { solution_dependencies.test_conflict!("apt", "3.0.0") }.to raise_error(ChefCLI::DependencyConflict, expected_message)
      end

      it "raises when a cookbook's dependencies are no longer satisfiable" do
        solution_dependencies.update_cookbook_dep("nginx", "1.2.3", [ [ "apt", "~> 3.0" ] ])
        expected_message = "Cookbook nginx (1.2.3) has dependency constraints that cannot be met by the existing cookbook set:\n" +
          "Dependency on apt ~> 3.0 conflicts with existing version apt (2.5.6)"
        expect { solution_dependencies.test_conflict!("nginx", "1.2.3") }.to raise_error(ChefCLI::DependencyConflict, expected_message)
      end

    end
  end

end