require "spec_helper"

module Berkshelf
  describe Downloader do
    let(:berksfile) do
      double(Berksfile,
        lockfile: lockfile,
        dependencies: [])
    end

    let(:lockfile) do
      double(Lockfile,
        graph: graph)
    end

    let(:graph) { double(Lockfile::Graph, locks: {}) }
    let(:self_signed_crt_path) { File.join(BERKS_SPEC_DATA, "trusted_certs") }
    let(:self_signed_crt) { OpenSSL::X509::Certificate.new(IO.read("#{self_signed_crt_path}/example.crt")) }
    let(:cert_store) { OpenSSL::X509::Store.new.add_cert(self_signed_crt) }
    let(:ssl_policy) { double(SSLPolicy, store: cert_store) }

    subject { described_class.new(berksfile) }

    describe "#download" do
      skip
    end

    describe "#try_download" do
      let(:remote_cookbook) { double("remote-cookbook") }
      let(:source) do
        source = double("source")
        allow(source).to receive(:cookbook) { remote_cookbook }
        source
      end
      let(:name) { "fake" }
      let(:version) { "1.0.0" }

      it "supports the 'opscode' location type" do
        allow(source).to receive(:type) { :supermarket }
        allow(source).to receive(:options) { { ssl: {} } }
        allow(remote_cookbook).to receive(:location_type) { :opscode }
        allow(remote_cookbook).to receive(:location_path) { "http://api.opscode.com" }
        rest = double("community-rest")
        expect(CommunityREST).to receive(:new).with("http://api.opscode.com", { ssl: {} }) { rest }
        expect(rest).to receive(:download).with(name, version)
        subject.try_download(source, name, version)
      end

      it "supports the 'supermarket' location type" do
        allow(source).to receive(:type) { :supermarket }
        allow(source).to receive(:options) { { ssl: {} } }
        allow(remote_cookbook).to receive(:location_type) { :supermarket }
        allow(remote_cookbook).to receive(:location_path) { "http://api.supermarket.com" }
        rest = double("community-rest")
        expect(CommunityREST).to receive(:new).with("http://api.supermarket.com", { ssl: {} }) { rest }
        expect(rest).to receive(:download).with(name, version)
        subject.try_download(source, name, version)
      end

      context "supports location paths" do
        before(:each) do
          allow(source).to receive(:type) { :supermarket }
          allow(source).to receive(:options) { { ssl: {} } }
          allow(source).to receive(:uri_string).and_return("http://localhost:8081/repository/chef-proxy")
          allow(remote_cookbook).to receive(:location_type) { :opscode }
        end

        let(:rest) { double("community-rest") }

        it "that are relative and prepends the source URI for the download" do
          allow(remote_cookbook).to receive(:location_path) { "/api/v1" }
          expect(CommunityREST).to receive(:new).with("http://localhost:8081/repository/chef-proxy/api/v1", { ssl: {} }) { rest }
          expect(rest).to receive(:download).with(name, version)
          subject.try_download(source, name, version)
        end

        it "that are absolute and uses the given absolute URI" do
          allow(remote_cookbook).to receive(:location_path) { "http://localhost:8081/repository/chef-proxy/api/v1" }
          expect(CommunityREST).to receive(:new).with("http://localhost:8081/repository/chef-proxy/api/v1", { ssl: {} }) { rest }
          expect(rest).to receive(:download).with(name, version)
          subject.try_download(source, name, version)
        end
      end

      context "with an artifactory source" do
        it "supports the 'opscode' location type" do
          allow(source).to receive(:type) { :artifactory }
          allow(source).to receive(:options) { { api_key: "secret", ssl: {} } }
          allow(remote_cookbook).to receive(:location_type) { :opscode }
          allow(remote_cookbook).to receive(:location_path) { "http://artifactory/" }
          rest = double("community-rest")
          expect(CommunityREST).to receive(:new).with("http://artifactory/", { ssl: {}, headers: { "X-Jfrog-Art-Api" => "secret" } }) { rest }
          expect(rest).to receive(:download).with(name, version)
          subject.try_download(source, name, version)
        end

        it "supports the 'supermarket' location type" do
          allow(source).to receive(:type) { :artifactory }
          allow(source).to receive(:options) { { api_key: "secret", ssl: {} } }
          allow(remote_cookbook).to receive(:location_type) { :supermarket }
          allow(remote_cookbook).to receive(:location_path) { "http://artifactory/" }
          rest = double("community-rest")
          expect(CommunityREST).to receive(:new).with("http://artifactory/", { ssl: {}, headers: { "X-Jfrog-Art-Api" => "secret" } }) { rest }
          expect(rest).to receive(:download).with(name, version)
          subject.try_download(source, name, version)
        end
      end

      describe "chef_server location type" do
        let(:chef_server_url) { "http://configured-chef-server/" }
        let(:ridley_client) do
          instance_double(Berkshelf::RidleyCompat)
        end
        let(:chef_config) do
          double(Berkshelf::ChefConfigCompat,
            node_name: "fake-client",
            client_key: "client-key",
            chef_server_url: chef_server_url,
            validation_client_name: "validator",
            validation_key: "validator.pem",
            artifactory_api_key: "secret",
            cookbook_copyright: "user",
            cookbook_email: "user@example.com",
            cookbook_license: "apachev2",
            trusted_certs_dir: self_signed_crt_path)
        end

        let(:berkshelf_config) do
          double(Config,
            ssl:  double(verify: true),
            chef: chef_config)
        end

        before do
          allow(Berkshelf).to receive(:config).and_return(berkshelf_config)
          allow(subject).to receive(:ssl_policy).and_return(ssl_policy)
          allow(remote_cookbook).to receive(:location_type) { :chef_server }
          allow(remote_cookbook).to receive(:location_path) { chef_server_url }
          allow(source).to receive(:options) { { read_timeout: 30, open_timeout: 3, ssl: { verify: true, cert_store: cert_store } } }
        end

        it "uses the berkshelf config and provides a custom cert_store" do
          credentials = {
            server_url: chef_server_url,
            client_name: chef_config.node_name,
            client_key: chef_config.client_key,
            ssl: {
              verify: berkshelf_config.ssl.verify,
              cert_store: cert_store,
            },
          }
          expect(Berkshelf::RidleyCompat).to receive(:new_client).with(credentials) { ridley_client }
          subject.try_download(source, name, version)
        end

        context "with a source option for client_name" do
          before do
            allow(source).to receive(:options) { { client_name: "other-client", read_timeout: 30, open_timeout: 3, ssl: { verify: true, cert_store: cert_store } } }
          end
          it "uses the override" do
            credentials = {
              server_url: chef_server_url,
              client_name: "other-client",
              client_key: chef_config.client_key,
              ssl: {
                verify: berkshelf_config.ssl.verify,
                cert_store: cert_store,
              },
            }
            expect(Berkshelf::RidleyCompat).to receive(:new_client).with(credentials) { ridley_client }
            subject.try_download(source, name, version)
          end
        end

        context "with a source option for client_key" do
          before do
            allow(source).to receive(:options) { { client_key: "other-key", read_timeout: 30, open_timeout: 3, ssl: { verify: true, cert_store: cert_store } } }
          end
          it "uses the override" do
            credentials = {
              server_url: chef_server_url,
              client_name: chef_config.node_name,
              client_key: "other-key",
              ssl: {
                verify: berkshelf_config.ssl.verify,
                cert_store: cert_store,
              },
            }
            expect(Berkshelf::RidleyCompat).to receive(:new_client).with(credentials) { ridley_client }
            subject.try_download(source, name, version)
          end
        end
      end

      it "supports the 'file_store' location type" do
        skip
      end
    end
  end
end
