# 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 # }}}