# frozen_string_literal: true require 'bolt_spec/plans/action_stubs' require 'bolt/error' require 'bolt/result_set' require 'bolt/result' require 'pathname' require 'set' module BoltSpec module Plans MOCKED_ACTIONS = %i[command script task upload].freeze class UnexpectedInvocation < ArgumentError; end # Nothing on the executor is 'public' class MockExecutor attr_reader :noop, :error_message attr_accessor :run_as def initialize(modulepath) @noop = false @run_as = nil @error_message = nil @allow_apply = false @modulepath = [modulepath].flatten.map { |path| File.absolute_path(path) } MOCKED_ACTIONS.each { |action| instance_variable_set(:"@#{action}_doubles", {}) } end def module_file_id(file) modpath = @modulepath.select { |path| file =~ /^#{path}/ } raise "Could not identify module path containing #{file}: #{modpath}" unless modpath.size == 1 path = Pathname.new(file) relative = path.relative_path_from(Pathname.new(modpath.first)) segments = relative.to_path.split('/') ([segments[0]] + segments[2..-1]).join('/') end def run_command(targets, command, options = {}) result = nil if (doub = @command_doubles[command] || @command_doubles[:default]) result = doub.process(targets, command, options) end unless result targets = targets.map(&:name) @error_message = "Unexpected call to 'run_command(#{command}, #{targets}, #{options})'" raise UnexpectedInvocation, @error_message end result end def run_script(targets, script_path, arguments, options = {}) script = module_file_id(script_path) result = nil if (doub = @script_doubles[script] || @script_doubles[:default]) result = doub.process(targets, script, arguments, options) end unless result targets = targets.map(&:name) params = options.merge('arguments' => arguments) @error_message = "Unexpected call to 'run_script(#{script}, #{targets}, #{params})'" raise UnexpectedInvocation, @error_message end result end def run_task(targets, task, arguments, options = {}) result = nil if (doub = @task_doubles[task.name] || @task_doubles[:default]) result = doub.process(targets, task.name, arguments, options) end unless result targets = targets.map(&:name) params = arguments.merge(options) @error_message = "Unexpected call to 'run_task(#{task.name}, #{targets}, #{params})'" raise UnexpectedInvocation, @error_message end result end def upload_file(targets, source_path, destination, options = {}) source = module_file_id(source_path) result = nil if (doub = @upload_doubles[source] || @upload_doubles[:default]) result = doub.process(targets, source, destination, options) end unless result targets = targets.map(&:name) @error_message = "Unexpected call to 'upload_file(#{source}, #{destination}, #{targets}, #{options})'" raise UnexpectedInvocation, @error_message end result end def assert_call_expectations MOCKED_ACTIONS.each do |action| instance_variable_get(:"@#{action}_doubles").map do |object, doub| doub.assert_called(object) end end end MOCKED_ACTIONS.each do |action| define_method(:"stub_#{action}") do |object| instance_variable_get(:"@#{action}_doubles")[object] ||= ActionDouble.new(:"#{action.capitalize}Stub") end end def stub_apply @allow_apply = true end def wait_until_available(targets, _options) Bolt::ResultSet.new(targets.map { |target| Bolt::Result.new(target) }) end def log_action(*_args) yield end def log_plan(_plan_name) yield end def without_default_logging yield end def report_function_call(_function); end def report_bundled_content(_mode, _name); end def report_apply(_statements, _resources); end # Mocked for Apply so it does not compile and execute. def with_node_logging(_description, targets) raise "Unexpected call to apply(#{targets})" unless @allow_apply end def queue_execute(targets) raise "Unexpected call to apply(#{targets})" unless @allow_apply targets end def await_results(promises) raise "Unexpected call to apply(#{targets})" unless @allow_apply Bolt::ResultSet.new(promises.map { |target| Bolt::ApplyResult.new(target) }) end # End Apply mocking # Mocked for apply_prep def transport(_protocol) # Always return a transport that includes the puppet-agent feature so version/install are skipped. Class.new do def provided_features ['puppet-agent'] end end.new end # End apply_prep mocking end end end