require 'spec_helper'

describe Puppet::Type.type(:ssh_authorized_key), :unless => Puppet.features.microsoft_windows? do
  include PuppetSpec::Files

  before do
    provider_class = double('provider_class', :name => "fake", :suitable? => true, :supports_parameter? => true)
    allow(described_class).to receive(:defaultprovider).and_return(provider_class)
    allow(described_class).to receive(:provider).and_return(provider_class)

    provider = double('provider', :class => provider_class, :file_path => make_absolute("/tmp/whatever"), :clear => nil)
    allow(provider_class).to receive(:new).and_return(provider)
  end

  it "has :name as its namevar" do
    expect(described_class.key_attributes).to eq [:name]
  end

  describe "when validating attributes" do
    [:name, :provider].each do |param|
      it "has a #{param} parameter" do
        expect(described_class.attrtype(param)).to eq :param
      end
    end

    [:type, :key, :user, :target, :options, :ensure].each do |property|
      it "has a #{property} property" do
        expect(described_class.attrtype(property)).to eq :property
      end
    end
  end

  describe "when validating values" do
    describe "for name" do
      it "supports valid names" do
        described_class.new(:name => "username", :ensure => :present, :user => "nobody")
        described_class.new(:name => "username@hostname", :ensure => :present, :user => "nobody")
      end

      it "supports whitespace" do
        described_class.new(:name => "my test", :ensure => :present, :user => "nobody")
      end
    end

    describe "for ensure" do
      it "supports :present" do
        described_class.new(:name => "whev", :ensure => :present, :user => "nobody")
      end

      it "supports :absent" do
        described_class.new(:name => "whev", :ensure => :absent, :user => "nobody")
      end

      it "nots support other values" do
        expect { described_class.new(:name => "whev", :ensure => :foo, :user => "nobody") }.to raise_error(Puppet::Error, /Invalid value/)
      end
    end

    describe "for type" do
      [
        :'ssh-dss', :dsa,
        :'ssh-rsa', :rsa,
        :'ecdsa-sha2-nistp256',
        :'ecdsa-sha2-nistp384',
        :'ecdsa-sha2-nistp521',
        :ed25519, :'ssh-ed25519',
      ].each do |keytype|
        it "supports #{keytype}" do
          described_class.new(:name => "whev", :type => keytype, :user => "nobody")
        end
      end

      it "aliases :rsa to :ssh-rsa" do
        key = described_class.new(:name => "whev", :type => :rsa, :user => "nobody")
        expect(key.should(:type)).to eq :'ssh-rsa'
      end

      it "aliases :dsa to :ssh-dss" do
        key = described_class.new(:name => "whev", :type => :dsa, :user => "nobody")
        expect(key.should(:type)).to eq :'ssh-dss'
      end

      it "doesn't support values other than ssh-dss, ssh-rsa, dsa, rsa" do
        expect { described_class.new(:name => "whev", :type => :something) }.to raise_error(Puppet::Error,/Invalid value/)
      end
    end

    describe "for key" do
      it "supports a valid key like a 1024 bit rsa key" do
        expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :key => 'AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCPfzW2ry7XvMc6E5Kj2e5fF/YofhKEvsNMUogR3PGL/HCIcBlsEjKisrY0aYgD8Ikp7ZidpXLbz5dBsmPy8hJiBWs5px9ZQrB/EOQAwXljvj69EyhEoGawmxQMtYw+OAIKHLJYRuk1QiHAMHLp5piqem8ZCV2mLb9AsJ6f7zUVw==')}.to_not raise_error
      end

      it "supports a valid key like a 4096 bit rsa key" do
        expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :key => 'AAAAB3NzaC1yc2EAAAADAQABAAACAQDEY4pZFyzSfRc9wVWI3DfkgT/EL033UZm/7x1M+d+lBD00qcpkZ6CPT7lD3Z+vylQlJ5S8Wcw6C5Smt6okZWY2WXA9RCjNJMIHQbJAzwuQwgnwU/1VMy9YPp0tNVslg0sUUgpXb13WW4mYhwxyGmIVLJnUrjrQmIFhtfHsJAH8ZVqCWaxKgzUoC/YIu1u1ScH93lEdoBPLlwm6J0aiM7KWXRb7Oq1nEDZtug1zpX5lhgkQWrs0BwceqpUbY+n9sqeHU5e7DCyX/yEIzoPRW2fe2Gx1Iq6JKM/5NNlFfaW8rGxh3Z3S1NpzPHTRjw8js3IeGiV+OPFoaTtM1LsWgPDSBlzIdyTbSQR7gKh0qWYCNV/7qILEfa0yIFB5wIo4667iSPZw2pNgESVtenm8uXyoJdk8iWQ4mecdoposV/znknNb2GPgH+n/2vme4btZ0Sl1A6rev22GQjVgbWOn8zaDglJ2vgCN1UAwmq41RXprPxENGeLnWQppTnibhsngu0VFllZR5kvSIMlekLRSOFLFt92vfd+tk9hZIiKm9exxcbVCGGQPsf6dZ27rTOmg0xM2Sm4J6RRKuz79HQgA4Eg18+bqRP7j/itb89DmtXEtoZFAsEJw8IgIfeGGDtHTkfAlAC92mtK8byeaxGq57XCTKbO/r5gcOMElZHy1AcB8kw==')}.to_not raise_error
      end

      it "supports a valid key like a 1024 bit dsa key" do
        expect { described_class.new(:name => "whev", :type => :dsa, :user => "nobody", :key => 'AAAAB3NzaC1kc3MAAACBAI80iR78QCgpO4WabVqHHdEDigOjUEHwIjYHIubR/7u7DYrXY+e+TUmZ0CVGkiwB/0yLHK5dix3Y/bpj8ZiWCIhFeunnXccOdE4rq5sT2V3l1p6WP33RpyVYbLmeuHHl5VQ1CecMlca24nHhKpfh6TO/FIwkMjghHBfJIhXK+0w/AAAAFQDYzLupuMY5uz+GVrcP+Kgd8YqMmwAAAIB3SVN71whLWjFPNTqGyyIlMy50624UfNOaH4REwO+Of3wm/cE6eP8n75vzTwQGBpJX3BPaBGW1S1Zp/DpTOxhCSAwZzAwyf4WgW7YyAOdxN3EwTDJZeyiyjWMAOjW9/AOWt9gtKg0kqaylbMHD4kfiIhBzo31ZY81twUzAfN7angAAAIBfva8sTSDUGKsWWIXkdbVdvM4X14K4gFdy0ZJVzaVOtZ6alysW6UQypnsl6jfnbKvsZ0tFgvcX/CPyqNY/gMR9lyh/TCZ4XQcbqeqYPuceGehz+jL5vArfqsW2fJYFzgCcklmr/VxtP5h6J/T0c9YcDgc/xIfWdZAlznOnphI/FA==')}.to_not raise_error
      end

      it "doesn't support whitespaces" do
        expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :key => 'AAA FA==')}.to raise_error(Puppet::Error,/Key must not contain whitespace/)
      end
    end

    describe "for options" do
      it "supports flags as options" do
        expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'cert-authority')}.to_not raise_error
        expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'no-port-forwarding')}.to_not raise_error
      end

      it "supports key-value pairs as options" do
        expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'command="command"')}.to_not raise_error
      end

      it "supports key-value pairs where value consist of multiple items" do
        expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'from="*.domain1,host1.domain2"')}.to_not raise_error
      end

      it "supports environments as options" do
        expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'environment="NAME=value"')}.to_not raise_error
      end

      it "supports multiple options as an array" do
        expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => ['cert-authority','environment="NAME=value"'])}.to_not raise_error
      end

      it "doesn't support a comma separated list" do
        expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'cert-authority,no-port-forwarding')}.to raise_error(Puppet::Error, /must be provided as an array/)
      end

      it "uses :absent as a default value" do
        expect(described_class.new(:name => "whev", :type => :rsa, :user => "nobody").should(:options)).to eq [:absent]
      end

      it "property should return well formed string of arrays from is_to_s" do
        resource = described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => ["a","b","c"])
        expect(resource.property(:options).is_to_s(["a","b","c"])).to eq "['a', 'b', 'c']"
      end

      it "property should return well formed string of arrays from should_to_s" do
        resource = described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => ["a","b","c"])
        expect(resource.property(:options).should_to_s(["a","b","c"])).to eq "['a', 'b', 'c']"
      end
    end

    describe "for user" do
      it "supports present users" do
        described_class.new(:name => "whev", :type => :rsa, :user => "root")
      end

      it "supports absent users" do
        described_class.new(:name => "whev", :type => :rsa, :user => "ihopeimabsent")
      end
    end

    describe "for target" do
      it "supports absolute paths" do
        described_class.new(:name => "whev", :type => :rsa, :target => "/tmp/here")
      end

      it "uses the user's path if not explicitly specified" do
        expect(described_class.new(:name => "whev", :user => 'root').should(:target)).to eq File.expand_path("~root/.ssh/authorized_keys")
      end

      it "doesn't consider the user's path if explicitly specified" do
        expect(described_class.new(:name => "whev", :user => 'root', :target => '/tmp/here').should(:target)).to eq '/tmp/here'
      end

      it "informs about an absent user" do
        Puppet::Log.level = :debug
        described_class.new(:name => "whev", :user => 'idontexist').should(:target)
        expect(@logs.map(&:message)).to include("The required user is not yet present on the system")
      end
    end
  end

  describe "when neither user nor target is specified" do
    it "raises an error" do
      expect do
        described_class.new(
          :name   => "Test",
          :key    => "AAA",
          :type   => "ssh-rsa",
          :ensure => :present)
      end.to raise_error(Puppet::Error,/user.*or.*target.*mandatory/)
    end
  end

  describe "when both target and user are specified" do
    it "uses target" do
      resource = described_class.new(
        :name => "Test",
        :user => "root",
        :target => "/tmp/blah"
      )
      expect(resource.should(:target)).to eq "/tmp/blah"
    end
  end

  describe "when user is specified" do
    it "determines target" do
      resource = described_class.new(
        :name   => "Test",
        :user   => "root"
      )
      target = File.expand_path("~root/.ssh/authorized_keys")
      expect(resource.should(:target)).to eq target
    end

    # Bug #2124 - ssh_authorized_key always changes target if target is not defined
    it "doesn't raise spurious change events" do
      resource = described_class.new(:name => "Test", :user => "root")
      target = File.expand_path("~root/.ssh/authorized_keys")
      expect(resource.property(:target).safe_insync?(target)).to eq true
    end
  end

  describe "when calling validate" do
    it "doesn't crash on a non-existent user" do
      resource = described_class.new(
        :name   => "Test",
        :user   => "ihopesuchuserdoesnotexist"
      )
      resource.validate
    end
  end
end
