require 'spec_helper' require 'hiera/util' class Hiera module Backend class Backend1x_backend def lookup(key, scope, order_override, resolution_type) ["a", "b"] end end end describe Backend do describe "#datadir" do it "interpolates any values in the configured value" do Config.load({:rspec => {:datadir => "/tmp/%{interpolate}"}}) dir = Backend.datadir(:rspec, { "interpolate" => "my_data" }) expect(dir).to eq("/tmp/my_data") end it "defaults to a directory in var" do Config.load({}) expect(Backend.datadir(:rspec, { "environment" => "foo" })).to eq(Hiera::Util.var_dir % { :environment => "foo"}) Config.load({:rspec => nil}) expect(Backend.datadir(:rspec, { "environment" => "foo" })).to eq(Hiera::Util.var_dir % { :environment => "foo"}) Config.load({:rspec => {}}) expect(Backend.datadir(:rspec, { "environment" => "foo" })).to eq(Hiera::Util.var_dir % { :environment => "foo"}) end it "fails when the datadir is an array" do Config.load({:rspec => {:datadir => []}}) expect do Backend.datadir(:rspec, {}) end.to raise_error(Hiera::InvalidConfigurationError, /datadir for rspec cannot be an array/) end end describe "#datafile" do it "translates a non-existant datafile into nil" do Hiera.expects(:debug).with("Cannot find datafile /nonexisting/test.yaml, skipping") Backend.expects(:datadir).returns("/nonexisting") expect(Backend.datafile(:yaml, {}, "test", "yaml")).to eq(nil) end it "concatenates the datadir and datafile and format to produce the full datafile filename" do Backend.expects(:datadir).returns("/nonexisting") File.expects(:exist?).with("/nonexisting/test.yaml").returns(true) expect(Backend.datafile(:yaml, {}, "test", "yaml")).to eq("/nonexisting/test.yaml") end end describe "#datasources" do it "iterates over the datasources in the order of the given hierarchy" do expected = ["one", "two"] Backend.datasources({}, nil, ["one", "two"]) do |backend| expect(backend).to eq(expected.delete_at(0)) end expect(expected.empty?).to eq(true) end it "uses the configured hierarchy no specific hierarchy is given" do Config.load(:hierarchy => "test") Backend.datasources({}) do |backend| expect(backend).to eq("test") end end it "defaults to a hierarchy of only 'common' if not configured or given" do Config.load({}) Backend.datasources({}) do |backend| expect(backend).to eq("common") end end it "prefixes the hierarchy with the override if an override is provided" do Config.load({}) expected = ["override", "common"] Backend.datasources({}, "override") do |backend| expect(backend).to eq(expected.delete_at(0)) end expect(expected.empty?).to eq(true) end it "parses the names of the hierarchy levels using the given scope" do Backend.expects(:parse_string).with('nodes/%{::trusted.certname}', {:rspec => :tests}, {}, {:order_override => nil}) Backend.expects(:parse_string).with('common', {:rspec => :tests}, {}, {:order_override => nil}) Backend.datasources({:rspec => :tests}) { } end it "defaults to 'common' if the hierarchy contains no hierarchies with non-empty names" do Config.load({}) expected = ["common"] Backend.datasources({}, "%{rspec}") do |backend| expect(backend).to eq(expected.delete_at(0)) end expect(expected.empty?).to eq(true) end end describe "#parse_string" do it "passes nil through untouched" do expect(Backend.parse_string(nil, {})).to eq(nil) end it "does not modify the input data" do data = "%{value}" Backend.parse_string(data, { "value" => "replacement" }) expect(data).to eq("%{value}") end it "passes non-string data through untouched" do input = { "not a" => "string" } expect(Backend.parse_string(input, {})).to eq(input) end @scope_interpolation_tests = { "replace %{part1} and %{part2}" => "replace value of part1 and value of part2", "replace %{scope('part1')} and %{scope('part2')}" => "replace value of part1 and value of part2" } @scope_interpolation_tests.each do |input, expected| it "replaces interpolations with data looked up in the scope" do scope = {"part1" => "value of part1", "part2" => "value of part2"} expect(Backend.parse_string(input, scope)).to eq(expected) end end it "replaces interpolations with data looked up in extra_data when scope does not contain the value" do input = "test_%{rspec}_test" expect(Backend.parse_string(input, {}, {"rspec" => "extra"})).to eq("test_extra_test") end it "prefers data from scope over data from extra_data" do input = "test_%{rspec}_test" expect(Backend.parse_string(input, {"rspec" => "test"}, {"rspec" => "fail"})).to eq("test_test_test") end @interprets_nil_in_scope_tests = { "test_%{rspec}_test" => "test__test", "test_%{scope('rspec')}_test" => "test__test" } @interprets_nil_in_scope_tests.each do |input, expected| it "interprets nil in scope as a non-value" do expect(Backend.parse_string(input, {"rspec" => nil})).to eq(expected) end end @interprets_false_in_scope_tests = { "test_%{rspec}_test" => "test_false_test", "test_%{scope('rspec')}_test" => "test_false_test" } @interprets_false_in_scope_tests.each do |input, expected| it "interprets false in scope as a real value" do input = "test_%{scope('rspec')}_test" expect(Backend.parse_string(input, {"rspec" => false})).to eq(expected) end end it "interprets false in extra_data as a real value" do input = "test_%{rspec}_test" expect(Backend.parse_string(input, {}, {"rspec" => false})).to eq("test_false_test") end it "interprets nil in extra_data as a non-value" do input = "test_%{rspec}_test" expect(Backend.parse_string(input, {}, {"rspec" => nil})).to eq("test__test") end @interprets_undefined_in_scope_tests = { "test_%{rspec}_test" => "test__test", "test_%{scope('rspec')}_test" => "test__test" } @exact_lookup_tests = { "test_%{::rspec::data}_test" => "test_value_test", "test_%{scope('::rspec::data')}_test" => "test_value_test" } @exact_lookup_tests.each do |input, expected| it "looks up the interpolated value exactly as it appears in the input" do expect(Backend.parse_string(input, {"::rspec::data" => "value"})).to eq(expected) end end @surrounding_whitespace_tests = { "test_%{\trspec::data }_test" => "test_value_test", "test_%{scope('\trspec::data ')}_test" => "test_value_test" } @surrounding_whitespace_tests.each do |input, expected| it "does not remove any surrounding whitespace when parsing the key to lookup" do expect(Backend.parse_string(input, {"\trspec::data " => "value"})).to eq(expected) end end @leading_double_colon_tests = { "test_%{::rspec::data}_test" => "test__test", "test_%{scope('::rspec::data')}_test" => "test__test" } @leading_double_colon_tests.each do |input, expected| it "does not try removing leading :: when a full lookup fails (#17434)" do expect(Backend.parse_string(input, {"rspec::data" => "value"})).to eq(expected) end end @double_colon_key_tests = { "test_%{::rspec::data}_test" => "test__test", "test_%{scope('::rspec::data')}_test" => "test__test" } @double_colon_key_tests.each do |input, expected| it "does not try removing leading sections separated by :: when a full lookup fails (#17434)" do expect(Backend.parse_string(input, {"data" => "value"})).to eq(expected) end end it "does not try removing unknown, preceeding characters when looking up values" do input = "test_%{$var}_test" expect(Backend.parse_string(input, {"$var" => "value"})).to eq("test_value_test") end it "looks up recursively" do scope = {"rspec" => "%{first}", "first" => "%{last}", "last" => "final"} input = "test_%{rspec}_test" expect(Backend.parse_string(input, scope)).to eq("test_final_test") end it "raises an error if the recursive lookup results in an infinite loop" do scope = {"first" => "%{second}", "second" => "%{first}"} input = "test_%{first}_test" expect do Backend.parse_string(input, scope) end.to raise_error Hiera::InterpolationLoop, "Detected in [first, second]" end it "replaces repeated occurances of the same lookup" do scope = {"rspec" => "value"} input = "it replaces %{rspec} and %{rspec}" expect(Backend.parse_string(input, scope)).to eq("it replaces value and value") end it "replaces hiera interpolations with data looked up in hiera" do input = "%{hiera('key1')}" scope = {} Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.stubs(:lookup).with("key1", scope, nil, :priority, instance_of(Hash)).returns("answer") expect(Backend.parse_string(input, scope)).to eq("answer") end it "interpolation passes the order_override back into the backend" do Backend.expects(:lookup).with("lookup::key", nil, {}, "order_override_datasource", :priority, instance_of(Hash)) Backend.parse_string("%{hiera('lookup::key')}", {}, {}, {:order_override => "order_override_datasource"}) end it "replaces literal interpolations with their argument" do scope = {} input = "%{literal('%')}{rspec::data}" expect(Backend.parse_string(input, scope)).to eq("%{rspec::data}") end end describe "#parse_answer" do it "interpolates values in strings" do input = "test_%{rspec}_test" expect(Backend.parse_answer(input, {"rspec" => "test"})).to eq("test_test_test") end it "interpolates each string in an array" do input = ["test_%{rspec}_test", "test_%{rspec}_test", ["test_%{rspec}_test"]] expect(Backend.parse_answer(input, {"rspec" => "test"})).to eq(["test_test_test", "test_test_test", ["test_test_test"]]) end it "interpolates each string in a hash" do input = {"foo" => "test_%{rspec}_test", "bar" => "test_%{rspec}_test"} expect(Backend.parse_answer(input, {"rspec" => "test"})).to eq({"foo"=>"test_test_test", "bar"=>"test_test_test"}) end it "interpolates string in hash keys" do input = {"%{rspec}" => "test"} expect(Backend.parse_answer(input, {"rspec" => "foo"})).to eq({"foo"=>"test"}) end it "interpolates strings in nested hash keys" do input = {"topkey" => {"%{rspec}" => "test"}} expect(Backend.parse_answer(input, {"rspec" => "foo"})).to eq({"topkey"=>{"foo" => "test"}}) end it "interpolates strings in a mixed structure of arrays and hashes" do input = {"foo" => "test_%{rspec}_test", "bar" => ["test_%{rspec}_test", "test_%{rspec}_test"]} expect(Backend.parse_answer(input, {"rspec" => "test"})).to eq({"foo"=>"test_test_test", "bar"=>["test_test_test", "test_test_test"]}) end it "interpolates hiera lookups values in strings" do input = "test_%{hiera('rspec')}_test" scope = {} Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("test") expect(Backend.parse_answer(input, scope)).to eq("test_test_test") end it "interpolates alias lookups with non-string types" do input = "%{alias('rspec')}" scope = {} Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns(['test', 'test']) expect(Backend.parse_answer(input, scope)).to eq(['test', 'test']) end it 'fails if alias interpolation is attempted in a string context with a prefix' do input = "stuff_before%{alias('rspec')}" scope = {} Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns(['test', 'test']) expect do expect(Backend.parse_answer(input, scope)).to eq(['test', 'test']) end.to raise_error(Hiera::InterpolationInvalidValue, 'Cannot call alias in the string context') end it 'fails if alias interpolation is attempted in a string context with a postfix' do input = "%{alias('rspec')}_stiff after" scope = {} Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns(['test', 'test']) expect do expect(Backend.parse_answer(input, scope)).to eq(['test', 'test']) end.to raise_error(Hiera::InterpolationInvalidValue, 'Cannot call alias in the string context') end it "interpolates hiera lookups in each string in an array" do input = ["test_%{hiera('rspec')}_test", "test_%{hiera('rspec')}_test", ["test_%{hiera('rspec')}_test"]] scope = {} Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("test") expect(Backend.parse_answer(input, scope)).to eq(["test_test_test", "test_test_test", ["test_test_test"]]) end it "interpolates hiera lookups in each string in a hash" do input = {"foo" => "test_%{hiera('rspec')}_test", "bar" => "test_%{hiera('rspec')}_test"} scope = {} Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("test") expect(Backend.parse_answer(input, scope)).to eq({"foo"=>"test_test_test", "bar"=>"test_test_test"}) end it "interpolates hiera lookups in string in hash keys" do input = {"%{hiera('rspec')}" => "test"} scope = {} Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("foo") expect(Backend.parse_answer(input, scope)).to eq({"foo"=>"test"}) end it "interpolates hiera lookups in strings in nested hash keys" do input = {"topkey" => {"%{hiera('rspec')}" => "test"}} scope = {} Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("foo") expect(Backend.parse_answer(input, scope)).to eq({"topkey"=>{"foo" => "test"}}) end it "interpolates hiera lookups in strings in a mixed structure of arrays and hashes" do input = {"foo" => "test_%{hiera('rspec')}_test", "bar" => ["test_%{hiera('rspec')}_test", "test_%{hiera('rspec')}_test"]} scope = {} Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("test") expect(Backend.parse_answer(input, scope)).to eq({"foo"=>"test_test_test", "bar"=>["test_test_test", "test_test_test"]}) end it "interpolates hiera lookups and scope lookups in the same string" do input = {"foo" => "test_%{hiera('rspec')}_test", "bar" => "test_%{rspec2}_test"} scope = {"rspec2" => "scope_rspec"} Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("hiera_rspec") expect(Backend.parse_answer(input, scope)).to eq({"foo"=>"test_hiera_rspec_test", "bar"=>"test_scope_rspec_test"}) end it "interpolates hiera and scope lookups with the same lookup query in a single string" do input = "test_%{hiera('rspec')}_test_%{rspec}" scope = {"rspec" => "scope_rspec"} Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority, instance_of(Hash)).returns("hiera_rspec") expect(Backend.parse_answer(input, scope)).to eq("test_hiera_rspec_test_scope_rspec") end it "passes integers unchanged" do input = 1 expect(Backend.parse_answer(input, {"rspec" => "test"})).to eq(1) end it "passes floats unchanged" do input = 0.233 expect(Backend.parse_answer(input, {"rspec" => "test"})).to eq(0.233) end it "passes the boolean true unchanged" do input = true expect(Backend.parse_answer(input, {"rspec" => "test"})).to eq(true) end it "passes the boolean false unchanged" do input = false expect(Backend.parse_answer(input, {"rspec" => "test"})).to eq(false) end it "interpolates lookups using single or double quotes" do input = "test_%{scope(\"rspec\")}_test_%{scope('rspec')}" scope = {"rspec" => "scope_rspec"} expect(Backend.parse_answer(input, scope)).to eq("test_scope_rspec_test_scope_rspec") end end describe "#resolve_answer" do it "flattens and removes duplicate values from arrays during an array lookup" do expect(Backend.resolve_answer(["foo", ["foo", "foo"], "bar"], :array)).to eq(["foo", "bar"]) end it "returns the data unchanged during a priority lookup" do expect(Backend.resolve_answer(["foo", ["foo", "foo"], "bar"], :priority)).to eq(["foo", ["foo", "foo"], "bar"]) end end describe "#lookup" do before do Hiera.stubs(:debug) Hiera.stubs(:warn) end it "caches loaded backends" do Backend.clear! Hiera.expects(:debug).with(regexp_matches(/Hiera YAML backend starting/)).once Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend.lookup("key", "default", {}, nil, nil) Backend.lookup("key", "default", {}, nil, nil) end it "returns the answer from the backend" do Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, nil, instance_of(Hash)).returns("answer") expect(Backend.lookup("key", "default", {}, nil, nil)).to eq("answer") end it "retains the datatypes as returned by the backend" do Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with("stringval", {}, nil, nil, instance_of(Hash)).returns("string") Backend::Yaml_backend.any_instance.expects(:lookup).with("boolval", {}, nil, nil, instance_of(Hash)).returns(false) Backend::Yaml_backend.any_instance.expects(:lookup).with("numericval", {}, nil, nil, instance_of(Hash)).returns(1) expect(Backend.lookup("stringval", "default", {}, nil, nil)).to eq("string") expect(Backend.lookup("boolval", "default", {}, nil, nil)).to eq(false) expect(Backend.lookup("numericval", "default", {}, nil, nil)).to eq(1) end it "calls to all backends till an answer is found" do backend = mock backend.expects(:lookup).returns("answer") Config.load({}) Config.instance_variable_set("@config", {:backends => ["yaml", "rspec"]}) Backend.instance_variable_set("@backends", {"rspec" => backend}) #Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {"rspec" => "test"}, nil, nil) Backend.expects(:constants).returns(["Yaml_backend", "Rspec_backend"]).twice expect(Backend.lookup("key", "test_%{rspec}", {"rspec" => "test"}, nil, nil)).to eq("answer") end it "calls to all backends till an answer is found when doing array lookups" do backend = mock backend.expects(:lookup).returns(["answer"]) Config.load({}) Config.instance_variable_set("@config", {:backends => ["yaml", "rspec"]}) Backend.instance_variable_set("@backends", {"rspec" => backend}) Backend.expects(:constants).returns(["Yaml_backend", "Rspec_backend"]).twice expect(Backend.lookup("key", "notfound", {"rspec" => "test"}, nil, :array)).to eq(["answer"]) end it "calls to all backends till an answer is found when doing hash lookups" do thehash = {:answer => "value"} backend = mock backend.expects(:lookup).returns(thehash) Config.load({}) Config.instance_variable_set("@config", {:backends => ["yaml", "rspec"]}) Backend.instance_variable_set("@backends", {"rspec" => backend}) Backend.expects(:constants).returns(["Yaml_backend", "Rspec_backend"]).twice expect(Backend.lookup("key", "notfound", {"rspec" => "test"}, nil, :hash)).to eq(thehash) end it "builds a merged hash from all backends for hash searches" do backend1 = mock :lookup => {"a" => "answer"} backend2 = mock :lookup => {"b" => "bnswer"} Config.load({}) Config.instance_variable_set("@config", {:backends => ["first", "second"]}) Backend.instance_variable_set("@backends", {"first" => backend1, "second" => backend2}) Backend.stubs(:constants).returns(["First_backend", "Second_backend"]) expect(Backend.lookup("key", {}, {"rspec" => "test"}, nil, :hash)).to eq({"a" => "answer", "b" => "bnswer"}) end it "builds an array from all backends for array searches" do backend1 = mock :lookup => ["a", "b"] backend2 = mock :lookup => ["c", "d"] Config.load({}) Config.instance_variable_set("@config", {:backends => ["first", "second"]}) Backend.instance_variable_set("@backends", {"first" => backend1, "second" => backend2}) Backend.stubs(:constants).returns(["First_backend", "Second_backend"]) expect(Backend.lookup("key", {}, {"rspec" => "test"}, nil, :array)).to eq(["a", "b", "c", "d"]) end it "uses the earliest backend result for priority searches" do backend1 = mock backend1.stubs(:lookup).returns(["a", "b"]) backend2 = mock backend2.stubs(:lookup).returns(["c", "d"]) Config.load({}) Config.instance_variable_set("@config", {:backends => ["first", "second"]}) Backend.instance_variable_set("@backends", {"first" => backend1, "second" => backend2}) Backend.stubs(:constants).returns(["First_backend", "Second_backend"]) expect(Backend.lookup("key", {}, {"rspec" => "test"}, nil, :priority)).to eq(["a", "b"]) end it "parses the answers based on resolution_type" do Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend.expects(:resolve_answer).with("test_test", :priority).returns("parsed") Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {"rspec" => "test"}, nil, :priority, instance_of(Hash)).returns("test_test") expect(Backend.lookup("key", "test_%{rspec}", {"rspec" => "test"}, nil, :priority)).to eq("parsed") end it "returns the default with variables parsed if nothing is found" do Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {"rspec" => "test"}, nil, nil, instance_of(Hash)).throws(:no_such_key) expect(Backend.lookup("key", "test_%{rspec}", {"rspec" => "test"}, nil, nil)).to eq("test_test") end it "returns nil instead of the default when key is found with a nil value" do Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {"rspec" => "test"}, nil, nil, instance_of(Hash)).returns(nil) expect(Backend.lookup("key", "test_%{rspec}", {"rspec" => "test"}, nil, nil)).to eq(nil) end it "keeps string default data as a string" do Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, nil, instance_of(Hash)).throws(:no_such_key) expect(Backend.lookup("key", "test", {}, nil, nil)).to eq("test") end it "keeps array default data as an array" do Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, :array, instance_of(Hash)).throws(:no_such_key) expect(Backend.lookup("key", ["test"], {}, nil, :array)).to eq(["test"]) end it "keeps hash default data as a hash" do Config.load({:yaml => {:datadir => "/tmp"}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, :hash, instance_of(Hash)).throws(:no_such_key) expect(Backend.lookup("key", {"test" => "value"}, {}, nil, :hash)).to eq({"test" => "value"}) end it 'can use qualified key to lookup value in hash' do Config.load({:yaml => {:datadir => '/tmp'}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, nil, instance_of(Hash)).returns({ 'test' => 'value'}) expect(Backend.lookup('key.test', 'dflt', {}, nil, nil)).to eq('value') end it 'can use qualified key to lookup value in array' do Config.load({:yaml => {:datadir => '/tmp'}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, nil, instance_of(Hash)).returns([ 'first', 'second']) expect(Backend.lookup('key.1', 'dflt', {}, nil, nil)).to eq('second') end it 'will fail when qualified key is partially found but not expected hash' do Config.load({:yaml => {:datadir => '/tmp'}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, nil, instance_of(Hash)).returns(['value 1', 'value 2']) expect do Backend.lookup('key.test', 'dflt', {}, nil, nil) end.to raise_error(Exception, /^Hiera type mismatch:/) end it 'will fail when qualified key used with resolution_type :hash' do expect do Backend.lookup('key.test', 'dflt', {}, nil, :hash) end.to raise_error(ArgumentError, /^Resolution type :hash is illegal/) end it 'will fail when qualified key used with resolution_type :array' do expect do Backend.lookup('key.test', 'dflt', {}, nil, :array) end.to raise_error(ArgumentError, /^Resolution type :array is illegal/) end it 'will succeed when qualified key used with resolution_type :priority' do Config.load({:yaml => {:datadir => '/tmp'}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, :priority, instance_of(Hash)).returns({ 'test' => 'value'}) expect(Backend.lookup('key.test', 'dflt', {}, nil, :priority)).to eq('value') end it 'will fail when qualified key is partially found but not expected array' do Config.load({:yaml => {:datadir => '/tmp'}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, nil, instance_of(Hash)).returns({ 'test' => 'value'}) expect do Backend.lookup('key.2', 'dflt', {}, nil, nil) end.to raise_error(Exception, /^Hiera type mismatch:/) end it 'will not fail when qualified key is partially not found' do Config.load({:yaml => {:datadir => '/tmp'}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, nil, instance_of(Hash)).returns(nil) expect(Backend.lookup('key.test', 'dflt', {}, nil, nil)).to eq('dflt') end it 'will not fail when qualified key is array index out of bounds' do Config.load({:yaml => {:datadir => '/tmp'}}) Config.load_backends Backend::Yaml_backend.any_instance.expects(:lookup).with('key', {}, nil, nil, instance_of(Hash)).returns(['value 1', 'value 2']) expect(Backend.lookup('key.33', 'dflt', {}, nil, nil)).to eq('dflt') end it 'can use qualified key in interpolation to lookup value in hash' do Config.load({:yaml => {:datadir => '/tmp'}}) Config.load_backends Hiera::Backend.stubs(:datasourcefiles).yields('foo', 'bar') Hiera::Filecache.any_instance.expects(:read_file).at_most(2).returns({'key' => '%{hiera(\'some.subkey\')}', 'some' => { 'subkey' => 'value' }}) expect(Backend.lookup('key', 'dflt', {}, nil, nil)).to eq('value') end it 'can use qualified key in interpolated default and scope' do Config.load({:yaml => {:datadir => '/tmp'}}) Config.load_backends scope = { 'some' => { 'test' => 'value'}} Backend::Yaml_backend.any_instance.expects(:lookup).with('key', scope, nil, nil, instance_of(Hash)) expect(Backend.lookup('key.notfound', '%{some.test}', scope, nil, nil)).to eq('value') end it "handles older backend with 4 argument lookup" do Config.load({}) Config.instance_variable_set("@config", {:backends => ["Backend1x"]}) Hiera.expects(:debug).at_least_once.with(regexp_matches /Using Hiera 1.x backend/) expect(Backend.lookup("key", {}, {"rspec" => "test"}, nil, :priority)).to eq(["a", "b"]) end end describe '#merge_answer' do before do Hiera.stubs(:debug) Hiera.stubs(:warn) Config.stubs(:validate!) end it "uses Hash.merge when configured with :merge_behavior => :native" do Config.load({:merge_behavior => :native}) Hash.any_instance.expects(:merge).with({"b" => "bnswer"}).returns({"a" => "answer", "b" => "bnswer"}) expect(Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"})).to eq({"a" => "answer", "b" => "bnswer"}) end it "uses deep_merge! when configured with :merge_behavior => :deeper" do Config.load({:merge_behavior => :deeper}) Hash.any_instance.expects('deep_merge!').with({"b" => "bnswer"}, {}).returns({"a" => "answer", "b" => "bnswer"}) expect(Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"})).to eq({"a" => "answer", "b" => "bnswer"}) end it "uses deep_merge when configured with :merge_behavior => :deep" do Config.load({:merge_behavior => :deep}) Hash.any_instance.expects('deep_merge').with({"b" => "bnswer"}, {}).returns({"a" => "answer", "b" => "bnswer"}) expect(Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"})).to eq({"a" => "answer", "b" => "bnswer"}) end it "disregards configuration when 'merge' parameter is given as a Hash" do Config.load({:merge_behavior => :deep}) Hash.any_instance.expects('deep_merge!').with({"b" => "bnswer"}, {}).returns({"a" => "answer", "b" => "bnswer"}) expect(Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"}, {:behavior => 'deeper' })).to eq({"a" => "answer", "b" => "bnswer"}) end it "propagates deep merge options when given Hash 'merge' parameter" do Hash.any_instance.expects('deep_merge!').with({"b" => "bnswer"}, { :knockout_prefix => '-' }).returns({"a" => "answer", "b" => "bnswer"}) expect(Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"}, {:behavior => 'deeper', :knockout_prefix => '-'})).to eq({"a" => "answer", "b" => "bnswer"}) end it "passes Config[:deep_merge_options] into calls to deep_merge" do Config.load({:merge_behavior => :deep, :deep_merge_options => { :knockout_prefix => '-' } }) Hash.any_instance.expects('deep_merge').with({"b" => "bnswer"}, {:knockout_prefix => '-'}).returns({"a" => "answer", "b" => "bnswer"}) expect(Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"})).to eq({"a" => "answer", "b" => "bnswer"}) end end end end