require "spec_helper" describe Berkshelf::Berksfile do describe "ClassMethods" do describe "::from_file" do let(:content) do <<-EOF.strip cookbook 'ntp', '<= 1.0.0' cookbook 'mysql' cookbook 'nginx', '< 0.101.2' cookbook 'ssh_known_hosts2', :git => 'https://github.com/erikh/chef-ssh_known_hosts2.git' solver :foo, :required EOF end let(:berksfile) { tmp_path.join("Berksfile") } before { File.open(berksfile, "w+") { |f| f.write(content) } } subject(:from_file) { described_class.from_file(berksfile) } it "reads the content of the Berksfile and binds them to a new instance" do %w{ntp mysql nginx ssh_known_hosts2}.each do |name| expect(subject).to have_dependency(name) end end it "returns an instance of Berkshelf::Berksfile" do expect(subject).to be_a(described_class) end context "when Berksfile does not exist at given path" do let(:bad_path) { tmp_path.join("thisdoesnotexist") } it "raises BerksfileNotFound" do expect do Berkshelf::Berksfile.from_file(bad_path) end.to raise_error(Berkshelf::BerksfileNotFound) end end end end let(:dependency_one) { double("dependency_one", name: "nginx") } let(:dependency_two) { double("dependency_two", name: "mysql") } subject do berksfile_path = tmp_path.join("Berksfile").to_s FileUtils.touch(berksfile_path) Berkshelf::Berksfile.new(berksfile_path) end describe "#cookbook" do let(:name) { "artifact" } let(:constraint) { double("constraint") } let(:default_options) { { group: [] } } it "sends the add_dependency message with the name, constraint, and options to the instance of the includer" do expect(subject).to receive(:add_dependency).with(name, constraint, default_options) subject.cookbook(name, constraint, default_options) end it "merges the default options into specified options" do expect(subject).to receive(:add_dependency) do |arg_name, arg_constraint, arg_options| expect(arg_name).to eq(name) expect(arg_constraint).to eq(constraint) expect(arg_options[:path]).to match(%r{/Users/reset}) expect(arg_options[:group]).to eq([]) end subject.cookbook(name, constraint, path: "/Users/reset") end it "converts a single specified group option into an array of groups" do expect(subject).to receive(:add_dependency).with(name, constraint, group: [:production]) subject.cookbook(name, constraint, group: :production) end context "when no constraint specified" do it "sends the add_dependency message with a nil value for constraint" do expect(subject).to receive(:add_dependency).with(name, nil, default_options) subject.cookbook(name, default_options) end end context "when no options specified" do it "sends the add_dependency message with an empty Hash for the value of options" do expect(subject).to receive(:add_dependency).with(name, constraint, default_options) subject.cookbook(name, constraint) end end it "is a DSL method" do expect(subject).to have_exposed_method(:cookbook) end end describe "#group" do let(:name) { "artifact" } let(:group) { "production" } it "sends the add_dependency message with an array of groups determined by the parameter to the group block" do expect(subject).to receive(:add_dependency).with(name, nil, group: [group]) subject.group(group) do subject.cookbook(name) end end it "is a DSL method" do expect(subject).to have_exposed_method(:group) end end describe "#metadata" do let(:path) { fixtures_path.join("cookbooks/example_cookbook") } subject { Berkshelf::Berksfile.new(path.join("Berksfile")) } before { Dir.chdir(path) } it "sends the add_dependency message with an explicit version constraint and the path to the cookbook" do expect(subject).to receive(:add_dependency).with("example_cookbook", nil, path: path.to_s, metadata: true) subject.metadata end it "is a DSL method" do expect(subject).to have_exposed_method(:metadata) end end describe "#source" do let(:new_source) { "http://berks.riotgames.com" } it "is a DSL method" do expect(subject).to have_exposed_method(:source) end it "adds a source to the sources" do subject.source(new_source) expect(subject.sources.map(&:to_s)).to include(new_source) end it "converts the string to a Source" do subject.source(new_source) subject.sources.each do |source| expect(source).to be_a(Berkshelf::Source) end end it "adds each source in order they appear" do subject.source(new_source) subject.source("http://berks.other.com") expect(subject.sources[0].to_s).to eq(new_source) expect(subject.sources[1].to_s).to eq("http://berks.other.com") end it "does not add duplicate entries" do subject.source(new_source) subject.source(new_source) expect(subject.sources[0].to_s).to eq(new_source) expect(subject.sources[1].to_s).to_not eq(new_source) end context "adding an invalid source" do let(:invalid_uri) { ".....$1233...." } it "raises an InvalidSourceURI" do expect { subject.source(invalid_uri).uri }.to raise_error(Berkshelf::InvalidSourceURI) end end end describe "#sources" do context "when there are no sources" do it "raises an exception" do expect do subject.sources end.to raise_error(Berkshelf::NoAPISourcesDefined) end end context "when there are sources" do before { subject.source("https://api.berkshelf.org") } it "returns an Array" do expect(subject.sources).to be_a(Array) end it "contains a collection of Berkshelf::Source" do subject.sources.each do |source| expect(source).to be_a(Berkshelf::Source) end end end end describe "#extension" do it "is a DSL method" do expect(subject).to have_exposed_method(:extension) end end describe "#dependencies" do let(:groups) do %i{ nautilus skarner } end it "returns all Berkshelf::Dependencys added to the instance of Berksfile" do subject.add_dependency(dependency_one.name) subject.add_dependency(dependency_two.name) expect(subject.dependencies.size).to eq(2) expect(subject).to have_dependency(dependency_one.name) expect(subject).to have_dependency(dependency_two.name) end end describe "#cookbooks" do it "raises an exception if a cookbook is not installed" do subject.add_dependency("bacon", nil) expect { subject.cookbooks }.to raise_error(Berkshelf::DependencyNotFound) end it "retrieves the locked (cached) cookbook for each dependency" do subject.add_dependency("bacon", nil) subject.add_dependency("ham", nil) allow(subject).to receive(:retrive_locked) expect(subject).to receive(:retrieve_locked).twice subject.cookbooks end end describe "#groups" do before do allow(subject).to receive(:dependencies) { [dependency_one, dependency_two] } allow(dependency_one).to receive(:groups) { %i{nautilus skarner} } allow(dependency_two).to receive(:groups) { %i{nautilus riven} } end it "returns a hash containing keys for every group a dependency is a member of" do expect(subject.groups.keys.size).to eq(3) expect(subject.groups).to have_key(:nautilus) expect(subject.groups).to have_key(:skarner) expect(subject.groups).to have_key(:riven) end it "returns an Array of Berkshelf::Dependencys who are members of the group for value" do expect(subject.groups[:nautilus].size).to eq(2) expect(subject.groups[:riven].size).to eq(1) end end describe "#add_dependency" do let(:name) { "cookbook_one" } let(:constraint) { "= 1.2.0" } let(:options) { {} } before(:each) do subject.add_dependency(name, constraint, options) end let(:dependency) { subject.dependencies.first } it "adds new dependency to the list of dependencies" do expect(subject.dependencies.size).to eq(1) end it "is a Berkshelf::Dependency" do expect(dependency).to be_a(Berkshelf::Dependency) end it "has a name matching the given name" do expect(dependency.name).to eq(name) end it "has a version_constraint matching the given constraint" do expect(dependency.version_constraint.to_s).to eq(constraint) end it "raises DuplicateDependencyDefined if multiple dependencies of the same name are found" do expect do subject.add_dependency(name) end.to raise_error(Berkshelf::DuplicateDependencyDefined) end it "has a nil location if no location options are provided" do expect(dependency.location).to be_nil end context "when given the :git option" do let(:options) { { git: "git@github.com:berkshelf/berkshelf.git" } } it "has a GitLocation location" do expect(dependency.location).to be_a(Berkshelf::GitLocation) end end context "when given the :github option" do let(:options) { { github: "berkshelf/berkshelf" } } it "has a GithubLocation location" do expect(dependency.location).to be_a(Berkshelf::GithubLocation) end end context "when given the :path option" do let(:options) { { path: fixtures_path.join("cookbooks", "example_cookbook") } } it "has a PathLocation location" do expect(dependency.location).to be_a(Berkshelf::PathLocation) end end end describe "#retrieve_locked" do let(:lockfile) { double("lockfile") } let(:dependency) { double("dependency", name: "bacon") } let(:locked) { double("locked", cached_cookbook: cached, locked_version: "1.0.0") } let(:cached) { double("cached") } before do allow(subject).to receive(:lockfile).and_return(lockfile) end it "delegates to the lockfile" do expect(lockfile).to receive(:retrieve).with(dependency) subject.retrieve_locked(dependency) end end describe "#upload" do let(:uploader) { double(Berkshelf::Uploader, run: nil) } before do allow(subject).to receive(:validate_lockfile_present!) allow(subject).to receive(:validate_lockfile_trusted!) allow(subject).to receive(:validate_dependencies_installed!) allow(Berkshelf::Uploader).to receive(:new).and_return(uploader) end it "validates the lockfile is present" do expect(subject).to receive(:validate_lockfile_present!).once subject.upload end it "validates the lockfile is trusted" do expect(subject).to receive(:validate_lockfile_trusted!).once subject.upload end it "validates the dependencies are installed" do expect(subject).to receive(:validate_dependencies_installed!).once subject.upload end it "creates a new Uploader" do expect(Berkshelf::Uploader).to receive(:new).with(subject) expect(uploader).to receive(:run) subject.upload end end describe "#vendor" do let(:cached_cookbook) { instance_double(Berkshelf::CachedCookbook, cookbook_name: "my_cookbook", path: "/my_cookbook/path", compile_metadata: true, reload: nil) } let(:installer) { instance_double(Berkshelf::Installer, run: [cached_cookbook]) } let(:raw_metadata_files) { [File.join(cached_cookbook.cookbook_name, "metadata.rb")] } let(:destination) { "/a/destination/path" } let(:options) { { exclude: Berkshelf::Berksfile::EXCLUDED_VCS_FILES_WHEN_VENDORING, delete: nil } } before do allow(Berkshelf::Installer).to receive(:new).and_return(installer) end it "invokes FileSyncer with correct arguments" do expect(Berkshelf::FileSyncer).to receive(:sync).with(/vendor/, destination, options) subject.vendor(destination) end it "excludes the top-level metadata.rb file" do expect(options[:exclude].any? { |exclude| File.fnmatch?(exclude, "my_cookbook/recipes/metadata.rb", File::FNM_DOTMATCH) }).to be(false) end end describe "#solver" do let(:solver_precedence) { :please_dont_exist } it "defaults to nil required solver and :gecode preferred solver" do expect(subject.preferred_solver).to equal(:gecode) expect(subject.required_solver).to equal(nil) end it "adds preferred and required solvers" do subject.solver(:a, :preferred) subject.solver(:b, :required) expect(subject.preferred_solver).to equal(:a) expect(subject.required_solver).to equal(:b) end it "raises an exception with a bad precedence" do expect { subject.solver(:foo, :bar) }.to raise_error(/Invalid solver precedence ':bar'/) end it "is a DSL method" do expect(subject).to have_exposed_method(:solver) end end end