# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2018, 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 "cgi" describe Chef::Mixin::Template, "render_template" do let(:sep) { ChefUtils.windows? ? "\r\n" : "\n" } before :each do @context = Chef::Mixin::Template::TemplateContext.new({}) end it "should render the template evaluated in the given context" do @context[:foo] = "bar" output = @context.render_template_from_string("<%= @foo %>") expect(output).to eq("bar") end template_contents = [ "Fancy\r\nTemplate\r\n\r\n", "Fancy\nTemplate\n\n", "Fancy\r\nTemplate\n\r\n"] describe "when running on windows" do before do allow(ChefUtils).to receive(:windows?).and_return(true) end it "should render the templates with windows line endings" do template_contents.each do |template_content| output = @context.render_template_from_string(template_content) output.each_line do |line| expect(line).to end_with("\r\n") end end end end describe "when running on unix" do before do allow(ChefUtils).to receive(:windows?).and_return(false) end it "should render the templates with unix line endings" do template_contents.each do |template_content| output = @context.render_template_from_string(template_content) output.each_line do |line| expect(line).to end_with("\n") end end end end it "should provide a node method to access @node" do @context[:node] = "tehShizzle" output = @context.render_template_from_string("<%= @node %>") expect(output).to eq("tehShizzle") end describe "with a template resource" do before :each do @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) Chef::Cookbook::FileVendor.fetch_from_disk(@cookbook_repo) @node = Chef::Node.new cl = Chef::CookbookLoader.new(@cookbook_repo) cl.load_cookbooks @cookbook_collection = Chef::CookbookCollection.new(cl) @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events) @rendered_file_location = Dir.tmpdir + "/openldap_stuff.conf" @resource = Chef::Resource::Template.new(@rendered_file_location) @resource.cookbook_name = "openldap" @current_resource = @resource.dup @content_provider = Chef::Provider::Template::Content.new(@resource, @current_resource, @run_context) @template_context = Chef::Mixin::Template::TemplateContext.new({}) @template_context[:node] = @node @template_context[:template_finder] = Chef::Provider::TemplateFinder.new(@run_context, @resource.cookbook_name, @node) end it "should provide a render method" do output = @template_context.render_template_from_string("before {<%= render('test.erb').strip -%>} after") expect(output).to eq("before {We could be diving for pearls!} after") end it "should render local files" do begin tf = Tempfile.new("partial") tf.write "test" tf.rewind output = @template_context.render_template_from_string("before {<%= render '#{tf.path}', :local => true %>} after") expect(output).to eq("before {test} after") ensure tf.close end end it "should render partials from a different cookbook" do @template_context[:template_finder] = Chef::Provider::TemplateFinder.new(@run_context, "apache2", @node) output = @template_context.render_template_from_string("before {<%= render('test.erb', :cookbook => 'openldap').strip %>} after") expect(output).to eq("before {We could be diving for pearls!} after") end it "should render using the source argument if provided" do begin tf = Tempfile.new("partial") tf.write "test" tf.rewind output = @template_context.render_template_from_string("before {<%= render 'something', :local => true, :source => '#{tf.path}' %>} after") expect(output).to eq("before {test} after") ensure tf.close end end it "should pass the node to partials" do @node.normal[:slappiness] = "happiness" output = @template_context.render_template_from_string("before {<%= render 'openldap_stuff.conf.erb' %>} after") expect(output).to eq("before {slappiness is happiness} after") end it "should pass the original variables to partials" do @template_context[:secret] = "candy" output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb' %>} after") output == "before {super secret is candy} after" end it "should pass the template finder to the partials" do output = @template_context.render_template_from_string("before {<%= render 'nested_openldap_partials.erb', :variables => {:hello => 'Hello World!' } %>} after") output == "before {Hello World!} after" end it "should pass variables to partials" do output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb', :variables => {:secret => 'whatever' } %>} after") expect(output).to eq("before {super secret is whatever} after") end it "should pass variables to partials even if they are named the same" do @template_context[:secret] = "one" output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb', :variables => {:secret => 'two' } %>} after <%= @secret %>") expect(output).to eq("before {super secret is two} after one") end it "should pass nil for missing variables in partials" do output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb', :variables => {} %>} after") expect(output).to eq("before {super secret is } after") output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb' %>} after") expect(output).to eq("before {super secret is } after") end it "should render nested partials" do path = File.expand_path(File.join(CHEF_SPEC_DATA, "partial_one.erb")) output = @template_context.render_template_from_string("before {<%= render('#{path}', :local => true).strip %>} after") expect(output).to eq("before {partial one We could be diving for pearls! calling home} after") end describe "when an exception is raised in the template" do let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "templates", "failed.erb")) } def do_raise @template_context.render_template(template_file) end it "should catch and re-raise the exception as a TemplateError" do expect { do_raise }.to raise_error(Chef::Mixin::Template::TemplateError) end describe "the raised TemplateError" do subject(:exception) do begin do_raise rescue Chef::Mixin::Template::TemplateError => e e end end it "should contain template file and line numbers" do expect(exception.line_number).to eq(5) end it "should provide a source listing of the template around the exception" do expect(exception.source_listing).to eq(" 3: Which includes some content\n 4: \n 5: And will fail <%= nil[] %>") end it "should provide a nice source location" do expect(exception.source_location).to eq("on line #5") end it "should create a pretty output for the terminal" do expect(exception.to_s).to match(/Chef::Mixin::Template::TemplateError/) expect(exception.to_s).to match(/undefined method `\[\]' for nil:NilClass/) expect(exception.to_s).to include(" 3: Which includes some content\n 4: \n 5: And will fail <%= nil[] %>") expect(exception.to_s).to include(exception.original_exception.backtrace.first) end it "should include template file on original_exception backtrace" do expect(exception.original_exception.backtrace).to include(/#{Regexp.escape(template_file)}/) end end end describe "when customizing the template context" do it "extends the context to include modules" do mod = Module.new do def hello "ohai" end end @template_context._extend_modules([mod]) output = @template_context.render_template_from_string("<%=hello%>") expect(output).to eq("ohai") end it "emits a warning when overriding 'core' methods" do mod = Module.new do def render; end def node; end def render_template; end def render_template_from_string; end end %w{node render render_template render_template_from_string}.each do |method_name| expect(Chef::Log).to receive(:warn).with(/^Core template method `#{method_name}' overridden by extension module/) end @template_context._extend_modules([mod]) end end end describe "when an exception is raised in the template" do def do_raise @context.render_template_from_string("foo\nbar\nbaz\n<%= this_is_not_defined %>\nquin\nqunx\ndunno") end it "should catch and re-raise the exception as a TemplateError" do expect { do_raise }.to raise_error(Chef::Mixin::Template::TemplateError) end it "should raise an error if an attempt is made to access node but it is nil" do expect { @context.render_template_from_string("<%= node %>") { |r| r } }.to raise_error(Chef::Mixin::Template::TemplateError) end describe "the raised TemplateError" do before :each do begin do_raise rescue Chef::Mixin::Template::TemplateError => e @exception = e end end it "should have the original exception" do expect(@exception.original_exception).to be expect(@exception.original_exception.message).to match(/undefined local variable or method `this_is_not_defined'/) end it "should determine the line number of the exception" do expect(@exception.line_number).to eq(4) end it "should provide a source listing of the template around the exception" do expect(@exception.source_listing).to eq(" 2: bar\n 3: baz\n 4: <%= this_is_not_defined %>\n 5: quin\n 6: qunx") end it "should provide the evaluation context of the template" do expect(@exception.context).to eq(@context) end it "should defer the message to the original exception" do expect(@exception.message).to match(/undefined local variable or method `this_is_not_defined'/) end it "should provide a nice source location" do expect(@exception.source_location).to eq("on line #4") end it "should create a pretty output for the terminal" do expect(@exception.to_s).to match(/Chef::Mixin::Template::TemplateError/) expect(@exception.to_s).to match(/undefined local variable or method `this_is_not_defined'/) expect(@exception.to_s).to include(" 2: bar\n 3: baz\n 4: <%= this_is_not_defined %>\n 5: quin\n 6: qunx") expect(@exception.to_s).to include(@exception.original_exception.backtrace.first) end end end end