#
# 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/lister"

describe ChefCLI::Policyfile::Lister do

  def api_url(org_specific_path)
    "https://chef.example/organizations/myorg/#{org_specific_path}"
  end

  let(:config) do
    double("Chef::Config",
      chef_server_url: "https://localhost:10443",
      client_key: "/path/to/client/key.pem",
      node_name: "deuce")
  end

  let(:http_client) { instance_double(Chef::ServerAPI) }

  subject(:info_fetcher) do
    described_class.new(config: config)
  end

  it "configures an HTTP client" do
    expect(Chef::ServerAPI).to receive(:new).with("https://localhost:10443",
      signing_key_filename: "/path/to/client/key.pem",
      client_name: "deuce")
    info_fetcher.http_client
  end

  context "when the data is fetched successfully from the server" do

    before do
      allow(info_fetcher).to receive(:http_client).and_return(http_client)

      allow(http_client).to receive(:get).with("policy_groups").and_return(policy_group_list_data)
      allow(http_client).to receive(:get).with("policies").and_return(policy_list_data)
    end

    context "when the server has no policies or groups" do

      let(:policy_group_list_data) { {} }
      let(:policy_list_data) { {} }

      it "gives a Hash of policy revisions by policy name" do
        expect(info_fetcher.policies_by_name).to eq({})
      end

      it "gives a Hash of policy revisions by policy group" do
        expect(info_fetcher.policies_by_group).to eq({})
      end

      it "is empty" do
        expect(info_fetcher).to be_empty
      end

      it "has no active revisions" do
        expect(info_fetcher.active_revisions).to be_empty
      end
    end

    context "when the server has policies and groups" do
      ##
      # Example API response data copied from oc-chef-pedant:

      let(:policy_list_data) do
        {
          "appserver" => {
            "uri" => api_url("policies/appserver"),
            "revisions" => {
              "1111111111111111111111111111111111111111" => {},
              "2222222222222222222222222222222222222222" => {},
              "3333333333333333333333333333333333333333" => {},
              "4444444444444444444444444444444444444444" => {},
            },
          },
          "db" => {
            "uri" => api_url("policies/db"),
            "revisions" => {
              "6666666666666666666666666666666666666666" => {},
              "7777777777777777777777777777777777777777" => {},
              "8888888888888888888888888888888888888888" => {},
              "9999999999999999999999999999999999999999" => {},
            },
          },
          "cache" => {
            "uri" => api_url("policies/cache"),
            "revisions" => {
              "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" => {},
              "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" => {},
            },
          },
        }
      end

      let(:dev_group_data) do
        {
          "uri" => api_url("policy_groups/dev"),
          "policies" => {
            "db" => { "revision_id" => "6666666666666666666666666666666666666666" },
            "appserver" => { "revision_id" => "1111111111111111111111111111111111111111" },
            "cache" => { "revision_id" => "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" },
          },
        }
      end

      let(:test_group_data) do
        {
          "uri" => api_url("policy_groups/test"),
          "policies" => {
            "db" => { "revision_id" => "7777777777777777777777777777777777777777" },
            "appserver" => { "revision_id" => "2222222222222222222222222222222222222222" },
            "cache" => { "revision_id" => "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" },
          },
        }
      end

      let(:prod_group_data) do
        {
          "uri" => api_url("policy_groups/prod"),
          "policies" => {
            "db" => { "revision_id" => "8888888888888888888888888888888888888888" },
            "appserver" => { "revision_id" => "3333333333333333333333333333333333333333" },
          },
        }
      end

      let(:policy_group_list_data) do
        {
          "dev" => dev_group_data,
          "test" => test_group_data,
          "prod" => prod_group_data,
        }
      end

      let(:expected_policy_list) do
        {
          "appserver" => {
            "1111111111111111111111111111111111111111" => {},
            "2222222222222222222222222222222222222222" => {},
            "3333333333333333333333333333333333333333" => {},
            "4444444444444444444444444444444444444444" => {},
          },
          "db" => {
            "6666666666666666666666666666666666666666" => {},
            "7777777777777777777777777777777777777777" => {},
            "8888888888888888888888888888888888888888" => {},
            "9999999999999999999999999999999999999999" => {},
          },
          "cache" => {
            "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" => {},
            "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" => {},
          },
        }
      end

      let(:expected_policy_group_list) do
        {
          "dev" => {
            "db" => "6666666666666666666666666666666666666666",
            "appserver" => "1111111111111111111111111111111111111111",
            "cache" => "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          },
          "test" => {
            "db" => "7777777777777777777777777777777777777777",
            "appserver" => "2222222222222222222222222222222222222222",
            "cache" => "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
          },
          "prod" => {
            "db" => "8888888888888888888888888888888888888888",
            "appserver" => "3333333333333333333333333333333333333333",
          },
        }
      end

      it "gives a Hash of policy revisions by policy name" do
        expect(info_fetcher.policies_by_name).to eq(expected_policy_list)
      end

      it "gives a Hash of policy revisions by policy group" do
        expect(info_fetcher.policies_by_group).to eq(expected_policy_group_list)
      end

      it "is not empty" do
        expect(info_fetcher).to_not be_empty
      end

      it "lists active revisions" do
        expected_active_revisions = expected_policy_group_list.values.map(&:values).flatten

        expected_active_revisions_set = Set.new(expected_active_revisions)
        expect(info_fetcher.active_revisions).to eq(expected_active_revisions_set)
      end

      it "lists orphaned revisions for a given policy" do
        expect(info_fetcher.orphaned_revisions("db")).to eq(%w{ 9999999999999999999999999999999999999999 })
        expect(info_fetcher.orphaned_revisions("appserver")).to eq(%w{ 4444444444444444444444444444444444444444 })
        expect(info_fetcher.orphaned_revisions("cache")).to eq([])
      end

      it "yields revision ids by group" do
        map = {}

        info_fetcher.revision_ids_by_group_for_each_policy do |policy_name, rev_id_by_group|
          map[policy_name] = rev_id_by_group
        end

        appserver_rev_ids = {
          "dev" => "1111111111111111111111111111111111111111",
          "test" => "2222222222222222222222222222222222222222",
          "prod" => "3333333333333333333333333333333333333333",
        }

        expect(map["appserver"].revision_ids_by_group).to eq(appserver_rev_ids)

        db_rev_ids = {
          "dev" => "6666666666666666666666666666666666666666",
          "test" => "7777777777777777777777777777777777777777",
          "prod" => "8888888888888888888888888888888888888888",
        }

        expect(map["db"].revision_ids_by_group).to eq(db_rev_ids)

        cache_rev_ids = {
          "dev" => "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          "test" => "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
          "prod" => nil,
        }
        expect(map["cache"].revision_ids_by_group).to eq(cache_rev_ids)
      end

      context "when the server has an empty group" do

        let(:dev_group_data) do
          {
            "uri" => api_url("policy_groups/dev"),
          }
        end

        # Regression test: this exercises the case where the policy group data
        # from the server has no "policies" key, which previously caused a NoMethodError.
        it "correctly lists groups without policies" do
          expect(info_fetcher.policies_by_group["dev"]).to eq({})
        end

      end

    end

  end

end