[GEMINI] Organize test suite to mirror new ctf.cli and ctf.forensics package structures
This commit is contained in:
121
tests/cli/test_basic.py
Normal file
121
tests/cli/test_basic.py
Normal file
@@ -0,0 +1,121 @@
|
||||
# tests/cli/test_basic.py
|
||||
# {{{ imports
|
||||
from click.testing import CliRunner
|
||||
from ctf.cli.basic import basic_group
|
||||
from ctf.config import Config
|
||||
# }}}
|
||||
|
||||
# {{{ test_basic_test_cli
|
||||
def test_basic_test_cli():
|
||||
"""
|
||||
Verifies that 'basic test' runs successfully and outputs 'hello from test'.
|
||||
"""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(basic_group, ["test"])
|
||||
assert result.exit_code == 0
|
||||
assert "hello from test" in result.output
|
||||
# }}}
|
||||
|
||||
# {{{ test_basic_greet_cli
|
||||
def test_basic_greet_cli():
|
||||
"""
|
||||
Verifies that 'basic greet' runs successfully and greets the name argument.
|
||||
"""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(basic_group, ["greet", "Alice"])
|
||||
assert result.exit_code == 0
|
||||
assert "hello Alice" in result.output
|
||||
# }}}
|
||||
|
||||
# {{{ test_basic_set_flag_format_cli
|
||||
def test_basic_set_flag_format_cli():
|
||||
"""
|
||||
Verifies that 'basic set-flag-format' CLI command writes the pattern to config.toml.
|
||||
"""
|
||||
runner = CliRunner()
|
||||
|
||||
cfg = Config()
|
||||
assert cfg.data["Competition"]["flag_format"] == r"picoCTF\{.*\}"
|
||||
|
||||
result = runner.invoke(basic_group, ["set-flag-format", "TEST_FLAG{[a-z]+}"])
|
||||
assert result.exit_code == 0
|
||||
assert "Flag format set to" in result.output
|
||||
|
||||
updated_cfg = Config()
|
||||
assert updated_cfg.data["Competition"]["flag_format"] == "TEST_FLAG{[a-z]+}"
|
||||
# }}}
|
||||
|
||||
# {{{ test_basic_set_flag_format_original_option_cli
|
||||
def test_basic_set_flag_format_original_option_cli():
|
||||
"""
|
||||
Verifies that 'basic set-flag-format' with --original/-o option prompts for selection and writes it.
|
||||
"""
|
||||
runner = CliRunner()
|
||||
|
||||
result = runner.invoke(basic_group, ["set-flag-format", "-o", "SKY-1111-000"], input="2\n")
|
||||
assert result.exit_code == 0
|
||||
assert "Select a pattern index" in result.output
|
||||
assert "Flag format set to" in result.output
|
||||
|
||||
updated_cfg = Config()
|
||||
selected_pattern = updated_cfg.data["Competition"]["flag_format"]
|
||||
assert len(selected_pattern) > 0
|
||||
# }}}
|
||||
|
||||
# {{{ test_basic_set_flag_format_validation_cli
|
||||
def test_basic_set_flag_format_validation_cli():
|
||||
"""
|
||||
Verifies validation rules:
|
||||
- Specifying both PATTERN and --original should fail.
|
||||
- Specifying neither PATTERN nor --original should fail.
|
||||
- Specifying empty/whitespace-only original flag should fail.
|
||||
"""
|
||||
runner = CliRunner()
|
||||
|
||||
# Both specified
|
||||
result1 = runner.invoke(basic_group, ["set-flag-format", "PAT", "-o", "SKY-1111-000"])
|
||||
assert result1.exit_code != 0
|
||||
assert "Cannot specify both PATTERN and --original/-o option." in result1.output
|
||||
|
||||
# Neither specified
|
||||
result2 = runner.invoke(basic_group, ["set-flag-format"])
|
||||
assert result2.exit_code != 0
|
||||
assert "Either PATTERN positional argument or --original/-o option must be specified." in result2.output
|
||||
|
||||
# Empty original
|
||||
result3 = runner.invoke(basic_group, ["set-flag-format", "-o", " "])
|
||||
assert result3.exit_code != 0
|
||||
assert "Original flag cannot be empty or whitespace only." in result3.output
|
||||
# }}}
|
||||
|
||||
# {{{ test_basic_set_competition_cli
|
||||
def test_basic_set_competition_cli():
|
||||
"""
|
||||
Verifies that 'basic set-competition' updates the competition name in config.toml.
|
||||
"""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(basic_group, ["set-competition", "CyberCTF2026"])
|
||||
assert result.exit_code == 0
|
||||
assert "Competition name set to: CyberCTF2026" in result.output
|
||||
|
||||
updated_cfg = Config()
|
||||
assert updated_cfg.data["Competition"]["competition"] == "CyberCTF2026"
|
||||
# }}}
|
||||
|
||||
# {{{ test_basic_set_flag_format_warning_cli
|
||||
def test_basic_set_flag_format_warning_cli():
|
||||
"""
|
||||
Verifies that if competition name is set and not present in the example flag,
|
||||
set-flag-format outputs a warning.
|
||||
"""
|
||||
runner = CliRunner()
|
||||
|
||||
# Set competition name first
|
||||
cfg = Config()
|
||||
cfg.data["Competition"]["competition"] = "SECURE"
|
||||
cfg.save(cfg.data)
|
||||
|
||||
result = runner.invoke(basic_group, ["set-flag-format", "-o", "CTF{easy_flag_123}"], input="1\n")
|
||||
assert result.exit_code == 0
|
||||
assert "Warning: Current competition name 'SECURE' was not found in the example flag." in result.output
|
||||
# }}}
|
||||
97
tests/cli/test_flag.py
Normal file
97
tests/cli/test_flag.py
Normal file
@@ -0,0 +1,97 @@
|
||||
# tests/cli/test_flag.py
|
||||
# {{{ imports
|
||||
import sys
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from click.testing import CliRunner
|
||||
from unittest.mock import patch
|
||||
from ctf.config import Config
|
||||
# }}}
|
||||
|
||||
# {{{ test_flag_cmd_cli
|
||||
def test_flag_cmd_cli():
|
||||
"""
|
||||
Verifies the ctf flag command outputs last_flag with and without --plain.
|
||||
"""
|
||||
from ctf.cli import cli
|
||||
|
||||
cfg = Config()
|
||||
cfg.data["Competition"]["last_flag"] = "flag{test_cli_flag}"
|
||||
cfg.save(cfg.data)
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
# Test standard flag command output
|
||||
result_std = runner.invoke(cli, ["flag"])
|
||||
assert result_std.exit_code == 0
|
||||
assert "Last detected flag: flag{test_cli_flag}" in result_std.output
|
||||
|
||||
# Test plain flag command output
|
||||
result_plain = runner.invoke(cli, ["flag", "--plain"])
|
||||
assert result_plain.exit_code == 0
|
||||
assert result_plain.output.strip() == "flag{test_cli_flag}"
|
||||
# }}}
|
||||
|
||||
# {{{ test_flag_cmd_exemption
|
||||
def test_flag_cmd_exemption(capsys):
|
||||
"""
|
||||
Verifies that running ctf flag is exempted from triggering the stdout flag warning box.
|
||||
"""
|
||||
from ctf.main import main
|
||||
|
||||
cfg = Config()
|
||||
cfg.data["Competition"]["flag_format"] = r"flag\{[a-z_]+\}"
|
||||
cfg.data["Competition"]["last_flag"] = "flag{test_exempt_flag}"
|
||||
cfg.save(cfg.data)
|
||||
|
||||
with patch.object(sys, "argv", ["ctf", "flag"]):
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
main()
|
||||
assert exc_info.value.code == 0
|
||||
|
||||
captured = capsys.readouterr()
|
||||
# Verify that the warning box is NOT in stdout
|
||||
assert "Potential flag(s) detected in command output" not in captured.out
|
||||
# Verify that the actual flag info IS printed
|
||||
assert "Last detected flag: flag{test_exempt_flag}" in captured.out
|
||||
# }}}
|
||||
|
||||
# {{{ test_flag_cmd_list_format
|
||||
def test_flag_cmd_list_format():
|
||||
"""
|
||||
Verifies that ctf flag --list / -l prints the current flag format.
|
||||
"""
|
||||
from ctf.cli import cli
|
||||
|
||||
cfg = Config()
|
||||
cfg.data["Competition"]["flag_format"] = "TEST_FORMAT{[a-z]+}"
|
||||
cfg.save(cfg.data)
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
# Test standard output
|
||||
result = runner.invoke(cli, ["flag", "-l"])
|
||||
assert result.exit_code == 0
|
||||
assert "Current flag format: TEST_FORMAT{[a-z]+}" in result.output
|
||||
|
||||
# Test plain output
|
||||
result_plain = runner.invoke(cli, ["flag", "-l", "-p"])
|
||||
assert result_plain.exit_code == 0
|
||||
assert result_plain.output.strip() == "TEST_FORMAT{[a-z]+}"
|
||||
# }}}
|
||||
|
||||
# {{{ test_flag_cmd_set_format
|
||||
def test_flag_cmd_set_format():
|
||||
"""
|
||||
Verifies that ctf flag --set / -s updates the flag format in config.toml.
|
||||
"""
|
||||
from ctf.cli import cli
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["flag", "-s", "NEW_FLAG_FORMAT{[0-9]+}"])
|
||||
assert result.exit_code == 0
|
||||
assert "Flag format set to: NEW_FLAG_FORMAT{[0-9]+}" in result.output
|
||||
|
||||
updated_cfg = Config()
|
||||
assert updated_cfg.data["Competition"]["flag_format"] == "NEW_FLAG_FORMAT{[0-9]+}"
|
||||
# }}}
|
||||
204
tests/cli/test_forensics.py
Normal file
204
tests/cli/test_forensics.py
Normal file
@@ -0,0 +1,204 @@
|
||||
# tests/cli/test_forensics.py
|
||||
# {{{ imports
|
||||
import struct
|
||||
from pathlib import Path
|
||||
from click.testing import CliRunner
|
||||
from ctf.cli.forensics import forensics_group
|
||||
# }}}
|
||||
|
||||
# Define the persistent test environment path
|
||||
TEST_ENV = Path("tests/env")
|
||||
|
||||
# {{{ test_forensics_metadata_cli_success
|
||||
def test_forensics_metadata_cli_success():
|
||||
"""
|
||||
Verifies that 'forensics metadata' successfully runs via the CLI and prints
|
||||
the metadata in a formatted table.
|
||||
"""
|
||||
runner = CliRunner()
|
||||
test_file = TEST_ENV / "test_file.png"
|
||||
with open(test_file, "wb") as f:
|
||||
f.write(b"\x89PNG\r\n\x1a\n")
|
||||
|
||||
result = runner.invoke(forensics_group, ["metadata", str(test_file)])
|
||||
assert result.exit_code == 0
|
||||
assert "Metadata: test_file.png" in result.output
|
||||
assert "Size" in result.output
|
||||
assert "89504E47" in result.output
|
||||
assert "Detected Type" in result.output
|
||||
assert "PNG Image" in result.output
|
||||
# }}}
|
||||
|
||||
# {{{ test_nested_metadata_cli_success
|
||||
def test_nested_metadata_cli_success():
|
||||
"""
|
||||
Verifies that 'ctf forensics metadata' successfully runs via the root CLI.
|
||||
"""
|
||||
from ctf.main import cli
|
||||
runner = CliRunner()
|
||||
test_file = TEST_ENV / "test_file_nested.png"
|
||||
with open(test_file, "wb") as f:
|
||||
f.write(b"\x89PNG\r\n\x1a\n")
|
||||
|
||||
result = runner.invoke(cli, ["forensics", "metadata", str(test_file)])
|
||||
assert result.exit_code == 0
|
||||
assert "Metadata: test_file_nested.png" in result.output
|
||||
assert "PNG Image" in result.output
|
||||
# }}}
|
||||
|
||||
# {{{ test_forensics_metadata_cli_missing_file
|
||||
def test_forensics_metadata_cli_missing_file():
|
||||
"""
|
||||
Verifies that the CLI fails gracefully when a non-existent file path is specified.
|
||||
"""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(forensics_group, ["metadata", "non_existent_file.png"])
|
||||
assert result.exit_code != 0
|
||||
assert "does not exist" in result.output
|
||||
# }}}
|
||||
|
||||
# {{{ test_forensics_signatures_cli
|
||||
def test_forensics_signatures_cli():
|
||||
"""
|
||||
Verifies that 'forensics signatures' runs successfully and displays
|
||||
the list of supported file signatures.
|
||||
"""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(forensics_group, ["signatures"])
|
||||
assert result.exit_code == 0
|
||||
assert "Supported Magic Signatures" in result.output
|
||||
assert "PNG Image" in result.output
|
||||
assert "89504E47" in result.output
|
||||
# }}}
|
||||
|
||||
# {{{ test_exif_cli_options
|
||||
def test_exif_cli_options():
|
||||
"""
|
||||
Verifies CLI options -i/--inode and -e/--exif work as expected.
|
||||
"""
|
||||
from ctf.main import cli
|
||||
runner = CliRunner()
|
||||
test_file = TEST_ENV / "mock_exif_image.png"
|
||||
|
||||
# Verify mutual exclusion error
|
||||
result_err = runner.invoke(cli, ["forensics", "metadata", "-i", "-e", str(test_file)])
|
||||
assert result_err.exit_code != 0
|
||||
assert "Cannot specify more than one" in result_err.output
|
||||
|
||||
# Verify --inode hides EXIF data table
|
||||
result_inode = runner.invoke(cli, ["forensics", "metadata", "-i", str(test_file)])
|
||||
assert result_inode.exit_code == 0
|
||||
assert "Metadata: mock_exif_image.png" in result_inode.output
|
||||
assert "EXIF Metadata" not in result_inode.output
|
||||
|
||||
# Verify --exif hides POSIX table but displays EXIF data table
|
||||
result_exif = runner.invoke(cli, ["forensics", "metadata", "-e", str(test_file)])
|
||||
assert result_exif.exit_code == 0
|
||||
assert "Metadata: mock_exif_image.png" not in result_exif.output
|
||||
assert "EXIF Metadata" in result_exif.output
|
||||
assert "mock_exif_image" in result_exif.output
|
||||
assert "Make" in result_exif.output
|
||||
assert "TEST" in result_exif.output
|
||||
# }}}
|
||||
|
||||
# {{{ test_exif_cli_options_warning
|
||||
def test_exif_cli_options_warning():
|
||||
"""
|
||||
Verifies CLI warns when no EXIF or comment is found and -e option is used.
|
||||
"""
|
||||
from ctf.main import cli
|
||||
runner = CliRunner()
|
||||
test_file = TEST_ENV / "clean_no_exif.png"
|
||||
with open(test_file, "wb") as f:
|
||||
f.write(b"\x89PNG\r\n\x1a\n")
|
||||
|
||||
result = runner.invoke(cli, ["forensics", "metadata", "-e", str(test_file)])
|
||||
assert result.exit_code == 0
|
||||
assert "No EXIF or comment metadata found" in result.output
|
||||
# }}}
|
||||
|
||||
# {{{ test_exif_cli_comment_only
|
||||
def test_exif_cli_comment_only():
|
||||
"""
|
||||
Verifies CLI displays comment when -e is used and only comment is found.
|
||||
"""
|
||||
from ctf.main import cli
|
||||
runner = CliRunner()
|
||||
comment_text = b"flag{comment_only}"
|
||||
comment_len = len(comment_text) + 2
|
||||
mock_jpeg = b"\xff\xd8\xff\xfe" + struct.pack(">H", comment_len) + comment_text
|
||||
|
||||
test_file = TEST_ENV / "comment_only.jpg"
|
||||
with open(test_file, "wb") as f:
|
||||
f.write(mock_jpeg)
|
||||
|
||||
result = runner.invoke(cli, ["forensics", "metadata", "-e", str(test_file)])
|
||||
assert result.exit_code == 0
|
||||
assert "Comment" in result.output
|
||||
assert "flag{comment_only}" in result.output
|
||||
# }}}
|
||||
|
||||
# {{{ test_adobe_xmp_cli
|
||||
def test_adobe_xmp_cli():
|
||||
"""
|
||||
Verifies that the ctf inspect CLI prints the parsed XMP fields.
|
||||
"""
|
||||
from ctf.main import cli
|
||||
runner = CliRunner()
|
||||
test_file = TEST_ENV / "mock_xmp.jpg"
|
||||
|
||||
result = runner.invoke(cli, ["forensics", "metadata", "-e", str(test_file)])
|
||||
assert result.exit_code == 0
|
||||
assert "EXIF Metadata" in result.output
|
||||
assert "cc:license" in result.output
|
||||
assert "cGljb0NURnt0ZXN0X3htcF9mbGFnfQ==" in result.output
|
||||
# }}}
|
||||
|
||||
# {{{ test_cli_physical_option
|
||||
def test_cli_physical_option():
|
||||
"""
|
||||
Verifies the ctf forensics metadata CLI supports -p/--physical and mutual exclusion rules.
|
||||
"""
|
||||
from ctf.main import cli
|
||||
runner = CliRunner()
|
||||
test_file = TEST_ENV / "mock_physical.jpg"
|
||||
|
||||
# Mutual exclusion check
|
||||
result_err = runner.invoke(cli, ["forensics", "metadata", "-p", "-e", str(test_file)])
|
||||
assert result_err.exit_code != 0
|
||||
assert "Cannot specify more than one" in result_err.output
|
||||
|
||||
# Physical view check
|
||||
result_phys = runner.invoke(cli, ["forensics", "metadata", "-p", str(test_file)])
|
||||
assert result_phys.exit_code == 0
|
||||
assert "Physical Metadata" in result_phys.output
|
||||
assert "Image Size" in result_phys.output
|
||||
assert "1500x1000" in result_phys.output
|
||||
assert "Metadata: mock_physical.jpg" not in result_phys.output
|
||||
assert "EXIF Metadata" not in result_phys.output
|
||||
# }}}
|
||||
|
||||
# {{{ test_metadata_decoded_hints_cli
|
||||
def test_metadata_decoded_hints_cli():
|
||||
"""
|
||||
Verifies that metadata command successfully decodes and prints hex/base64 encoded metadata hints.
|
||||
"""
|
||||
from ctf.main import cli
|
||||
runner = CliRunner()
|
||||
|
||||
# Create a mock JPEG with a base64 encoded comment: "ZmxhZ3tiNjRfbWV0YWRhdGF9" -> "flag{b64_metadata}"
|
||||
comment_text = b"ZmxhZ3tiNjRfbWV0YWRhdGF9"
|
||||
comment_len = len(comment_text) + 2
|
||||
mock_jpeg = b"\xff\xd8\xff\xfe" + struct.pack(">H", comment_len) + comment_text + b"\xff\xd9"
|
||||
|
||||
test_file = TEST_ENV / "mock_encoded_comment.jpg"
|
||||
with open(test_file, "wb") as f:
|
||||
f.write(mock_jpeg)
|
||||
|
||||
result = runner.invoke(cli, ["forensics", "metadata", str(test_file)])
|
||||
assert result.exit_code == 0
|
||||
assert "Decoded Metadata Hints" in result.output
|
||||
assert "Comment" in result.output
|
||||
assert "base64" in result.output
|
||||
assert "flag{b64_metadata}" in result.output
|
||||
# }}}
|
||||
171
tests/cli/test_steg.py
Normal file
171
tests/cli/test_steg.py
Normal file
@@ -0,0 +1,171 @@
|
||||
# tests/cli/test_steg.py
|
||||
# {{{ imports
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
import click
|
||||
from click.testing import CliRunner
|
||||
import pytest
|
||||
from ctf.steg import crack_steghide, SteghideCrackResult
|
||||
# }}}
|
||||
|
||||
# {{{ test_crack_steghide_missing_binary
|
||||
def test_crack_steghide_missing_binary():
|
||||
"""
|
||||
Verifies that crack_steghide returns failure if steghide is not installed.
|
||||
"""
|
||||
with patch("shutil.which", return_value=None):
|
||||
res = crack_steghide(Path("somefile.jpg"), ["pass1", "pass2"])
|
||||
assert not res.success
|
||||
assert "not found on system" in res.error_message
|
||||
# }}}
|
||||
|
||||
# {{{ test_crack_steghide_file_not_found
|
||||
def test_crack_steghide_file_not_found():
|
||||
"""
|
||||
Verifies that crack_steghide returns failure if the input file does not exist.
|
||||
"""
|
||||
with patch("shutil.which", return_value="steghide"):
|
||||
res = crack_steghide(Path("non_existent_file_12345.jpg"), ["pass1", "pass2"])
|
||||
assert not res.success
|
||||
assert "File not found" in res.error_message
|
||||
# }}}
|
||||
|
||||
# {{{ test_crack_steghide_success
|
||||
def test_crack_steghide_success():
|
||||
"""
|
||||
Verifies that crack_steghide returns success, the password, and payload when cracked.
|
||||
"""
|
||||
wordlist = ["wrong1", "correct_pass", "wrong2"]
|
||||
|
||||
# We want to mock subprocess.run to write a payload to the out_file (-xf)
|
||||
# when the correct password is tried.
|
||||
def mock_run(cmd, *args, **kwargs):
|
||||
password = cmd[5]
|
||||
out_file = cmd[7]
|
||||
mock_res = MagicMock()
|
||||
if password == "correct_pass":
|
||||
mock_res.returncode = 0
|
||||
Path(out_file).write_bytes(b"flag{steghide_cracked_payload}")
|
||||
else:
|
||||
mock_res.returncode = 1
|
||||
return mock_res
|
||||
|
||||
with patch("shutil.which", return_value="steghide"), \
|
||||
patch("subprocess.run", side_effect=mock_run), \
|
||||
patch("pathlib.Path.exists", return_value=True):
|
||||
|
||||
res = crack_steghide(Path("mock_image.jpg"), wordlist)
|
||||
assert res.success
|
||||
assert res.password == "correct_pass"
|
||||
assert res.payload == b"flag{steghide_cracked_payload}"
|
||||
assert res.error_message is None
|
||||
# }}}
|
||||
|
||||
# {{{ test_crack_steghide_failure
|
||||
def test_crack_steghide_failure():
|
||||
"""
|
||||
Verifies that crack_steghide returns failure if the wordlist is exhausted.
|
||||
"""
|
||||
wordlist = ["wrong1", "wrong2"]
|
||||
|
||||
def mock_run(cmd, *args, **kwargs):
|
||||
mock_res = MagicMock()
|
||||
mock_res.returncode = 1
|
||||
return mock_res
|
||||
|
||||
with patch("shutil.which", return_value="steghide"), \
|
||||
patch("subprocess.run", side_effect=mock_run), \
|
||||
patch("pathlib.Path.exists", return_value=True):
|
||||
|
||||
res = crack_steghide(Path("mock_image.jpg"), wordlist)
|
||||
assert not res.success
|
||||
assert res.password is None
|
||||
assert res.payload is None
|
||||
assert "Password not found in wordlist" in res.error_message
|
||||
# }}}
|
||||
|
||||
# {{{ test_cli_crack_steghide_success
|
||||
def test_cli_crack_steghide_success(tmp_path):
|
||||
"""
|
||||
Verifies that the crack-steghide CLI command prints success panel and previews payload.
|
||||
"""
|
||||
from ctf.main import cli
|
||||
runner = CliRunner()
|
||||
|
||||
# Create temp wordlist file
|
||||
wordlist_file = tmp_path / "words.txt"
|
||||
wordlist_file.write_text("pass1\npass2\n")
|
||||
|
||||
mock_file = tmp_path / "mock.jpg"
|
||||
mock_file.write_text("dummy")
|
||||
|
||||
mock_result = SteghideCrackResult(
|
||||
success=True,
|
||||
password="pass2",
|
||||
payload=b"flag{cli_steg_preview}"
|
||||
)
|
||||
|
||||
with patch("ctf.cli.steg.crack_steghide", return_value=mock_result):
|
||||
res = runner.invoke(cli, ["steg", "crack-steghide", str(mock_file), "-w", str(wordlist_file)])
|
||||
assert res.exit_code == 0
|
||||
assert "Successfully cracked!" in res.output
|
||||
assert "pass2" in res.output
|
||||
assert "flag{cli_steg_preview}" in res.output
|
||||
# }}}
|
||||
|
||||
# {{{ test_cli_crack_steghide_success_output_file
|
||||
def test_cli_crack_steghide_success_output_file(tmp_path):
|
||||
"""
|
||||
Verifies that the crack-steghide CLI command writes the payload to a file when specified.
|
||||
"""
|
||||
from ctf.main import cli
|
||||
runner = CliRunner()
|
||||
|
||||
wordlist_file = tmp_path / "words.txt"
|
||||
wordlist_file.write_text("pass1\n")
|
||||
|
||||
mock_file = tmp_path / "mock.jpg"
|
||||
mock_file.write_text("dummy")
|
||||
|
||||
out_file = tmp_path / "out.txt"
|
||||
|
||||
mock_result = SteghideCrackResult(
|
||||
success=True,
|
||||
password="pass1",
|
||||
payload=b"written_file_content"
|
||||
)
|
||||
|
||||
with patch("ctf.cli.steg.crack_steghide", return_value=mock_result):
|
||||
res = runner.invoke(cli, ["steg", "crack-steghide", str(mock_file), "-w", str(wordlist_file), "-o", str(out_file)])
|
||||
assert res.exit_code == 0
|
||||
assert "Successfully cracked!" in res.output
|
||||
assert out_file.exists()
|
||||
assert out_file.read_bytes() == b"written_file_content"
|
||||
# }}}
|
||||
|
||||
# {{{ test_cli_crack_steghide_failure
|
||||
def test_cli_crack_steghide_failure(tmp_path):
|
||||
"""
|
||||
Verifies that the crack-steghide CLI command displays correct error message on failure.
|
||||
"""
|
||||
from ctf.main import cli
|
||||
runner = CliRunner()
|
||||
|
||||
wordlist_file = tmp_path / "words.txt"
|
||||
wordlist_file.write_text("pass1\n")
|
||||
|
||||
mock_file = tmp_path / "mock.jpg"
|
||||
mock_file.write_text("dummy")
|
||||
|
||||
mock_result = SteghideCrackResult(
|
||||
success=False,
|
||||
error_message="Password not found in wordlist."
|
||||
)
|
||||
|
||||
with patch("ctf.cli.steg.crack_steghide", return_value=mock_result):
|
||||
res = runner.invoke(cli, ["steg", "crack-steghide", str(mock_file), "-w", str(wordlist_file)])
|
||||
assert res.exit_code == 0
|
||||
assert "Cracking failed: Password not found in wordlist" in res.output
|
||||
# }}}
|
||||
Reference in New Issue
Block a user