Skip to content

Add unit tests for core protocol libraries and SOCKS proxy (#1384, #1385)#1451

Open
get-D wants to merge 3 commits intoOWASP:masterfrom
get-D:make/tests
Open

Add unit tests for core protocol libraries and SOCKS proxy (#1384, #1385)#1451
get-D wants to merge 3 commits intoOWASP:masterfrom
get-D:make/tests

Conversation

@get-D
Copy link
Copy Markdown

@get-D get-D commented Mar 25, 2026

Description

This PR adds a comprehensive set of unit tests for the core protocol libraries and the SOCKS proxy module, significantly improving test coverage in the core engine.

All tests are designed to run fully offline by using Python’s unittest.mock to simulate network interactions. This ensures consistent and reliable test results without requiring external connections.

This work addresses Issue #1384 (unit tests for core protocol libraries) and Issue #1385 (unit tests for the SOCKS proxy). It also contributes toward the GSoC 2026 goal of achieving ~85% test coverage.


What’s Included

This PR introduces 7 new test files with a total of 55 unit tests, all passing successfully:

  • test_socks_proxy.py (10 tests)
    Covers different getaddrinfo formats and set_socks_proxy behavior, including SOCKS4/5 configurations (with and without authentication) and fallback scenarios. Uses mocking to safely handle local imports.

  • test_ftp.py (9 tests)
    Tests FTP and FTPS libraries, including successful logins, authentication failures, connection resets, and TLS handling.

  • test_pop3.py (9 tests)
    Covers POP3/POP3S behavior, including authentication errors, dropped connections, and SSL-related logic.

  • test_smtp.py (8 tests)
    Validates SMTP/SMTPS flows, especially ensuring starttls() is correctly executed before authentication.

  • test_ssh.py (6 tests)
    Tests authentication strategy selection (password vs NoneAuth) and verifies consistent use of AutoAddPolicy.

  • test_base.py (9 tests)
    Covers default behaviors in BaseLibrary and validates filter_large_content() logic, including proper truncation at word boundaries.

  • test_telnet.py (4 tests)
    Tests Telnet connections, including success cases, timeouts, and connection drops.


Test Results

All tests pass successfully on supported Python versions.

Tested locally using Poetry with Python 3.12
(Note: telnet-related tests work on Python 3.12, before telnetlib is removed in Python 3.13.)

55 passed in 4.88s

Impact

  • Improves confidence in core protocol implementations
  • Ensures stable, offline test execution
  • Moves the project closer to its test coverage goals
  • Provides a solid foundation for future testing contributions

Fixes #1384
Fixes #1385

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

Summary by CodeRabbit

  • Tests
    • Added coverage for base library/engine behaviors, including content filtering/truncation and default library placeholders.
    • Added FTP/FTPS, POP3/POP3S, SMTP/SMTPS, SSH, and Telnet brute-force success and error-path tests.
    • Added SOCKS proxy configuration and address-resolution tests.
    • Minor test lint comment adjustment for an SMB password test.

Walkthrough

Adds multiple pytest test modules covering core libraries: Base, FTP/FTPS, POP3/POP3S, SMTP/SMTPS, SSH, Telnet, and socks_proxy. Tests use mocks to validate brute-force/client flows, error propagation, content-filtering behavior, and engine↔library wiring. No production code changed.

Changes

Cohort / File(s) Summary
Base tests
tests/core/lib/test_base.py
New tests for BaseEngine.filter_large_content and BaseLibrary defaults, covering truncation thresholds, whitespace boundary handling, deterministic translation patching, and custom filter_rate behavior.
FTP / FTPS tests
tests/core/lib/test_ftp.py
Adds tests mocking FtpLibrary/FtpsLibrary clients to validate client construction with timeout, connect/login/close call sequence, returned credential dicts, and propagation of ftplib.error_perm and ConnectionRefusedError. Asserts FtpsLibrary defaults and engine wiring.
POP3 / POP3S tests
tests/core/lib/test_pop3.py
Adds tests mocking Pop3Library/Pop3sLibrary clients to assert constructor args, user()/pass_() usage, guaranteed quit() calls, returned credential mapping, and propagation of poplib.error_proto and ConnectionRefusedError. Verifies Pop3sLibrary default client and engine wiring.
SMTP / SMTPS tests
tests/core/lib/test_smtp.py
Adds tests mocking SMTP/SMTPS clients to verify client construction (host, port, timeout), login()/close(), starttls() usage for SMTPS, returned results, and propagation of smtplib exceptions and ConnectionRefusedError. Confirms engine-library attributes.
SSH tests
tests/core/lib/test_ssh.py
Adds tests mocking SSH client to confirm brute_force() forwards hostname/port/username, selects auth strategy (password vs none), sets AutoAddPolicy, propagates ConnectionRefusedError, and that SshEngine.library references SshLibrary.
Telnet tests
tests/core/lib/test_telnet.py
Adds tests mocking Telnet client flow: constructor args, prompt reads, writing username/password with newline, single close() call, returned credential dict, and propagation of ConnectionRefusedError/TimeoutError. Verifies engine wiring.
Socks proxy tests
tests/core/test_socks_proxy.py
Adds tests for getaddrinfo() to return a single AF_INET/SOCK_STREAM tuple and for set_socks_proxy() to return defaults for None/empty, configure socks.set_default_proxy for socks5:///socks4:// (with optional auth), and return (socks.socksocket, getaddrinfo).
SMB test tweak
tests/core/lib/test_smb.py
Minor change: appended # noqa: S105 to hardcoded PASSWORD constant (no logic changes).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main additions: unit tests for core protocol libraries and SOCKS proxy with specific issue references.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the test files, coverage scope, and test results.
Linked Issues check ✅ Passed The PR comprehensively addresses both linked issues: Issue #1384 covers protocol libraries (base, FTP, POP3, SMTP, SSH, telnet) and Issue #1385 covers SOCKS proxy, all with mocked network calls and multiple test cases per protocol.
Out of Scope Changes check ✅ Passed All changes are scoped to adding new test files for protocol libraries and SOCKS proxy; only one line changed in existing test_smb.py (a noqa comment), which is a minor linting adjustment directly related to test quality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
tests/core/test_socks_proxy.py (1)

75-102: Add return value assertions for completeness.

Both SOCKS4 tests only verify that set_default_proxy is called correctly but don't assert the return values (sock and addr_info). For consistency with the SOCKS5 tests and to ensure the function returns the expected socket/getaddrinfo pair, add these assertions.

Proposed fix
 class TestSetSocksProxySocks4:
     def test_socks4_no_auth(self):
         mock_socks = MagicMock()
         mock_socks.SOCKS5 = 2
         mock_socks.SOCKS4 = 1

         with patch.dict(sys.modules, {"socks": mock_socks}):
-            set_socks_proxy("socks4://10.0.0.1:1080")
+            sock, addr_info = set_socks_proxy("socks4://10.0.0.1:1080")

         mock_socks.set_default_proxy.assert_called_once_with(
             1,  # SOCKS4
             "10.0.0.1",
             1080,
         )
+        assert sock is mock_socks.socksocket
+        assert addr_info is getaddrinfo

     def test_no_scheme_defaults_to_socks4(self):
         mock_socks = MagicMock()
         mock_socks.SOCKS5 = 2
         mock_socks.SOCKS4 = 1

         with patch.dict(sys.modules, {"socks": mock_socks}):
-            set_socks_proxy("192.168.1.1:1080")
+            sock, addr_info = set_socks_proxy("192.168.1.1:1080")

         mock_socks.set_default_proxy.assert_called_once_with(
             1,  # SOCKS4
             "192.168.1.1",
             1080,
         )
+        assert sock is mock_socks.socksocket
+        assert addr_info is getaddrinfo
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/test_socks_proxy.py` around lines 75 - 102, The SOCKS4 tests call
set_socks_proxy but don't assert its return values; update
TestSetSocksProxySocks4.test_socks4_no_auth and
test_no_scheme_defaults_to_socks4 to capture the tuple returned by
set_socks_proxy (e.g., sock, addr_info) and add assertions that sock is the
mocked socket object and addr_info matches the expected getaddrinfo result
(similar to the SOCKS5 tests), using the existing mock_socks to supply the
socket and getaddrinfo behavior so the tests verify both side effects and return
values from set_socks_proxy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/core/lib/test_ftp.py`:
- Line 43: Append an explicit lint suppression to the intentional failing FTP
call by adding a trailing "# noqa: S321" to the mock setup line
(mock_conn.login.side_effect = ftplib.error_perm("530 Login incorrect")) in
tests/core/lib/test_ftp.py so the test-only FTP security lint hit is ignored.

In `@tests/core/lib/test_ssh.py`:
- Line 12: The hardcoded credential constant PASSWORD triggers Ruff S105 in
tests; suppress the warning by adding a ruff noqa for S105 to that declaration.
Update the PASSWORD assignment (symbol: PASSWORD) to include an inline
suppression comment like "# noqa: S105" (or add "ruff: noqa: S105" at file top)
so the test keeps the literal but CI linting ignores S105.

In `@tests/core/test_socks_proxy.py`:
- Around line 57-72: In test_socks5_with_auth, addr_info is currently unpacked
but never used and the test lacks the same addr_info assertion present in
test_socks5_no_auth; after calling set_socks_proxy in test_socks5_with_auth, add
an assertion that addr_info is getaddrinfo (i.e. assert addr_info is
getaddrinfo) to both use the variable and mirror the other test, leaving the
hardcoded test password as-is.

---

Nitpick comments:
In `@tests/core/test_socks_proxy.py`:
- Around line 75-102: The SOCKS4 tests call set_socks_proxy but don't assert its
return values; update TestSetSocksProxySocks4.test_socks4_no_auth and
test_no_scheme_defaults_to_socks4 to capture the tuple returned by
set_socks_proxy (e.g., sock, addr_info) and add assertions that sock is the
mocked socket object and addr_info matches the expected getaddrinfo result
(similar to the SOCKS5 tests), using the existing mock_socks to supply the
socket and getaddrinfo behavior so the tests verify both side effects and return
values from set_socks_proxy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 59df6d74-4620-4bcd-8260-7d9a37a399ae

📥 Commits

Reviewing files that changed from the base of the PR and between 74d89e2 and 215c8a5.

📒 Files selected for processing (7)
  • tests/core/lib/test_base.py
  • tests/core/lib/test_ftp.py
  • tests/core/lib/test_pop3.py
  • tests/core/lib/test_smtp.py
  • tests/core/lib/test_ssh.py
  • tests/core/lib/test_telnet.py
  • tests/core/test_socks_proxy.py

Signed-off-by: get-D <divyatwa5@gmail.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/core/test_socks_proxy.py (1)

73-100: Add return value assertions to SOCKS4 tests for completeness.

Both SOCKS4 tests call set_socks_proxy but discard the return value. Unlike the SOCKS5 tests which properly assert both sock and addr_info, these tests only verify that set_default_proxy was called correctly but don't validate that the function returns (socks.socksocket, getaddrinfo).

Since production code (see nettacker/core/app.py) relies on unpacking this 2-tuple, validating the return value is important.

Proposed fix
     def test_socks4_no_auth(self):
         mock_socks = MagicMock()
         mock_socks.SOCKS5 = 2
         mock_socks.SOCKS4 = 1

         with patch.dict(sys.modules, {"socks": mock_socks}):
-            set_socks_proxy("socks4://10.0.0.1:1080")
+            sock, addr_info = set_socks_proxy("socks4://10.0.0.1:1080")

         mock_socks.set_default_proxy.assert_called_once_with(
             1,  # SOCKS4
             "10.0.0.1",
             1080,
         )
+        assert sock is mock_socks.socksocket
+        assert addr_info is getaddrinfo

     def test_no_scheme_defaults_to_socks4(self):
         mock_socks = MagicMock()
         mock_socks.SOCKS5 = 2
         mock_socks.SOCKS4 = 1

         with patch.dict(sys.modules, {"socks": mock_socks}):
-            set_socks_proxy("192.168.1.1:1080")
+            sock, addr_info = set_socks_proxy("192.168.1.1:1080")

         mock_socks.set_default_proxy.assert_called_once_with(
             1,  # SOCKS4
             "192.168.1.1",
             1080,
         )
+        assert sock is mock_socks.socksocket
+        assert addr_info is getaddrinfo
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/test_socks_proxy.py` around lines 73 - 100, The SOCKS4 tests call
set_socks_proxy but never assert its return; update both
TestSetSocksProxySocks4.test_socks4_no_auth and
test_no_scheme_defaults_to_socks4 to capture the return value from
set_socks_proxy (e.g., sock, addr_info = set_socks_proxy(...)) and add
assertions that the first element is the mocked socks socket
(mock_socks.socksocket) and the second is the address-resolution callable used
by the code (addr_info is getaddrinfo or callable), while keeping the existing
mock_socks.set_default_proxy assertions intact so the tests validate the full
(socket, getaddrinfo) 2-tuple contract from set_socks_proxy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/core/test_socks_proxy.py`:
- Around line 73-100: The SOCKS4 tests call set_socks_proxy but never assert its
return; update both TestSetSocksProxySocks4.test_socks4_no_auth and
test_no_scheme_defaults_to_socks4 to capture the return value from
set_socks_proxy (e.g., sock, addr_info = set_socks_proxy(...)) and add
assertions that the first element is the mocked socks socket
(mock_socks.socksocket) and the second is the address-resolution callable used
by the code (addr_info is getaddrinfo or callable), while keeping the existing
mock_socks.set_default_proxy assertions intact so the tests validate the full
(socket, getaddrinfo) 2-tuple contract from set_socks_proxy.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5835824d-7280-4dd4-8017-180e773a491f

📥 Commits

Reviewing files that changed from the base of the PR and between 215c8a5 and 84a9d45.

📒 Files selected for processing (1)
  • tests/core/test_socks_proxy.py

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
tests/core/lib/test_ftp.py (1)

43-43: ⚠️ Potential issue | 🟡 Minor

Add explicit Ruff suppression for intentional FTP error-path mock.

Line 43 still triggers S321; mark this as intentional test-only FTP usage to keep lint green.

🔧 Minimal fix
-        mock_conn.login.side_effect = ftplib.error_perm("530 Login incorrect")
+        mock_conn.login.side_effect = ftplib.error_perm(  # noqa: S321 - intentional FTP test path
+            "530 Login incorrect"
+        )

As per coding guidelines "Line length: maximum 99 characters (enforced by ruff, ruff-format, and isort with black profile)" and this unresolved Ruff finding currently blocks that standard.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/lib/test_ftp.py` at line 43, The test intentionally sets
mock_conn.login.side_effect = ftplib.error_perm("530 Login incorrect") to
exercise an FTP error path, but ruff flags S321; add an inline ruff suppression
on that statement (e.g., append a noqa for S321) so the linter knows this use is
intentional for tests and keep the rest of the test unchanged; target the
mock_conn.login.side_effect line when applying the suppression.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@tests/core/lib/test_ftp.py`:
- Line 43: The test intentionally sets mock_conn.login.side_effect =
ftplib.error_perm("530 Login incorrect") to exercise an FTP error path, but ruff
flags S321; add an inline ruff suppression on that statement (e.g., append a
noqa for S321) so the linter knows this use is intentional for tests and keep
the rest of the test unchanged; target the mock_conn.login.side_effect line when
applying the suppression.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a413b84b-dd30-424f-aa63-1010ccff831d

📥 Commits

Reviewing files that changed from the base of the PR and between 84a9d45 and 343e409.

📒 Files selected for processing (7)
  • tests/core/lib/test_ftp.py
  • tests/core/lib/test_pop3.py
  • tests/core/lib/test_smb.py
  • tests/core/lib/test_smtp.py
  • tests/core/lib/test_ssh.py
  • tests/core/lib/test_telnet.py
  • tests/core/test_socks_proxy.py
✅ Files skipped from review due to trivial changes (2)
  • tests/core/lib/test_smb.py
  • tests/core/lib/test_telnet.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/core/lib/test_ssh.py
  • tests/core/lib/test_smtp.py
  • tests/core/lib/test_pop3.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add unit tests for socks_proxy.py [Testing] Add unit tests for core protocol libraries (HTTP, SSH, FTP, SMTP, Telnet, POP3)

1 participant