diff --git a/spec/beef/core/extension_spec.rb b/spec/beef/core/extension_spec.rb new file mode 100644 index 000000000..deaf70f0e --- /dev/null +++ b/spec/beef/core/extension_spec.rb @@ -0,0 +1,94 @@ +RSpec.describe BeEF::Extension do + let(:config) { BeEF::Core::Configuration.instance } + + describe '.is_present' do + it 'returns true when extension exists in configuration' do + allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} }) + expect(described_class.is_present('test_ext')).to be true + end + + it 'returns false when extension does not exist' do + allow(config).to receive(:get).with('beef.extension').and_return({}) + expect(described_class.is_present('nonexistent')).to be false + end + + it 'converts extension key to string' do + allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} }) + expect(described_class.is_present(:test_ext)).to be true + end + end + + describe '.is_enabled' do + it 'returns true when extension is present and enabled' do + allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} }) + allow(config).to receive(:get).with('beef.extension.test_ext.enable').and_return(true) + expect(described_class.is_enabled('test_ext')).to be true + end + + it 'returns false when extension is not present' do + allow(config).to receive(:get).with('beef.extension').and_return({}) + expect(described_class.is_enabled('nonexistent')).to be false + end + + it 'returns false when extension is disabled' do + allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} }) + allow(config).to receive(:get).with('beef.extension.test_ext.enable').and_return(false) + expect(described_class.is_enabled('test_ext')).to be false + end + end + + describe '.is_loaded' do + it 'returns true when extension is enabled and loaded' do + allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} }) + allow(config).to receive(:get).with('beef.extension.test_ext.enable').and_return(true) + allow(config).to receive(:get).with('beef.extension.test_ext.loaded').and_return(true) + expect(described_class.is_loaded('test_ext')).to be true + end + + it 'returns false when extension is not enabled' do + allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} }) + allow(config).to receive(:get).with('beef.extension.test_ext.enable').and_return(false) + expect(described_class.is_loaded('test_ext')).to be false + end + + it 'returns false when extension is not loaded' do + allow(config).to receive(:get).with('beef.extension').and_return({ 'test_ext' => {} }) + allow(config).to receive(:get).with('beef.extension.test_ext.enable').and_return(true) + allow(config).to receive(:get).with('beef.extension.test_ext.loaded').and_return(false) + expect(described_class.is_loaded('test_ext')).to be false + end + end + + describe '.load' do + it 'returns true when extension file exists' do + ext_path = "#{$root_dir}/extensions/test_ext/extension.rb" + allow(File).to receive(:exist?).with(ext_path).and_return(true) + allow(config).to receive(:set).with('beef.extension.test_ext.loaded', true).and_return(true) + # Stub require on the module itself since it's called directly + allow(described_class).to receive(:require).with(ext_path) + expect(described_class.load('test_ext')).to be true + end + + it 'returns false when extension file does not exist' do + ext_path = "#{$root_dir}/extensions/test_ext/extension.rb" + allow(File).to receive(:exist?).with(ext_path).and_return(false) + expect(described_class.load('test_ext')).to be false + end + + it 'sets loaded flag to true when successfully loaded' do + ext_path = "#{$root_dir}/extensions/test_ext/extension.rb" + allow(File).to receive(:exist?).with(ext_path).and_return(true) + allow(described_class).to receive(:require).with(ext_path) + expect(config).to receive(:set).with('beef.extension.test_ext.loaded', true).and_return(true) + described_class.load('test_ext') + end + + it 'handles errors during loading gracefully' do + ext_path = "#{$root_dir}/extensions/test_ext/extension.rb" + allow(File).to receive(:exist?).with(ext_path).and_return(true) + allow(described_class).to receive(:require).with(ext_path).and_raise(StandardError.new('Load error')) + # The rescue block calls print_more which may return a value, so just verify it doesn't raise + expect { described_class.load('test_ext') }.not_to raise_error + end + end +end diff --git a/spec/beef/core/main/command_spec.rb b/spec/beef/core/main/command_spec.rb index 4605f6f1e..2a887b92d 100644 --- a/spec/beef/core/main/command_spec.rb +++ b/spec/beef/core/main/command_spec.rb @@ -1,4 +1,4 @@ -RSpec.describe 'BeEF Command class testing' do +RSpec.describe BeEF::Core::Command do let(:config) { BeEF::Core::Configuration.instance } before do @@ -14,10 +14,215 @@ RSpec.describe 'BeEF Command class testing' do end end - it 'should return a beef configuration variable' do - expect do - command_mock = BeEF::Core::Command.new('test_get_variable') - expect(command_mock.config.beef_host).to eq('0.0.0.0') - end.to_not raise_error + describe BeEF::Core::CommandUtils do + describe '#format_multiline' do + it 'converts newlines to escaped newlines' do + result = BeEF::Core::CommandUtils.instance_method(:format_multiline).bind(Object.new).call("line1\nline2") + expect(result).to eq("line1\\nline2") + end + + it 'handles strings without newlines' do + result = BeEF::Core::CommandUtils.instance_method(:format_multiline).bind(Object.new).call("single line") + expect(result).to eq("single line") + end + end + end + + describe BeEF::Core::CommandContext do + it 'initializes with hash' do + context = described_class.new({ 'key' => 'value' }) + expect(context['key']).to eq('value') + end + + it 'initializes without hash' do + context = described_class.new + expect(context).to be_a(Erubis::Context) + end + + it 'includes CommandUtils' do + context = described_class.new + expect(context).to respond_to(:format_multiline) + end + end + + describe '#initialize' do + it 'initializes with module key' do + command = described_class.new('test_get_variable') + expect(command.config).to eq(config) + expect(command.datastore).to eq({}) + expect(command.beefjs_components).to eq({}) + end + + it 'sets friendlyname from configuration' do + # Mock all config calls for initialization + allow(config).to receive(:get).and_call_original + allow(config).to receive(:get).with('beef.module.test_get_variable.name').and_return('Test Get Variable') + allow(config).to receive(:get).with('beef.module.test_get_variable.path').and_return('modules/test/') + allow(config).to receive(:get).with('beef.module.test_get_variable.mount').and_return('/command/test.js') + allow(config).to receive(:get).with('beef.module.test_get_variable.db.id').and_return(1) + command = described_class.new('test_get_variable') + expect(command.friendlyname).to eq('Test Get Variable') + end + end + + describe '#needs_configuration?' do + it 'returns true when datastore is not nil' do + command = described_class.new('test_get_variable') + command.instance_variable_set(:@datastore, {}) + expect(command.needs_configuration?).to be true + end + + it 'returns false when datastore is nil' do + command = described_class.new('test_get_variable') + command.instance_variable_set(:@datastore, nil) + expect(command.needs_configuration?).to be false + end + end + + describe '#to_json' do + it 'returns JSON with command information' do + # Mock all config calls for this test + allow(config).to receive(:get).and_call_original + allow(config).to receive(:get).with('beef.module.test_get_variable.name').and_return('Test Get Variable') + allow(config).to receive(:get).with('beef.module.test_get_variable.description').and_return('Test Description') + allow(config).to receive(:get).with('beef.module.test_get_variable.category').and_return('Test Category') + allow(config).to receive(:get).with('beef.module.test_get_variable.path').and_return('modules/test/') + allow(config).to receive(:get).with('beef.module.test_get_variable.mount').and_return('/command/test.js') + allow(config).to receive(:get).with('beef.module.test_get_variable.db.id').and_return(1) + allow(BeEF::Module).to receive(:get_options).with('test_get_variable').and_return([]) + command = described_class.new('test_get_variable') + json = command.to_json + parsed = JSON.parse(json) + expect(parsed['Name']).to eq('Test Get Variable') + expect(parsed['Description']).to eq('Test Description') + expect(parsed['Category']).to eq('Test Category') + end + end + + describe '#build_datastore' do + it 'parses JSON data into datastore' do + command = described_class.new('test_get_variable') + data = '{"key": "value"}' + command.build_datastore(data) + expect(command.datastore).to eq({ 'key' => 'value' }) + end + + it 'handles invalid JSON gracefully' do + command = described_class.new('test_get_variable') + command.build_datastore('invalid json') + expect(command.datastore).to eq({}) + end + end + + describe '#build_callback_datastore' do + it 'initializes datastore with http_headers' do + command = described_class.new('test_get_variable') + command.build_callback_datastore('result', 1, 'hook', nil, nil) + expect(command.datastore).to have_key('http_headers') + expect(command.datastore['http_headers']).to eq({}) + end + + it 'adds results, command_id, and beefhook' do + command = described_class.new('test_get_variable') + command.build_callback_datastore('result', 1, 'hook', nil, nil) + expect(command.datastore['results']).to eq('result') + expect(command.datastore['cid']).to eq(1) + expect(command.datastore['beefhook']).to eq('hook') + end + + it 'adds valid http_params to datastore' do + allow(BeEF::Filters).to receive(:is_valid_command_module_datastore_key?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_command_module_datastore_param?).and_return(true) + allow(Erubis::XmlHelper).to receive(:escape_xml) { |arg| arg } + command = described_class.new('test_get_variable') + command.build_callback_datastore('result', 1, 'hook', { 'param1' => 'value1' }, {}) + expect(command.datastore['param1']).to eq('value1') + end + + it 'skips invalid http_params' do + allow(BeEF::Filters).to receive(:is_valid_command_module_datastore_key?).and_return(false) + command = described_class.new('test_get_variable') + command.build_callback_datastore('result', 1, 'hook', { 'invalid' => 'value' }, {}) + expect(command.datastore).not_to have_key('invalid') + end + end + + describe '#save' do + it 'saves results' do + command = described_class.new('test_get_variable') + results = { 'data' => 'test' } + command.save(results) + expect(command.instance_variable_get(:@results)).to eq(results) + end + end + + describe '#map_file_to_url' do + it 'calls AssetHandler bind' do + mock_handler = double('AssetHandler') + allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(mock_handler) + expect(mock_handler).to receive(:bind).with('file.txt', nil, nil, 1) + command = described_class.new('test_get_variable') + command.map_file_to_url('file.txt') + end + end + + describe '#use' do + it 'adds component to beefjs_components when file exists' do + # The path construction adds an extra /, so account for that + component_path = "#{$root_dir}/core/main/client//net/local.js" + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with(component_path).and_return(true) + command = described_class.new('test_get_variable') + command.use('beef.net.local') + expect(command.beefjs_components).to have_key('beef.net.local') + end + + it 'raises error when component file does not exist' do + component_path = "#{$root_dir}/core/main/client//net/nonexistent.js" + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with(component_path).and_return(false) + command = described_class.new('test_get_variable') + expect { command.use('beef.net.nonexistent') }.to raise_error(/Invalid beefjs component/) + end + + it 'does not add component twice' do + component_path = "#{$root_dir}/core/main/client//net/local.js" + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with(component_path).and_return(true) + command = described_class.new('test_get_variable') + command.use('beef.net.local') + command.use('beef.net.local') + expect(command.beefjs_components.keys.count).to eq(1) + end + end + + describe '#oc_value' do + it 'returns option value when option exists' do + option = BeEF::Core::Models::OptionCache.create!(name: 'test_option', value: 'test_value') + command = described_class.new('test_get_variable') + expect(command.oc_value('test_option')).to eq('test_value') + end + + it 'returns nil when option does not exist' do + command = described_class.new('test_get_variable') + expect(command.oc_value('nonexistent')).to be_nil + end + end + + describe '#apply_defaults' do + it 'applies option cache values to datastore' do + BeEF::Core::Models::OptionCache.create!(name: 'option1', value: 'cached_value') + command = described_class.new('test_get_variable') + command.instance_variable_set(:@datastore, [{ 'name' => 'option1', 'value' => 'default_value' }]) + command.apply_defaults + expect(command.datastore[0]['value']).to eq('cached_value') + end + + it 'keeps default value when option cache does not exist' do + command = described_class.new('test_get_variable') + command.instance_variable_set(:@datastore, [{ 'name' => 'option1', 'value' => 'default_value' }]) + command.apply_defaults + expect(command.datastore[0]['value']).to eq('default_value') + end end end