Merge pull request #3520 from jake-the-dev/tests/extensions-modules

TESTS | modules: add baseline coverage specs for all module categories
This commit is contained in:
zinduolis
2026-05-27 09:40:09 +10:00
committed by GitHub
20 changed files with 1740 additions and 17 deletions
-1
View File
@@ -1,3 +1,2 @@
---
BUNDLE_WITHOUT: "development:test"
BUNDLE_WITH: "geoip:ext_msf:ext_notifications:ext_dns:ext_qrcode"
+6
View File
@@ -6,6 +6,12 @@ beef.log
test/msf-test
extensions/admin_ui/media/javascript-min/
custom-config.yaml
# Generated at runtime by Hook_default_browser#pre_send
modules/host/hook_default_browser/bounce_to_ie_configured.pdf
# Generated at runtime by browser/spyder_eye#post_execute (and possibly other capture modules)
screenshot_*.png
.DS_Store
.gitignore
.rvmrc
+2
View File
@@ -2,3 +2,5 @@
--color
--require spec_helper
-I .
--tag ~run_on_browserstack
--tag ~run_on_long_tests
+32
View File
@@ -0,0 +1,32 @@
# SimpleCov configuration file
# This provides a cleaner alternative to configuring SimpleCov in spec_helper.rb
SimpleCov.configure do
# Basic filters
add_filter '/spec/'
add_filter '/config/'
add_filter '/test/'
# Group coverage by component
add_group 'Core', 'core'
add_group 'Extensions', 'extensions'
add_group 'Modules', 'modules'
# Track files based on coverage focus
if ENV['COVERAGE'] == 'core'
track_files 'core/**/*.rb'
elsif ENV['COVERAGE'] == 'extensions'
track_files 'extensions/**/*.rb'
elsif ENV['COVERAGE'] == 'modules'
track_files 'modules/**/*.rb'
else
# Default: track everything
track_files '{core,extensions,modules}/**/*.rb'
end
# Coverage thresholds
minimum_coverage 80 if ENV['CI']
# Formatters
formatter SimpleCov::Formatter::HTMLFormatter
end
+73 -3
View File
@@ -5,12 +5,82 @@
#
require 'rspec/core/rake_task'
task :default => ["short"]
task default: ['short']
RSpec::Core::RakeTask.new(:short) do |task|
task.rspec_opts = ['--tag ~run_on_browserstack', '--tag ~run_on_long_tests']
# Run rspec with an explicit file list (avoids envs that only run 557).
# Note: when run with the full file list, module specs load after extensions; the Dns stub in
# network_spec must not run before the real Dns extension (dns_spec) or you get "superclass mismatch".
desc 'Run short spec suite (all specs except browserstack/long)'
task :short do
short_files = Dir[File.join(Dir.pwd, 'spec', '**', '*_spec.rb')].sort
$stderr.puts "[rake short] spec files=#{short_files.size}"
abort '[rake short] Expected 60+ spec files; check you are in project root.' if short_files.size < 60
opts = [
'--tag', '~run_on_browserstack',
'--tag', '~run_on_long_tests'
]
ok = system('bundle', 'exec', 'rspec', *short_files, *opts)
abort 'rspec failed' unless ok
end
# Legacy namespace for backward compatibility
namespace :coverage do
task :modules => 'coverage_modules'
task :core => 'coverage_core'
task :extensions => 'coverage_extensions'
task :all => 'coverage'
end
# Base spec tasks
RSpec::Core::RakeTask.new(:spec) do |t|
t.pattern = 'spec/**/*_spec.rb'
t.rspec_opts = ['--tag', '~run_on_browserstack', '--tag', '~run_on_long_tests']
end
RSpec::Core::RakeTask.new(:spec_core) do |t|
t.pattern = 'spec/beef/core/**/*_spec.rb'
t.rspec_opts = ['--tag', '~run_on_browserstack', '--tag', '~run_on_long_tests']
end
RSpec::Core::RakeTask.new(:spec_extensions) do |t|
t.pattern = 'spec/beef/extensions/**/*_spec.rb'
t.rspec_opts = ['--tag', '~run_on_browserstack', '--tag', '~run_on_long_tests']
end
RSpec::Core::RakeTask.new(:spec_modules) do |t|
t.pattern = 'spec/beef/modules/**/*_spec.rb'
t.rspec_opts = ['--tag', '~run_on_browserstack', '--tag', '~run_on_long_tests']
end
# Coverage tasks using environment variables for cleaner configuration
desc 'Run all specs with complete coverage tracking'
task :coverage do
ENV['COVERAGE'] = 'all'
Rake::Task['spec'].invoke
end
desc 'Run core specs with coverage'
task :coverage_core do
ENV['COVERAGE'] = 'core'
Rake::Task['spec_core'].invoke
end
desc 'Run extensions specs with coverage'
task :coverage_extensions do
ENV['COVERAGE'] = 'extensions'
Rake::Task['spec_extensions'].invoke
end
desc 'Run modules specs with coverage'
task :coverage_modules do
ENV['COVERAGE'] = 'modules'
Rake::Task['spec_modules'].invoke
end
# Alias for backward compatibility
task :coverage_complete => :coverage
task :coverage_all => :coverage
RSpec::Core::RakeTask.new(:long) do |task|
task.rspec_opts = ['--tag ~run_on_browserstack']
end
+168
View File
@@ -0,0 +1,168 @@
# BeEF Test Suite
This directory contains the BeEF test suite using RSpec and SimpleCov for comprehensive testing and coverage reporting.
## Setup
### Prerequisites
- Ruby 3.0+
- Bundler
- All gems from `Gemfile`
### Configuration Files
- `spec/spec_helper.rb` - Main test configuration
- `.simplecov` - Coverage configuration
- `spec/support/` - Test helpers and utilities
## Running Tests
### Basic Commands
```bash
# Run all tests (fast, no coverage)
bundle exec rake short
# or
bundle exec rspec spec/ --tag '~run_on_browserstack' --tag '~run_on_long_tests'
# Run specific component tests
bundle exec rspec spec/beef/core/
bundle exec rspec spec/beef/extensions/
bundle exec rspec spec/beef/modules/
```
### Coverage Commands
```bash
# Complete coverage (recommended)
bundle exec rake coverage
# or
COVERAGE=all bundle exec rspec spec/ --tag '~run_on_browserstack' --tag '~run_on_long_tests'
# Component-specific coverage
bundle exec rake coverage_core # Core only
bundle exec rake coverage_modules # Modules only
bundle exec rake coverage_extensions # Extensions only
```
### Legacy Commands (Still Supported)
```bash
# Old coverage commands still work
bundle exec rake coverage:modules
bundle exec rake coverage:all
bundle exec rake coverage_complete
```
## Architecture
### SimpleCov Configuration
- **Environment-based**: Uses `COVERAGE=core|modules|extensions|all` environment variable
- **Grouped reporting**: Separate groups for Core, Extensions, and Modules
- **Filtered tracking**: Only tracks relevant files based on focus area
- **HTML reports**: Generated in `coverage/` directory
### Test Organization
- **Centralized config**: `BeefTestConfig` module provides test data instead of global constants
- **Component isolation**: Each component (core/extensions/modules) has dedicated specs
- **Branch coverage**: Realistic test data for conditional logic testing
- **Mock management**: Proper mocking of external dependencies
### Rake Tasks
- **Clean separation**: Base `spec*` tasks vs coverage `coverage*` tasks
- **Environment variables**: Coverage controlled via `COVERAGE` env var
- **No sequential execution**: Single test runs with proper filtering
- **Backward compatibility**: Old task names still work
## Key Improvements
### ✅ **Eliminated Global Constant Collisions**
- Branch-coverage data for the dynamic module spec loaders lives in
`BeefTestConfig.branch_coverage_for(:component)` (in `spec/spec_helper.rb`).
- Each module spec file pulls its own component (`:browser`, `:exploits`, `:host`,
`:misc`, `:network`, `:social_engineering`) into a uniquely-named local
constant (e.g. `BRANCH_COVERAGE_HOST`), so loading the full suite no longer
triggers "already initialized constant" warnings or silently overwrites data.
### ✅ **Simplified Coverage Logic**
- Cleaner filtering using `track_files` instead of complex `add_filter` logic
- Environment variable `COVERAGE` instead of `COVERAGE_FOCUS`
### ✅ **Better Test Organization**
- Centralized test configuration in `BeefTestConfig`
- Component-specific test data loading
- Reduced code duplication
### ✅ **Cleaner Rake Tasks**
- Single execution instead of sequential runs
- Proper environment variable usage
- Backward compatibility maintained
### ✅ **Standard Patterns**
- Uses `.simplecov` config file (standard practice)
- Follows RSpec best practices
- Better separation of concerns
## Module Spec Pattern (dynamic loaders)
Specs under `spec/beef/modules/**/<category>_spec.rb` walk every
`modules/<category>/**/module.rb` file and generate a `RSpec.describe` block per
class. They cover:
- `.options` returns an `Array` (when defined)
- `#pre_send` runs without raising (when defined)
- `#post_execute` runs without raising (when defined)
- An extra `#post_execute` example with a realistic datastore for any module
listed in `BeefTestConfig.branch_coverage_for(:category)`
Trade-off: this is a coverage-driving baseline. The `expect { ... }.not_to
raise_error` assertion catches load failures, missing constants, nil crashes,
and undefined methods, but it does not assert behavioural correctness. Add
targeted assertions for high-value modules (see the `Wordpress_add_user` and
`Test_get_variable` examples in `spec/beef/modules/misc/misc_spec.rb`).
## Coverage Focus Areas
- **core**: Framework core functionality
- **extensions**: Extension modules
- **modules**: Command modules (main focus)
- **all**: Complete coverage across all areas
## Troubleshooting
### Common Issues
1. **"already initialized constant" warnings**
- Fixed by using centralized config instead of global constants
2. **Low coverage percentages**
- Use `COVERAGE=all` for complete coverage
- Ensure realistic test data triggers conditional paths
3. **Test failures**
- Check that mocks are properly configured
- Verify test data matches module expectations
### Debug Commands
```bash
# Run with debug output
bundle exec rspec --format documentation
# Run single test file
bundle exec rspec spec/beef/modules/browser/browser_spec.rb
# Check coverage report
open coverage/index.html
```
## Contributing
When adding new tests:
1. Use `BeefTestConfig.branch_coverage_for(:component)` for branch test data
2. Add realistic datastore values that trigger conditional logic
3. Mock external dependencies (database, network, etc.)
4. Follow existing patterns for consistency
+6 -6
View File
@@ -400,14 +400,14 @@ RSpec.describe BeEF::Filters do
expect(BeEF::Filters.has_valid_browser_details_chars?('')).to be(false)
end
it 'returns false when string only has allowed chars' do
# Method returns true when regex matches (invalid char found); false when only valid chars
expect(BeEF::Filters.has_valid_browser_details_chars?('abc')).to be(false)
expect(BeEF::Filters.has_valid_browser_details_chars?('a-b (c)')).to be(false)
it 'returns true when string only has allowed chars' do
# Method returns nil-on-no-match: nil.nil? == true, so a fully-valid string returns true.
expect(BeEF::Filters.has_valid_browser_details_chars?('abc')).to be(true)
expect(BeEF::Filters.has_valid_browser_details_chars?('a-b (c)')).to be(true)
end
it 'returns true when string contains disallowed character' do
expect(BeEF::Filters.has_valid_browser_details_chars?('ab@c')).to be(true)
it 'returns false when string contains disallowed character' do
expect(BeEF::Filters.has_valid_browser_details_chars?('ab@c')).to be(false)
end
end
+102
View File
@@ -0,0 +1,102 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Unit tests for every module under modules/browser/ (including hooked_origin).
# Branch coverage: extra post_execute for modules that set BrowserDetails when results match.
#
require_relative '../../../spec_helper'
project_root = File.expand_path('../../../../', __dir__)
browser_module_paths = Dir[File.join(project_root, 'modules/browser/**/module.rb')].sort
# Load branch coverage data from centralised config
BRANCH_COVERAGE_BROWSER = BeefTestConfig.branch_coverage_for(:browser)
browser_module_paths.each do |path|
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
branch_key = File.dirname(path).sub("#{project_root}/", '')
require_path = File.join('../../../../', rel)
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
next unless class_line
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
require_relative require_path
mod = Object.const_get(klass_name)
RSpec.describe mod do
if described_class.respond_to?(:options)
describe '.options' do
it 'returns an Array' do
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:beef_proto).and_return('http')
allow(config).to receive(:beef_port).and_return('3000')
allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000')
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
expect(described_class.options).to be_an(Array)
end
end
end
if described_class.method_defined?(:pre_send)
describe '#pre_send' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind).and_return(nil)
allow(handler).to receive(:bind).and_return(nil)
allow(handler).to receive(:bind_raw).and_return(nil)
allow(handler).to receive(:remap).and_return(nil)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
expect { run_pre_send(instance) }.not_to raise_error
end
end
end
if described_class.method_defined?(:post_execute)
describe '#post_execute' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:remap)
allow(handler).to receive(:bind_raw)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
# Some modules (e.g. Spyder_eye) write screenshot files to $home_dir on the success path.
# Stub File.open with a block-aware double so the test never touches the real filesystem.
file_double = double('File').as_null_object
allow(File).to receive(:open) do |*_args, &block|
block ? block.call(file_double) : file_double
end
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
expect { run_post_execute(instance) }.not_to raise_error
end
if BRANCH_COVERAGE_BROWSER[branch_key]
it 'runs branch path with realistic datastore' do
branch = BRANCH_COVERAGE_BROWSER[branch_key]
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:remap)
allow(handler).to receive(:bind_raw)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
allow(BeEF::Core::Models::BrowserDetails).to receive(:set)
instance = build_command_instance(described_class, branch[:datastore])
expect { run_post_execute(instance) }.not_to raise_error
end
end
end
end
end
end
@@ -0,0 +1,71 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Unit tests for every module under modules/chrome_extensions/.
# Each directory is tested for .options (when present) and #post_execute.
#
require_relative '../../../spec_helper'
project_root = File.expand_path('../../../../', __dir__)
chrome_module_paths = Dir[File.join(project_root, 'modules/chrome_extensions/**/module.rb')].sort
chrome_module_paths.each do |path|
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
require_path = File.join('../../../../', rel)
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
next unless class_line
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
require_relative require_path
mod = Object.const_get(klass_name)
RSpec.describe mod do
if described_class.respond_to?(:options)
describe '.options' do
it 'returns an Array' do
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
expect(described_class.options).to be_an(Array)
end
end
end
if described_class.method_defined?(:pre_send)
describe '#pre_send' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind).and_return(nil)
allow(handler).to receive(:bind).and_return(nil)
allow(handler).to receive(:bind_raw).and_return(nil)
allow(handler).to receive(:remap).and_return(nil)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
instance = build_command_instance(described_class, 'return' => '', 'result' => '', 'cid' => '0')
expect { run_pre_send(instance) }.not_to raise_error
end
end
end
if described_class.method_defined?(:post_execute)
describe '#post_execute' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
instance = build_command_instance(described_class, 'return' => '', 'result' => '', 'cid' => '0')
expect { run_post_execute(instance) }.not_to raise_error
end
end
end
end
end
+75
View File
@@ -0,0 +1,75 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Unit tests for every module under modules/debug/.
# Each directory is tested for .options (when present) and #post_execute.
# See test_beef_debugs_spec.rb for integration/E2E tests (BrowserStack).
#
require_relative '../../../spec_helper'
project_root = File.expand_path('../../../../', __dir__)
debug_module_paths = Dir[File.join(project_root, 'modules/debug/**/module.rb')].sort
debug_module_paths.each do |path|
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
require_path = File.join('../../../../', rel)
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
next unless class_line
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
require_relative require_path
mod = Object.const_get(klass_name)
RSpec.describe mod do
if described_class.respond_to?(:options)
describe '.options' do
it 'returns an Array' do
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:beef_port).and_return('3000')
allow(config).to receive(:beef_proto).and_return('http')
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
expect(described_class.options).to be_an(Array)
end
end
end
if described_class.method_defined?(:pre_send)
describe '#pre_send' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind).and_return(nil)
allow(handler).to receive(:bind).and_return(nil)
allow(handler).to receive(:bind_raw).and_return(nil)
allow(handler).to receive(:bind_redirect).with(anything, anything).and_return(nil)
allow(handler).to receive(:remap).and_return(nil)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
expect { run_pre_send(instance) }.not_to raise_error
end
end
end
if described_class.method_defined?(:post_execute)
describe '#post_execute' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
expect { run_post_execute(instance) }.not_to raise_error
end
end
end
end
end
+110
View File
@@ -0,0 +1,110 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Unit tests for every module under modules/exploits/.
# Branch coverage: extra post_execute for modules with conditional branches
# (driven by BeefTestConfig.branch_coverage_for(:exploits) in spec_helper.rb).
#
require_relative '../../../spec_helper'
project_root = File.expand_path('../../../../', __dir__)
exploit_module_paths = Dir[File.join(project_root, 'modules/exploits/**/module.rb')].sort
BRANCH_COVERAGE_EXPLOITS = BeefTestConfig.branch_coverage_for(:exploits)
exploit_module_paths.each do |path|
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
branch_key = File.dirname(path).sub("#{project_root}/", '')
require_path = File.join('../../../../', rel)
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
next unless class_line
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
require_relative require_path
mod = Object.const_get(klass_name)
RSpec.describe mod do
if described_class.respond_to?(:options)
describe '.options' do
it 'returns an Array' do
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
expect(described_class.options).to be_an(Array)
end
end
end
if described_class.method_defined?(:pre_send)
describe '#pre_send' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind).and_return(nil)
allow(handler).to receive(:bind).and_return(nil)
allow(handler).to receive(:bind_raw).and_return(nil)
allow(handler).to receive(:remap).and_return(nil)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
instance = build_command_instance(described_class, {})
expect { run_pre_send(instance) }.not_to raise_error
end
end
end
if described_class.method_defined?(:post_execute)
describe '#post_execute' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:remap)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
instance = build_command_instance(described_class, {})
expect { run_post_execute(instance) }.not_to raise_error
end
if BRANCH_COVERAGE_EXPLOITS[branch_key]
it 'runs branch path with realistic datastore' do
raw = BRANCH_COVERAGE_EXPLOITS[branch_key]
branches = raw.is_a?(Array) ? raw : [raw]
# NetworkService / NetworkHost belong to the network extension and may not be
# loaded during a modules-only spec run. Provide class doubles per-example
# so `allow(...).to receive(:create)` resolves regardless.
stub_const('BeEF::Core::Models::NetworkService', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkService)
stub_const('BeEF::Core::Models::NetworkHost', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkHost)
branches.each do |branch|
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:remap)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
allow(BeEF::Core::Models::NetworkService).to receive(:create)
allow(BeEF::Core::Models::NetworkHost).to receive(:create)
allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true)
if branch[:config_get]
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:get).with(anything) do |key|
branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1'
end
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
end
instance = build_command_instance(described_class, branch[:datastore])
expect { run_post_execute(instance) }.not_to raise_error
end
end
end
end
end
end
end
+131
View File
@@ -0,0 +1,131 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Unit tests for every module under modules/host/.
# Each directory is tested for .options (when present), #pre_send, and #post_execute.
# Branch coverage: extra post_execute runs for modules listed in
# BeefTestConfig.branch_coverage_for(:host) (defined in spec_helper.rb).
#
require_relative '../../../spec_helper'
project_root = File.expand_path('../../../../', __dir__)
host_module_paths = Dir[File.join(project_root, 'modules/host/**/module.rb')].sort
BRANCH_COVERAGE_HOST = BeefTestConfig.branch_coverage_for(:host)
host_module_paths.each do |path|
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
branch_key = File.dirname(path).sub("#{project_root}/", '')
require_path = File.join('../../../../', rel)
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
next unless class_line
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
require_relative require_path
mod = Object.const_get(klass_name)
RSpec.describe mod do
if described_class.respond_to?(:options)
describe '.options' do
it 'returns an Array' do
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000')
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
expect(described_class.options).to be_an(Array)
end
end
end
if described_class.method_defined?(:pre_send)
describe '#pre_send' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind).and_return(nil)
allow(handler).to receive(:bind).and_return(nil)
allow(handler).to receive(:bind_raw).and_return(nil)
allow(handler).to receive(:remap).and_return(nil)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
# Stub File.open so Hook_default_browser#pre_send doesn't read/write the real
# bounce_to_ie_configured.pdf in modules/host/hook_default_browser/.
# The source calls File.open(path, 'w') without a block and File.open(path, 'r') { |f| ... } with one.
file_double = double('File').as_null_object
allow(File).to receive(:open) do |*_args, &block|
block ? block.call(file_double) : file_double
end
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
expect { run_pre_send(instance) }.not_to raise_error
end
end
end
if described_class.method_defined?(:post_execute)
describe '#post_execute' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:remap)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
file_double = double('File').as_null_object
allow(File).to receive(:open).and_return(file_double)
allow(BeEF::Core::Models::Command).to receive(:save_result)
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
# Minimal datastore so modules that call .sub/.to_s on keys don't get nil
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
expect { run_post_execute(instance) }.not_to raise_error
end
if BRANCH_COVERAGE_HOST[branch_key]
it 'runs branch path with realistic datastore' do
branch = BRANCH_COVERAGE_HOST[branch_key]
# NetworkService / NetworkHost belong to the network extension and may not be
# loaded during a modules-only spec run. Provide class doubles per-example
# so `allow(...).to receive(:create)` resolves regardless.
stub_const('BeEF::Core::Models::NetworkService', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkService)
stub_const('BeEF::Core::Models::NetworkHost', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkHost)
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:remap)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
file_double = double('File').as_null_object
allow(File).to receive(:open).and_return(file_double)
allow(BeEF::Core::Models::Command).to receive(:save_result)
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
allow(BeEF::Core::Models::NetworkService).to receive(:create)
allow(BeEF::Core::Models::NetworkHost).to receive(:create)
allow(BeEF::Core::Models::BrowserDetails).to receive(:get).and_return('Linux')
allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true)
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000')
allow(config).to receive(:get).with(anything) do |key|
branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1'
end
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
instance = build_command_instance(described_class, branch[:datastore])
expect { run_post_execute(instance) }.not_to raise_error
end
end
end
end
end
end
+113
View File
@@ -0,0 +1,113 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Unit tests for every module under modules/ipec/.
#
# Some ipec modules reference BeEF::Extension::ETag and
# BeEF::Extension::ServerClientDnsTunnel which are not loaded in unit tests.
# We stub these per-example with stub_const so the stubs don't leak across
# the suite or shadow the real extensions when they happen to be loaded.
#
require_relative '../../../spec_helper'
project_root = File.expand_path('../../../../', __dir__)
paths = Dir[File.join(project_root, 'modules/ipec/**/module.rb')].sort
# Lightweight stand-ins for the extension singletons; no real behaviour required.
def stub_etag_messages_class
Class.new do
def self.instance
@instance ||= Struct.new(:messages).new({})
end
end
end
def stub_dns_tunnel_server_class
Class.new do
def self.instance
@instance ||= Struct.new(:messages).new({})
end
end
end
paths.each do |path|
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
require_path = File.join('../../../../', rel)
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
next unless class_line
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
require_relative require_path
mod = Object.const_get(klass_name)
RSpec.describe mod do
before(:each) do
# Per-example stubs (auto-removed after each example).
BeEF::Extension.const_set(:ETag, Module.new) unless BeEF::Extension.const_defined?(:ETag)
BeEF::Extension.const_set(:ServerClientDnsTunnel, Module.new) unless BeEF::Extension.const_defined?(:ServerClientDnsTunnel)
stub_const('BeEF::Extension::ETag::ETagMessages', stub_etag_messages_class) unless BeEF::Extension::ETag.const_defined?(:ETagMessages)
stub_const('BeEF::Extension::ServerClientDnsTunnel::Server', stub_dns_tunnel_server_class) unless BeEF::Extension::ServerClientDnsTunnel.const_defined?(:Server)
end
if described_class.respond_to?(:options)
describe '.options' do
it 'returns an Array' do
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:beef_port).and_return('3000')
allow(config).to receive(:beef_proto).and_return('http')
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
expect(described_class.options).to be_an(Array)
end
end
end
if described_class.method_defined?(:pre_send)
describe '#pre_send' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind).and_return(nil)
allow(handler).to receive(:bind).and_return(nil)
allow(handler).to receive(:bind_raw).and_return(nil)
allow(handler).to receive(:remap).and_return(nil)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:get).with(anything) do |key|
case key
when 'beef.extension.etag.enable' then true
when 'beef.extension.s2c_dns_tunnel.enable' then true
when 'beef.extension.s2c_dns_tunnel.zone' then 'example.com'
else '127.0.0.1'
end
end
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
instance = build_command_instance(described_class, [{ 'name' => 'data', 'value' => 'test' }])
expect { run_pre_send(instance) }.not_to raise_error
end
end
end
if described_class.method_defined?(:post_execute)
describe '#post_execute' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:remap)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
file_double = double('File').as_null_object
allow(File).to receive(:open).and_return(file_double)
allow(BeEF::Core::Models::Command).to receive(:save_result)
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
expect { run_post_execute(instance) }.not_to raise_error
end
end
end
end
end
+163
View File
@@ -0,0 +1,163 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Unit tests for every module under modules/misc/ (including subdirs).
# Branch coverage: extra post_execute runs for modules in
# BeefTestConfig.branch_coverage_for(:misc) (defined in spec_helper.rb).
#
require_relative '../../../spec_helper'
project_root = File.expand_path('../../../../', __dir__)
paths = Dir[File.join(project_root, 'modules/misc/**/module.rb')].sort
BRANCH_COVERAGE_MISC = BeefTestConfig.branch_coverage_for(:misc)
paths.each do |path|
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
branch_key = File.dirname(path).sub("#{project_root}/", '')
require_path = File.join('../../../../', rel)
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+(?:\w+|BeEF::\w+(?:::\w+)*)/ }
next unless class_line
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+(?:\w+|BeEF::\w+(?:::\w+)*)/)[1]
require_relative require_path
mod = Object.const_get(klass_name)
RSpec.describe mod do
if described_class.respond_to?(:options)
describe '.options' do
it 'returns an Array' do
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:beef_port).and_return('3000')
allow(config).to receive(:beef_proto).and_return('http')
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
expect(described_class.options).to be_an(Array)
end
end
# Targeted structural assertions for selected misc modules. Higher value than
# the generic smoke test: checks option count, names, types and shape.
if klass_name == 'Wordpress_add_user'
describe '.options (Wordpress_add_user specifics)' do
it 'includes wordpress path and user options' do
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:beef_port).and_return('3000')
allow(config).to receive(:beef_proto).and_return('http')
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
options = described_class.options
expect(options).to be_an(Array)
expect(options.length).to eq(5) # wp_path (parent) + username, password, email, role
wp_path_option = options.find { |opt| opt['name'] == 'wp_path' }
expect(wp_path_option).not_to be_nil
expect(wp_path_option['value']).to eq('/')
username_option = options.find { |opt| opt['name'] == 'username' }
expect(username_option).not_to be_nil
expect(username_option['value']).to eq('beef')
password_option = options.find { |opt| opt['name'] == 'password' }
expect(password_option).not_to be_nil
expect(password_option['value']).to be_a(String)
expect(password_option['value'].length).to eq(10) # SecureRandom.hex(5) = 10 chars
role_option = options.find { |opt| opt['name'] == 'role' }
expect(role_option).not_to be_nil
expect(role_option['value']).to eq('administrator')
end
end
end
if klass_name == 'Test_get_variable'
describe '.options (Test_get_variable specifics)' do
it 'returns payload_name option with correct structure' do
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:beef_port).and_return('3000')
allow(config).to receive(:beef_proto).and_return('http')
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
options = described_class.options
expect(options).to be_an(Array)
expect(options.length).to eq(1)
option = options.first
expect(option['name']).to eq('payload_name')
expect(option['ui_label']).to eq('Payload Name')
expect(option['type']).to eq('text')
expect(option['value']).to eq('message')
expect(option['width']).to eq('400px')
end
end
end
end
if described_class.method_defined?(:pre_send)
describe '#pre_send' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind).and_return(nil)
allow(handler).to receive(:bind).and_return(nil)
allow(handler).to receive(:bind_raw).and_return(nil)
allow(handler).to receive(:remap).and_return(nil)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
allow(IO).to receive(:popen).and_return(StringIO.new(''))
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
expect { run_pre_send(instance) }.not_to raise_error
end
end
end
if described_class.method_defined?(:post_execute)
describe '#post_execute' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:remap)
allow(handler).to receive(:bind_raw)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
file_double = double('File').as_null_object
allow(File).to receive(:open).and_return(file_double)
allow(BeEF::Core::Models::Command).to receive(:save_result)
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
expect { run_post_execute(instance) }.not_to raise_error
end
if BRANCH_COVERAGE_MISC[branch_key]
it 'runs branch path with realistic datastore' do
branch = BRANCH_COVERAGE_MISC[branch_key]
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:remap)
allow(handler).to receive(:bind_raw)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
file_double = double('File').as_null_object
allow(File).to receive(:open).and_return(file_double)
allow(BeEF::Core::Models::Command).to receive(:save_result)
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
instance = build_command_instance(described_class, branch[:datastore])
expect { run_post_execute(instance) }.not_to raise_error
end
end
end
end
end
end
+172
View File
@@ -0,0 +1,172 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Unit tests for every module under modules/network/ (including ADC).
#
# Branch coverage data lives in BeefTestConfig.branch_coverage_for(:network)
# (see spec_helper.rb). Per-example stub_const calls inject a Dns::Server
# stand-in only when the real Dns extension is not loaded, avoiding the
# "superclass mismatch for class Server" hazard that arises if the stub
# leaks into the suite before the real extension's spec loads.
#
require_relative '../../../spec_helper'
BRANCH_COVERAGE_NETWORK = BeefTestConfig.branch_coverage_for(:network)
# Lightweight Dns server stand-in used when the real BeEF::Extension::Dns is
# not loaded (e.g. when running modules-only specs).
DNS_SERVER_STUB = Class.new do
def self.instance
@instance ||= Object.new.tap do |o|
def o.add_rule(*) 1 end
def o.remove_rule!(*) nil end
end
end
end
project_root = File.expand_path('../../../../', __dir__)
paths = Dir[File.join(project_root, 'modules/network/**/module.rb')].sort
paths.each do |path|
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
branch_key = File.dirname(path).sub("#{project_root}/", '')
require_path = File.join('../../../../', rel)
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
next unless class_line
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
require_relative require_path
mod = Object.const_get(klass_name)
RSpec.describe mod do
before(:each) do
# Irc_nat_pinning calls sleep 30 in post_execute; suppress so the suite doesn't block.
allow(Kernel).to receive(:sleep)
allow_any_instance_of(described_class).to receive(:sleep).and_return(nil)
# Provide a per-example Dns::Server stand-in only if the real extension hasn't been loaded.
# Using stub_const keeps the stub scoped to this example only.
unless defined?(BeEF::Extension::Dns) && BeEF::Extension::Dns.const_defined?(:Server)
BeEF::Extension.const_set(:Dns, Module.new) unless BeEF::Extension.const_defined?(:Dns)
stub_const('BeEF::Extension::Dns::Server', DNS_SERVER_STUB)
end
end
if described_class.respond_to?(:options)
describe '.options' do
it 'returns an Array' do
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:beef_port).and_return('3000')
allow(config).to receive(:beef_proto).and_return('http')
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
expect(described_class.options).to be_an(Array)
end
end
end
if described_class.method_defined?(:pre_send)
describe '#pre_send' do
it 'runs without error' do
# If the real Dns extension is loaded (e.g. rake short), stub Server.instance so Dns_rebinding works.
if defined?(BeEF::Extension::Dns) && BeEF::Extension::Dns.const_defined?(:Server)
dns_instance = double('DnsServer', add_rule: 1, remove_rule!: nil)
allow(BeEF::Extension::Dns::Server).to receive(:instance).and_return(dns_instance)
end
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind).and_return(nil)
allow(handler).to receive(:bind).and_return(nil)
allow(handler).to receive(:bind_raw).and_return(nil)
allow(handler).to receive(:bind_socket).and_return(nil)
allow(handler).to receive(:unbind_socket).and_return(nil)
allow(handler).to receive(:bind_cached).and_return(nil)
allow(handler).to receive(:remap).and_return(nil)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:get).with(anything) do |key|
case key
when 'beef.extension.dns_rebinding' then { 'address_http_external' => '127.0.0.1', 'port_proxy' => '1234' }
when 'beef.module.dns_rebinding.domain' then 'example.com'
else '127.0.0.1'
end
end
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
# Dns_rebinding expects @datastore as Array of option hashes (target, domain, url_callback).
datastore = described_class.name.include?('Dns_rebinding') ? [{ 'value' => '192.168.0.1' }, { 'value' => 'example.com' }, {}] : { 'result' => '', 'results' => '', 'cid' => '0' }
instance = build_command_instance(described_class, datastore)
expect { run_pre_send(instance) }.not_to raise_error
end
end
end
if described_class.method_defined?(:post_execute)
describe '#post_execute' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:bind_socket)
allow(handler).to receive(:unbind_socket)
allow(handler).to receive(:remap)
allow(handler).to receive(:bind_cached)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
file_double = double('File').as_null_object
allow(File).to receive(:open).and_return(file_double)
allow(BeEF::Core::Models::Command).to receive(:save_result)
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
expect { run_post_execute(instance) }.not_to raise_error
end
if BRANCH_COVERAGE_NETWORK[branch_key]
it 'runs branch path with realistic datastore' do
raw = BRANCH_COVERAGE_NETWORK[branch_key]
branches = raw.is_a?(Array) ? raw : [raw]
# NetworkService / NetworkHost belong to the network extension and may not be
# loaded during a modules-only spec run. Provide class doubles per-example
# so `allow(...).to receive(:create)` resolves regardless.
stub_const('BeEF::Core::Models::NetworkService', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkService)
stub_const('BeEF::Core::Models::NetworkHost', Class.new { def self.create(*); end }) unless defined?(BeEF::Core::Models::NetworkHost)
branches.each do |branch|
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:bind_socket)
allow(handler).to receive(:unbind_socket)
allow(handler).to receive(:remap)
allow(handler).to receive(:bind_cached)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
file_double = double('File').as_null_object
allow(File).to receive(:open).and_return(file_double)
allow(BeEF::Core::Models::Command).to receive(:save_result)
allow_any_instance_of(described_class).to receive(:ip).and_return('0.0.0.0')
allow_any_instance_of(described_class).to receive(:timestamp).and_return('0')
allow(BeEF::Core::Models::NetworkService).to receive(:create)
allow(BeEF::Core::Models::NetworkHost).to receive(:create)
allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true)
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:beef_port).and_return('3000')
allow(config).to receive(:beef_proto).and_return('http')
allow(config).to receive(:get).with(anything) do |key|
branch[:config_get] && branch[:config_get].key?(key) ? branch[:config_get][key] : '127.0.0.1'
end
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
instance = build_command_instance(described_class, branch[:datastore])
expect { run_post_execute(instance) }.not_to raise_error
end
end
end
end
end
end
end
@@ -0,0 +1,75 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Unit tests for every module under modules/persistence/.
#
require_relative '../../../spec_helper'
project_root = File.expand_path('../../../../', __dir__)
paths = Dir[File.join(project_root, 'modules/persistence/**/module.rb')].sort
paths.each do |path|
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
require_path = File.join('../../../../', rel)
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
next unless class_line
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
require_relative require_path
mod = Object.const_get(klass_name)
RSpec.describe mod do
if described_class.respond_to?(:options)
describe '.options' do
it 'returns an Array' do
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:beef_port).and_return('3000')
allow(config).to receive(:beef_proto).and_return('http')
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
expect(described_class.options).to be_an(Array)
end
end
end
if described_class.method_defined?(:pre_send)
describe '#pre_send' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind).and_return(nil)
allow(handler).to receive(:bind).and_return(nil)
allow(handler).to receive(:bind_raw).and_return(nil)
allow(handler).to receive(:remap).and_return(nil)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:get).with(anything).and_return('/hook.js')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
expect { run_pre_send(instance) }.not_to raise_error
end
end
end
if described_class.method_defined?(:post_execute)
describe '#post_execute' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:remap)
allow(handler).to receive(:bind_raw)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
file_double = double('File').as_null_object
allow(File).to receive(:open).and_return(file_double)
allow(BeEF::Core::Models::Command).to receive(:save_result)
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'cid' => '0')
expect { run_post_execute(instance) }.not_to raise_error
end
end
end
end
end
@@ -0,0 +1,72 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Unit tests for every module under modules/phonegap/.
# Some modules use #callback instead of #post_execute; only post_execute is tested here.
#
require_relative '../../../spec_helper'
project_root = File.expand_path('../../../../', __dir__)
paths = Dir[File.join(project_root, 'modules/phonegap/**/module.rb')].sort
paths.each do |path|
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
require_path = File.join('../../../../', rel)
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
next unless class_line
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
require_relative require_path
mod = Object.const_get(klass_name)
RSpec.describe mod do
if described_class.respond_to?(:options)
describe '.options' do
it 'returns an Array' do
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:beef_proto).and_return('http')
allow(config).to receive(:beef_port).and_return('3000')
allow(config).to receive(:hook_file_path).and_return('/hook.js')
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
expect(described_class.options).to be_an(Array)
end
end
end
if described_class.method_defined?(:pre_send)
describe '#pre_send' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind).and_return(nil)
allow(handler).to receive(:bind).and_return(nil)
allow(handler).to receive(:bind_raw).and_return(nil)
allow(handler).to receive(:remap).and_return(nil)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
instance = build_command_instance(described_class, 'result' => '', 'Result' => '', 'cid' => '0')
expect { run_pre_send(instance) }.not_to raise_error
end
end
end
if described_class.method_defined?(:post_execute)
describe '#post_execute' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
instance = build_command_instance(described_class, 'result' => '', 'Result' => '', 'cid' => '0')
expect { run_post_execute(instance) }.not_to raise_error
end
end
end
end
end
@@ -0,0 +1,103 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Unit tests for every module under modules/social_engineering/.
# Branch coverage data and pre_send skip list live in BeefTestConfig
# (see spec_helper.rb).
#
require_relative '../../../spec_helper'
project_root = File.expand_path('../../../../', __dir__)
paths = Dir[File.join(project_root, 'modules/social_engineering/**/module.rb')].sort
BRANCH_COVERAGE_SOCENG = BeefTestConfig.branch_coverage_for(:social_engineering)
PRE_SEND_SKIP_SOCENG = BeefTestConfig.pre_send_skip_for(:social_engineering)
paths.each do |path|
rel = path.sub("#{project_root}/", '').sub(/\.rb$/, '')
branch_key = File.dirname(path).sub("#{project_root}/", '')
require_path = File.join('../../../../', rel)
class_line = File.read(path).lines.find { |l| l =~ /\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/ }
next unless class_line
klass_name = class_line.match(/\bclass\s+(\w+)\s+<\s+BeEF::Core::Command/)[1]
require_relative require_path
mod = Object.const_get(klass_name)
RSpec.describe mod do
if described_class.respond_to?(:options)
describe '.options' do
it 'returns an Array' do
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:beef_host).and_return('127.0.0.1')
allow(config).to receive(:beef_proto).and_return('http')
allow(config).to receive(:beef_port).and_return('3000')
allow(config).to receive(:beef_url_str).and_return('http://127.0.0.1:3000')
allow(config).to receive(:get).with(anything) do |key|
key == 'beef.module.simple_hijacker.templates' ? [] : '127.0.0.1'
end
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
expect(described_class.options).to be_an(Array)
end
end
end
if described_class.method_defined?(:pre_send) && !PRE_SEND_SKIP_SOCENG.include?(klass_name)
describe '#pre_send' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind).and_return(nil)
allow(handler).to receive(:bind).and_return(nil)
allow(handler).to receive(:bind_raw).with(anything, anything, anything, anything, anything).and_return(nil)
allow(handler).to receive(:remap).and_return(nil)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
config = instance_double(BeEF::Core::Configuration)
allow(config).to receive(:get).with(anything).and_return('127.0.0.1')
allow(BeEF::Core::Configuration).to receive(:instance).and_return(config)
allow(IO).to receive(:popen).and_return(StringIO.new(''))
# Many pre_send implementations expect @datastore to be an Array of option hashes (name/value)
pre_send_datastore = [
{ 'name' => 'payload', 'value' => 'cmd' },
{ 'name' => 'payload_handler', 'value' => 'http://127.0.0.1:8080' },
{ 'name' => 'extension_name', 'value' => 'TestExt' },
{ 'name' => 'xpi_name', 'value' => 'test.xpi' },
{ 'name' => 'lport', 'value' => '4444' }
]
instance = build_command_instance(described_class, pre_send_datastore)
expect { run_pre_send(instance) }.not_to raise_error
end
end
end
if described_class.method_defined?(:post_execute)
describe '#post_execute' do
it 'runs without error' do
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:remap)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
instance = build_command_instance(described_class, 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0')
expect { run_post_execute(instance) }.not_to raise_error
end
if BRANCH_COVERAGE_SOCENG[branch_key]
it 'runs branch path with realistic datastore' do
branch = BRANCH_COVERAGE_SOCENG[branch_key]
handler = instance_double('AssetHandler')
allow(handler).to receive(:unbind)
allow(handler).to receive(:bind)
allow(handler).to receive(:remap)
allow(BeEF::Core::NetworkStack::Handlers::AssetHandler).to receive(:instance).and_return(handler)
instance = build_command_instance(described_class, branch[:datastore])
expect { run_post_execute(instance) }.not_to raise_error
end
end
end
end
end
end
+207 -7
View File
@@ -3,14 +3,214 @@
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Coverage must start before loading application code.
require 'simplecov'
SimpleCov.start do
add_filter '/spec/'
add_group 'Core', 'core'
add_group 'Extensions', 'extensions'
add_group 'Modules', 'modules'
track_files '{core,extensions,modules}/**/*.rb'
require 'simplecov' and SimpleCov.start if ENV['COVERAGE']
# Branch-coverage datastore/config fixtures for the dynamic module spec loaders.
# Each spec file under spec/beef/modules/* uses BeefTestConfig.branch_coverage_for(:component)
# to drive an extra post_execute example with realistic data, hitting conditional branches
# that the generic smoke test misses.
#
# Centralising these here:
# - removes duplicate top-level BRANCH_COVERAGE constant definitions across spec files
# (which previously triggered "already initialized constant" warnings and silent overrides),
# - makes the test data discoverable from one place, and
# - matches what spec/README.md claims.
module BeefTestConfig
PRE_SEND_SKIP = {
# Modules whose pre_send needs Zip, real filesystem access, or speech tooling we
# don't want to invoke in unit tests; the dynamic loader skips the generic pre_send
# example for these described_class names.
social_engineering: %w[
Firefox_extension_bindshell Firefox_extension_dropper Firefox_extension_reverse_shell
Text_to_voice Ui_abuse_ie
].freeze
}.freeze
def self.pre_send_skip_for(component)
PRE_SEND_SKIP[component] || []
end
def self.branch_coverage_for(component)
case component
when :browser
{
'modules/browser/detect_wmp' => { datastore: { 'results' => 'wmp=Yes', 'wmp' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } },
'modules/browser/detect_vlc' => { datastore: { 'results' => 'vlc=Yes', 'vlc' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } },
'modules/browser/detect_office' => { datastore: { 'results' => 'office=Office 2016', 'result' => '', 'cid' => '0', 'beefhook' => '1' } },
'modules/browser/detect_foxit' => { datastore: { 'results' => 'foxit=Yes', 'result' => '', 'cid' => '0', 'beefhook' => '1' } },
'modules/browser/detect_activex' => { datastore: { 'results' => 'activex=Yes', 'activex' => '', 'result' => '', 'cid' => '0', 'beefhook' => '1' } }
}
when :exploits
{
'modules/exploits/vtiger_crm_upload_exploit' => { datastore: { 'result' => 'ok', 'cid' => '0' } },
'modules/exploits/router/asus_rt_n12e_get_info' => {
datastore: {
'result' => '', 'results' => 'ip=192.168.1.1&clients=192.168.1.2,00:11:22:33:44:55&wanip=1.2.3.4&netmask=255.255.255.0&gateway=192.168.1.1&dns=8.8.8.8 8.8.4.4',
'beefhook' => '1', 'cid' => '0'
},
config_get: { 'beef.extension.network.enable' => true }
}
}
when :host
{
'modules/host/detect_cups' => {
datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=631&cups=Installed', 'beefhook' => '1', 'result' => '', 'cid' => '0' },
config_get: { 'beef.extension.network.enable' => true }
},
'modules/host/detect_airdroid' => {
datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=8888&airdroid=Installed', 'airdroid' => '', 'beefhook' => '1', 'result' => '', 'cid' => '0' },
config_get: { 'beef.extension.network.enable' => true }
},
'modules/host/get_internal_ip_java' => {
datastore: { 'results' => '192.168.1.1', 'Result' => '', 'result' => '', 'beefhook' => '1', 'cid' => '0' },
config_get: { 'beef.extension.network.enable' => true }
},
'modules/host/get_internal_ip_webrtc' => {
datastore: { 'results' => 'IP is 192.168.1.1', 'Result' => '', 'result' => '', 'beefhook' => '1', 'cid' => '0' },
config_get: { 'beef.extension.network.enable' => true }
}
}
when :misc
{
'modules/misc/wordpress_post_auth_rce' => { datastore: { 'result' => 'ok', 'cid' => '0' } }
}
when :network
{
'modules/network/port_scanner' => {
datastore: { 'results' => 'ip=1.2.3.4&port=HTTP: Port 80 is OPEN Apache', 'beefhook' => '1', 'result' => '', 'port' => '', 'cid' => '0' },
config_get: { 'beef.extension.network.enable' => true }
},
'modules/network/ping_sweep' => {
datastore: { 'results' => 'ip=192.168.1.1&ping=10ms', 'beefhook' => '1', 'result' => '', 'cid' => '0' },
config_get: { 'beef.extension.network.enable' => true }
},
'modules/network/ping_sweep_ff' => {
datastore: { 'results' => 'host=192.168.1.1 is alive', 'beefhook' => '1', 'result' => '', 'host' => '', 'cid' => '0' },
config_get: { 'beef.extension.network.enable' => true }
},
'modules/network/get_http_servers' => {
datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&url=http://1.2.3.4/', 'beefhook' => '1', 'result' => '', 'url' => '', 'cid' => '0' },
config_get: { 'beef.extension.network.enable' => true }
},
'modules/network/cross_origin_scanner_cors' => {
datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&status=200', 'beefhook' => '1', 'result' => '', 'cid' => '0' },
config_get: { 'beef.extension.network.enable' => true }
},
'modules/network/detect_burp' => {
datastore: { 'results' => 'has_burp=true&response=PROXY 127.0.0.1:8080', 'beefhook' => '1', 'result' => '', 'cid' => '0' },
config_get: { 'beef.extension.network.enable' => true }
},
'modules/network/get_ntop_network_hosts' => {
datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=3000&data={"hostNumIpAddress":"192.168.1.1"}', 'beefhook' => '1', 'result' => '', 'cid' => '0' },
config_get: { 'beef.extension.network.enable' => true }
},
'modules/network/internal_network_fingerprinting' => {
datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&discovered=Apache&url=http://test/', 'beefhook' => '1', 'result' => '', 'discovered' => '', 'url' => '', 'cid' => '0' },
config_get: { 'beef.extension.network.enable' => true }
},
'modules/network/get_proxy_servers_wpad' => [
{ datastore: { 'results' => 'proxies=PROXY 192.168.1.1:3128', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } },
{ datastore: { 'results' => 'proxies=SOCKS 192.168.1.1:1080', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }
],
'modules/network/jslanscanner' => [
{ datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&service=HTTP', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } },
{ datastore: { 'results' => 'ip=1.2.3.4&device=Router', 'beefhook' => '1', 'result' => '', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }
],
'modules/network/fetch_port_scanner' => {
datastore: { 'result' => 'ok', 'beefhook' => '1', 'cid' => '0' },
config_get: { 'beef.extension.network.enable' => true }
},
'modules/network/cross_origin_scanner_flash' => [
{ datastore: { 'results' => 'ip=1.2.3.4&status=alive', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } },
{ datastore: { 'results' => 'proto=http&ip=1.2.3.4&port=80&title=Apache', 'result' => '', 'beefhook' => '1', 'cid' => '0' }, config_get: { 'beef.extension.network.enable' => true } }
]
}
when :social_engineering
{
'modules/social_engineering/fake_lastpass' => { datastore: { 'meta' => 'KILLFRAME', 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0' } },
'modules/social_engineering/fake_evernote_clipper' => { datastore: { 'meta' => 'KILLFRAME', 'result' => '', 'results' => '', 'answer' => '', 'cid' => '0' } }
}
else
{}
end
end
end
# Returns [total_lines, covered_lines] for a group, or nil if no group.
def group_coverage(r, group_name)
group = r.groups[group_name]
return nil unless group&.respond_to?(:each)
total_lines = 0
covered_lines = 0
group.each do |src_file|
total_lines += src_file.lines_of_code
covered_lines += src_file.covered_lines.compact.size
end
[total_lines, covered_lines]
end
# Print one group's coverage (skip forked children: very low coverage or zero).
def print_group_coverage(r, group_name, color: "\e[36m")
total_lines, covered_lines = group_coverage(r, group_name)
return unless total_lines && total_lines.positive?
return if covered_lines.zero?
pct = covered_lines.to_f / total_lines * 100
# Skip noise from forked children (e.g. 0.03% Core).
return if pct < 1.0
puts "#{color}#{group_name} coverage: #{pct.round(2)}% (#{covered_lines} / #{total_lines} lines)\e[0m"
end
# Only print from the main process (skip forked children with tiny coverage).
MIN_COVERAGE_PCT = 5.0
def coverage_from_main_process?(r, focus)
case focus
when 'all'
%w[Core Extensions Modules].any? do |name|
total, covered = group_coverage(r, name)
next false unless total && total.positive?
(covered.to_f / total * 100) >= MIN_COVERAGE_PCT
end
when 'core'
total, covered = group_coverage(r, 'Core')
total && total.positive? && (covered.to_f / total * 100) >= MIN_COVERAGE_PCT
when 'extensions'
total, covered = group_coverage(r, 'Extensions')
total && total.positive? && (covered.to_f / total * 100) >= MIN_COVERAGE_PCT
when 'modules', nil
total, covered = group_coverage(r, 'Modules')
total && total.positive? && (covered.to_f / total * 100) >= MIN_COVERAGE_PCT
else
false
end
end
at_exit do
if defined?(SimpleCov) && SimpleCov.respond_to?(:result) && (r = SimpleCov.result)
focus = ENV['COVERAGE']
next unless coverage_from_main_process?(r, focus)
case focus
when 'core'
print_group_coverage(r, 'Core')
when 'extensions'
print_group_coverage(r, 'Extensions')
when 'modules'
print_group_coverage(r, 'Modules')
when 'all'
puts
print_group_coverage(r, 'Core')
print_group_coverage(r, 'Extensions')
print_group_coverage(r, 'Modules')
else
print_group_coverage(r, 'Modules')
end
end
end
# Set external and internal character encodings to UTF-8
+59
View File
@@ -0,0 +1,59 @@
#
# Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
# Helpers for unit specs of command modules (modules/*/module.rb).
# Use with stubbed config; no REST/API or real server.
#
module ModuleSpecHelper
#
# Build a command instance for testing post_execute (or other instance methods).
# Uses allocate so we avoid Command#initialize reading from config.
# Set @datastore before calling post_execute; results are stored in @results via save().
#
# @param klass [Class] The command module class (e.g. Test_beef_debug)
# @param datastore [Hash] Data to set on @datastore (simulates callback data)
# @return [Object] Instance with @datastore set; call post_execute then read instance_variable_get(:@results)
#
def build_command_instance(klass, datastore = {})
instance = klass.allocate
instance.instance_variable_set(:@datastore, datastore)
instance
end
#
# Call post_execute on a command instance and return the saved results.
# Use after build_command_instance(klass, datastore).
#
# @param instance [Object] Command instance from build_command_instance
# @return [Hash, nil] The value passed to save() (stored in @results)
#
def run_post_execute(instance)
instance.post_execute
instance.instance_variable_get(:@results)
end
#
# Call pre_send on a command instance (e.g. before sending the command to the hooked browser).
# Use after build_command_instance(klass, datastore). Stub AssetHandler, Configuration, etc. before calling.
#
# @param instance [Object] Command instance from build_command_instance
#
def run_pre_send(instance)
instance.pre_send
end
end
RSpec.configure do |config|
config.include ModuleSpecHelper
# Stub Kernel.sleep for all module specs so modules that call sleep
# (e.g. Irc_nat_pinning's post_execute sleep 30) don't slow the suite.
config.before(:each) do |example|
if example.metadata[:file_path]&.include?('spec/beef/modules')
allow(Kernel).to receive(:sleep)
end
end
end