# # Author:: Adam Jacob () # Copyright:: Copyright (c) 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/search/query" describe Chef::Search::Query do let(:rest) { double("Chef::ServerAPI") } let(:query) { Chef::Search::Query.new } let(:default_rows) { 1000 } shared_context "filtered search" do let(:query_string) { "search/node?q=platform:rhel&start=0&rows=#{default_rows}" } let(:server_url) { "https://api.opscode.com/organizations/opscode/nodes" } let(:args) { { filter_key => filter_hash } } let(:filter_hash) do { "env" => [ "chef_environment" ], "ruby_plat" => %w{languages ruby platform}, } end let(:response) do { "rows" => [ { "url" => "#{server_url}/my-name-is-node", "data" => { "env" => "elysium", "ruby_plat" => "nudibranch", }, }, { "url" => "#{server_url}/my-name-is-jonas", "data" => { "env" => "hades", "ruby_plat" => "i386-mingw32", }, }, { "url" => "#{server_url}/my-name-is-flipper", "data" => { "env" => "elysium", "ruby_plat" => "centos", }, }, { "url" => "#{server_url}/my-name-is-butters", "data" => { "env" => "moon", "ruby_plat" => "solaris2", }, }, ], "start" => 0, "total" => 4, } end let(:response_rows) do [ { "env" => "elysium", "ruby_plat" => "nudibranch" }, { "env" => "hades", "ruby_plat" => "i386-mingw32" }, { "env" => "elysium", "ruby_plat" => "centos" }, { "env" => "moon", "ruby_plat" => "solaris2" }, ] end end before(:each) do allow(Chef::ServerAPI).to receive(:new).and_return(rest) allow(rest).to receive(:get).and_return(response) end describe "search" do let(:query_string) { "search/node?q=platform:rhel&start=0&rows=#{default_rows}" } let(:query_string_continue) { "search/node?q=platform:rhel&start=4&rows=#{default_rows}" } let(:query_string_with_rows) { "search/node?q=platform:rhel&start=0&rows=4" } let(:query_string_continue_with_rows) { "search/node?q=platform:rhel&start=4&rows=4" } let(:response) do { "rows" => [ { "name" => "my-name-is-node", "chef_environment" => "elysium", "platform" => "rhel", "run_list" => [], "automatic" => { "languages" => { "ruby" => { "platform" => "nudibranch", "version" => "1.9.3", "target" => "ming-the-merciless", }, }, }, }, { "name" => "my-name-is-jonas", "chef_environment" => "hades", "platform" => "rhel", "run_list" => [], "automatic" => { "languages" => { "ruby" => { "platform" => "i386-mingw32", "version" => "1.9.3", "target" => "bilbo", }, }, }, }, { "name" => "my-name-is-flipper", "chef_environment" => "elysium", "platform" => "rhel", "run_list" => [], "automatic" => { "languages" => { "ruby" => { "platform" => "centos", "version" => "2.0.0", "target" => "uno", }, }, }, }, { "name" => "my-name-is-butters", "chef_environment" => "moon", "platform" => "rhel", "run_list" => [], "automatic" => { "languages" => { "ruby" => { "platform" => "solaris2", "version" => "2.1.2", "target" => "random", }, }, }, }, ], "start" => 0, "total" => 4, } end let(:big_response_empty) do { "start" => 0, "total" => 8, "rows" => [], } end let(:big_response_end) do r = response.dup r["start"] = 4 r["total"] = 8 r end it "accepts a type as the first argument" do expect { query.search("node") }.not_to raise_error expect { query.search(:node) }.not_to raise_error expect { query.search({}) }.to raise_error(Chef::Exceptions::InvalidSearchQuery, /(Hash)/) end it "queries for every object of a type by default" do expect(rest).to receive(:get).with("search/node?q=*:*&start=0&rows=#{default_rows}").and_return(response) query.search(:node) end it "allows a custom query" do expect(rest).to receive(:get).with("search/node?q=platform:rhel&start=0&rows=#{default_rows}").and_return(response) query.search(:node, "platform:rhel") end it "lets you set a starting object" do expect(rest).to receive(:get).with("search/node?q=platform:rhel&start=2&rows=#{default_rows}").and_return(response) query.search(:node, "platform:rhel", start: 2) end it "lets you set how many rows to return" do expect(rest).to receive(:get).with("search/node?q=platform:rhel&start=0&rows=40").and_return(response) query.search(:node, "platform:rhel", rows: 40) end it "throws an exception if you pass an incorrect option" do expect { query.search(:node, "platform:rhel", total: 10) } .to raise_error(ArgumentError, /unknown keyword: :?total/) end it "returns the raw rows, start, and total if no block is passed" do rows, start, total = query.search(:node) expect(rows).to equal(response["rows"]) expect(start).to equal(response["start"]) expect(total).to equal(response["total"]) end it "calls a block for each object in the response" do @call_me = double("blocky") response["rows"].each { |r| expect(@call_me).to receive(:do).with(Chef::Node.from_hash(r)) } query.search(:node) { |r| @call_me.do(r) } end it "pages through the responses" do @call_me = double("blocky") response["rows"].each { |r| expect(@call_me).to receive(:do).with(Chef::Node.from_hash(r)) } query.search(:node, "*:*", start: 0, rows: 4) { |r| @call_me.do(r) } end # This test would loop infinitely if pagination didn't advance it "paginates correctly in the face of filtered nodes without explicit rows" do allow(rest).to receive(:get).with(query_string).and_return(big_response_empty) query.search(:node, "platform:rhel") do |r| nil end end it "paginates correctly in the face of filtered nodes" do expect(rest).to receive(:get).with(query_string_with_rows).and_return(big_response_empty) expect(rest).to receive(:get).with(query_string_continue_with_rows).and_return(big_response_end) query.search(:node, "platform:rhel", rows: 4) do |r| nil end end it "fuzzifies node searches when fuzz is set and type is a symbol" do expect(rest).to receive(:get).with( "search/node?q=tags:*free.messi*%20OR%20roles:*free.messi*%20OR%20fqdn:*free.messi*%20OR%20addresses:*free.messi*%20OR%20policy_name:*free.messi*%20OR%20policy_group:*free.messi*&start=0&rows=#{default_rows}" ).and_return(response) query.search(:node, "free.messi", fuzz: true) end it "fuzzifies node searches when fuzz is set and type is a string" do expect(rest).to receive(:get).with( "search/node?q=tags:*free.messi*%20OR%20roles:*free.messi*%20OR%20fqdn:*free.messi*%20OR%20addresses:*free.messi*%20OR%20policy_name:*free.messi*%20OR%20policy_group:*free.messi*&start=0&rows=#{default_rows}" ).and_return(response) query.search("node", "free.messi", fuzz: true) end it "does not fuzzify node searches when fuzz is not set" do expect(rest).to receive(:get).with( "search/node?q=free.messi&start=0&rows=#{default_rows}" ).and_return(response) query.search(:node, "free.messi") end it "does not fuzzify client searches" do expect(rest).to receive(:get).with( "search/client?q=messi&start=0&rows=#{default_rows}" ).and_return(response) query.search(:client, "messi", fuzz: true) end context "when :filter_result is provided as a result" do include_context "filtered search" do let(:filter_key) { :filter_result } before(:each) do expect(rest).to receive(:post).with(query_string, args[filter_key]).and_return(response) end it "returns start" do start = query.search(:node, "platform:rhel", args)[1] expect(start).to eq(response["start"]) end it "returns total" do total = query.search(:node, "platform:rhel", args)[2] expect(total).to eq(response["total"]) end it "returns rows with the filter applied" do filtered_rows = query.search(:node, "platform:rhel", args)[0] expect(filtered_rows).to match_array(response_rows) end end end end end