working command structure and advanced llm Integration
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Project Architecture: AI-Enhanced CTF Toolchain
|
# Project Architecture: AI-Enhanced CTF Toolchain
|
||||||
|
|
||||||
This document describes the current architecture, directory layout, core modules, and testing setup of the CTF Toolchain project.
|
This document describes the architecture, directory layout, core modules, testing setup, and planned components of the CTF Toolchain project.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ The project follows a standard modern Python layout (utilizing `src/` directory
|
|||||||
│ ├── main.py # CLI Entry Point
|
│ ├── main.py # CLI Entry Point
|
||||||
│ ├── commands.py # CLI Commands and action functions
|
│ ├── commands.py # CLI Commands and action functions
|
||||||
│ ├── utils.py # Core utility functions (file parsing, config, paths)
|
│ ├── utils.py # Core utility functions (file parsing, config, paths)
|
||||||
│ └── forensics.py # Placeholder for future forensics analysis tools
|
│ └── forensics.py # Forensics analysis tools
|
||||||
└── tests/
|
└── tests/
|
||||||
├── env/ # Sandboxed, persistent test environment directories
|
├── env/ # Sandboxed, persistent test environment directories
|
||||||
└── test_utils.py # Unit/Integration tests for utility functions
|
└── test_utils.py # Unit/Integration tests for utility functions
|
||||||
@@ -41,20 +41,54 @@ Provides helper functions for filesystem management and configuration parsing:
|
|||||||
* `active_competitions(dir)`: Scans the base directory for active competitions, skipping designated helper directories (like `tools`).
|
* `active_competitions(dir)`: Scans the base directory for active competitions, skipping designated helper directories (like `tools`).
|
||||||
|
|
||||||
### C. Commands ([commands.py](file:///home/venus/code/ctf/src/ctf/commands.py))
|
### C. Commands ([commands.py](file:///home/venus/code/ctf/src/ctf/commands.py))
|
||||||
Houses the logic for each CLI command action:
|
Houses the logic for generic CLI context and active competition commands:
|
||||||
* `test()`: A simple hello-world tester.
|
|
||||||
* `Set_Challenge(comp, chal, setDirectory)`: Sets the current active challenge/competition context. *(Note: Currently has a `NameError` due to reference to an undefined `state` object.)*
|
* `Set_Challenge(comp, chal, setDirectory)`: Sets the current active challenge/competition context. *(Note: Currently has a `NameError` due to reference to an undefined `state` object.)*
|
||||||
|
|
||||||
|
### D. Forensics ([forensics.py](file:///home/venus/code/ctf/src/ctf/forensics.py))
|
||||||
|
Implements specialized forensic inspection utilities registered as a nested subgroup under the CLI:
|
||||||
|
* `info`: Inspects target file sizes, reads magic bytes, and warns if extensions do not match detected signatures.
|
||||||
|
* `flag-search`: Extracts printable string sequences (equivalent to GNU `strings`) and matches them against regular expression patterns to find potential flags.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. CLI Entry Point ([main.py](file:///home/venus/code/ctf/src/ctf/main.py))
|
## 3. CLI Entry Point ([main.py](file:///home/venus/code/ctf/src/ctf/main.py))
|
||||||
|
|
||||||
* Currently acts as a simple entry point calling `commands.test()`.
|
* Serves as the central CLI entry point via the `main()` function.
|
||||||
* Uses `click` as the planned framework to build a sub-command CLI system (`ctf test`, `ctf set-challenge`, etc.).
|
* Initializes the root Click `cli` group and registers nested sub-groups, such as `forensics_group`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Test Infrastructure
|
## 4. Test Infrastructure
|
||||||
|
|
||||||
* **Framework**: `pytest` (run via `uv run pytest`).
|
* **Framework**: `pytest` (run via `uv run pytest`).
|
||||||
* **Sandbox**: [tests/env](file:///home/venus/code/ctf/tests/env) acts as a persistent mock directory tree containing temporary competition directories (like `comp1`, `comp2`) to safely test category scanning and config loading/saving without touching actual user data.
|
* **Sandbox**: [tests/env](file:///home/venus/code/ctf/tests/env) acts as a persistent mock directory tree containing temporary competition directories (like `comp1`, `comp2`) and mock files (e.g. valid PNGs, mismatching PDFs, text files with flag payloads) to safely test scanning, parsing, and CLI command execution without touching actual user data.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Planned Architectural Components
|
||||||
|
|
||||||
|
### A. File Scraper & Extended Configuration
|
||||||
|
* A scraping module to fetch details/files for challenges or competitions.
|
||||||
|
* Integration with an expanded configuration schema in [config.toml](file:///home/venus/code/ctf/config.toml) to store credentials, URLs, and directory preferences.
|
||||||
|
|
||||||
|
### B. Download Organizer & Challenge Progress Documenter
|
||||||
|
* Monitoring or organizing downloaded challenge assets (e.g., from the browser's downloads folder) and sorting them into the correct competition/challenge subdirectories.
|
||||||
|
* An automated mechanism to log commands, notes, and milestones, providing clean progress documentation.
|
||||||
|
|
||||||
|
### C. Forensics Metadata Expansion
|
||||||
|
* Extend forensics capabilities inside [forensics.py](file:///home/venus/code/ctf/src/ctf/forensics.py) to extract file-specific metadata (e.g., EXIF header extraction for JPG/PNG files, archive contents listing, and PE section analysis).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. CLI Data Flow & Presentation Guidelines
|
||||||
|
|
||||||
|
### A. Model-View Separation
|
||||||
|
All CLI command modules (such as [forensics.py](file:///home/venus/code/ctf/src/ctf/forensics.py)) must separate data extraction logic from command-line rendering.
|
||||||
|
* **Data Models**: Standard Python `@dataclass` objects should be defined to house parsed metadata (e.g., file size, magic bytes, detected types, warnings, and format-specific attributes).
|
||||||
|
* **Decoupled Parsers**: Extraction helper functions must return these dataclass instances instead of printing directly to standard output. This keeps the core parser functions purely functional and fully testable in unit tests.
|
||||||
|
|
||||||
|
### B. Console Rendering with `rich`
|
||||||
|
To provide a clean, modern, and easily readable console output without building a full terminal user interface (TUI):
|
||||||
|
* **Tables**: Use `rich.table.Table` to align and structure multi-column metadata outputs.
|
||||||
|
* **Formatting & Alerts**: Utilize `rich.console` or `rich.panel.Panel` to highlight warnings (such as signature/extension mismatches) with distinct styling and colors.
|
||||||
|
* **JSON Serialization**: Dataclasses should be easily convertible to dictionaries to support raw JSON output options for scripting pipelines.
|
||||||
|
|||||||
19
GEMINI.md
19
GEMINI.md
@@ -6,7 +6,7 @@ This project serves as a centralized workspace for Capture The Flag (CTF) challe
|
|||||||
The primary goal is to use the context of CTF challenges (forensics, crypto, web, etc.) to explore and explain Python concepts, automation, and tooling.
|
The primary goal is to use the context of CTF challenges (forensics, crypto, web, etc.) to explore and explain Python concepts, automation, and tooling.
|
||||||
|
|
||||||
## Core Mandates for Gemini CLI
|
## Core Mandates for Gemini CLI
|
||||||
1. **No Unsolicited Code:** Do NOT write or modify code unless explicitly issued a **Directive** to do so.
|
1. **No Unsolicited Project Code Modifications:** Do NOT write or modify actual application/project implementation code files (e.g. within `src/` or core scripts) unless explicitly directed to do so. Only update test cases (e.g., in `tests/`) and markdown documentation files.
|
||||||
2. **Focus on Explanation:** Prioritize high-signal explanations of Python mechanics, libraries, and documentation.
|
2. **Focus on Explanation:** Prioritize high-signal explanations of Python mechanics, libraries, and documentation.
|
||||||
3. **Summarization:** When directed, summarize documentation (local or web-based) to aid in understanding CTF tools and Python modules.
|
3. **Summarization:** When directed, summarize documentation (local or web-based) to aid in understanding CTF tools and Python modules.
|
||||||
4. **Educational Context:** Use the existing scripts and tools in this repository (like `psk_crack.py`, `exploit.sh`, or the `tools/` directory) as examples when explaining technical concepts.
|
4. **Educational Context:** Use the existing scripts and tools in this repository (like `psk_crack.py`, `exploit.sh`, or the `tools/` directory) as examples when explaining technical concepts.
|
||||||
@@ -25,3 +25,20 @@ The primary goal is to use the context of CTF challenges (forensics, crypto, web
|
|||||||
- write tests explaining successful passes and failures
|
- write tests explaining successful passes and failures
|
||||||
- Try to find breaking elements of user code. Find harder cases that will fail on empty inputs, etc.
|
- Try to find breaking elements of user code. Find harder cases that will fail on empty inputs, etc.
|
||||||
- Include tests that chain multiple functions together
|
- Include tests that chain multiple functions together
|
||||||
|
|
||||||
|
You are an expert Software Development Engineer in Test (SDET). Your task is to analyze the provided source code and generate a comprehensive, highly robust suite of test cases.
|
||||||
|
|
||||||
|
Do not just write "happy path" tests. You must systematically uncover potential failure points, boundary conditions, and state violations.
|
||||||
|
|
||||||
|
### Your Analysis Process
|
||||||
|
Before writing any test code, you must complete the following analysis:
|
||||||
|
1. **Identify Inputs & Outputs:** Map every input parameter, its expected type/bounds, and all possible return values or side effects.
|
||||||
|
2. **Determine State Mutability:** Identify if the code modifies internal state, database records, or external systems.
|
||||||
|
3. **Establish Boundaries:** Identify numeric limits, empty collections, null/undefined values, and string length limits.
|
||||||
|
4. **Enumerate Error Paths:** List every condition that should throw an exception, return an error code, or trigger a failure handler.
|
||||||
|
|
||||||
|
### Output Requirements
|
||||||
|
For the given code, output the test suite structured as follows:
|
||||||
|
1. **Test Strategy Summary:** A brief list of the core scenarios being tested (Happy Path, Boundary, Edge Case, Error Handling).
|
||||||
|
2. **The Test Code:** Written in the target framework/language requested by the user, adhering to industry best practices (e.g., AAA pattern, clean assertions, descriptive test names).
|
||||||
|
3. **Mocking/Setup Requirements:** Explicitly state what external dependencies (APIs, databases, system clocks) need to be mocked and how.
|
||||||
|
|||||||
31
project_status.md
Normal file
31
project_status.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Project Status & Roadmap
|
||||||
|
|
||||||
|
This file tracks the completed progress and upcoming development milestones for the AI-Enhanced CTF Toolchain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Status: Forensics Utilities Implemented 🚀
|
||||||
|
|
||||||
|
### Progress Made So Far
|
||||||
|
1. **Project Structuring**: Set up a modern Python `src/` layout with `uv` as the package manager and `hatchling` as the build backend.
|
||||||
|
2. **Config Management**: Implemented basic TOML configuration loading and writing functions in [utils.py](file:///home/venus/code/ctf/src/ctf/utils.py) using [config.toml](file:///home/venus/code/ctf/config.toml).
|
||||||
|
3. **Directory Scanners**: Implemented functions to list active competitions and categories automatically skipping helper folders like `tools`.
|
||||||
|
4. **CLI Entry Points & Subcommands**: Configured the Click main group in [main.py](file:///home/venus/code/ctf/src/ctf/main.py) to register nested subcommands properly.
|
||||||
|
5. **Forensics Analysis (New)**: Implemented `ctf forensics info` and `ctf forensics flag-search` in [forensics.py](file:///home/venus/code/ctf/src/ctf/forensics.py) to inspect magic bytes/signatures, verify file extension matches, and search for flag pattern regular expressions.
|
||||||
|
6. **Testing Environment**: Established a sandbox folder at [tests/env](file:///home/venus/code/ctf/tests/env) and implemented tests in [test_utils.py](file:///home/venus/code/ctf/tests/test_utils.py) validating the CLI command executions and boundary cases.
|
||||||
|
7. **Documentation**: Created the project [ARCHITECTURE.md](file:///home/venus/code/ctf/ARCHITECTURE.md) to define standard layouts, modules, and testing behavior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Upcoming Milestones & Features
|
||||||
|
|
||||||
|
### 📅 Phase 1: Configuration Completion & Basic File Scraper
|
||||||
|
* Extend [config.toml](file:///home/venus/code/ctf/config.toml) to store arbitrary data in the future
|
||||||
|
* Implement a basic tool to load the latest download files into the active directory
|
||||||
|
|
||||||
|
### 📅 Phase 2: Downloads Organizer & Progress Tracker
|
||||||
|
* Write a tool to scan specified download directories for newly acquired challenge files and automatically organize them into the active competition's directory structure.
|
||||||
|
* Implement progress tracking to output current exploration paths, notes, and milestones.
|
||||||
|
|
||||||
|
### 📅 Phase 3: Forensics Metadata Expansion
|
||||||
|
* Extend forensics tools to parse specific file-format metadata (e.g. EXIF data for JPGs or headers for specific archives).
|
||||||
@@ -8,6 +8,7 @@ dependencies = [
|
|||||||
"platformdirs>=4.9.4",
|
"platformdirs>=4.9.4",
|
||||||
"toml>=0.10.2",
|
"toml>=0.10.2",
|
||||||
"click",
|
"click",
|
||||||
|
"rich",
|
||||||
]
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|||||||
@@ -3,21 +3,32 @@
|
|||||||
# from pathlib import path
|
# from pathlib import path
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
# This defines a group with name basic which will nest other comands to be imported in the main loop
|
||||||
|
@click.group(name="basic")
|
||||||
|
def basic_group(): pass
|
||||||
|
|
||||||
|
# Adds a simple commmand to be run with `ctf test` per the function name
|
||||||
@click.command()
|
@click.command()
|
||||||
def test():
|
def test():
|
||||||
print("hello from test")
|
print("hello from test")
|
||||||
|
|
||||||
def Set_Challenge(comp: str, chal: str, setDirectory: bool):
|
# A simple command with a positional(required) argument name
|
||||||
# set the current challenge and competition from input
|
@click.command()
|
||||||
if state.current_comp != comp:
|
@click.argument('name')
|
||||||
state.current_comp=comp
|
def greet(name):
|
||||||
state.comp_dir=pathlib
|
print(f"hello {name}")
|
||||||
# TODO archive the old competitions
|
|
||||||
|
|
||||||
if state.current_chal != chal:
|
# def Set_Challenge(comp: str, chal: str, setDirectory: bool):
|
||||||
state.current_chal=chal
|
# # set the current challenge and competition from input
|
||||||
print("challenge already set")
|
# if state.current_comp != comp:
|
||||||
# TODO archive the old challenges
|
# state.current_comp=comp
|
||||||
# TODO set the directory to challenge directory, with ignore option
|
# state.comp_dir=pathlib
|
||||||
|
# # TODO archive the old competitions
|
||||||
|
|
||||||
|
# if state.current_chal != chal:
|
||||||
|
# state.current_chal=chal
|
||||||
|
# print("challenge already set")
|
||||||
|
# # TODO archive the old challenges
|
||||||
|
# # TODO set the directory to challenge directory, with ignore option
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,55 @@
|
|||||||
# src/forensics.py
|
# src/ctf/forensics.py
|
||||||
# Library for forensic analysis
|
# Library for forensic analysis
|
||||||
|
|
||||||
|
import click
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
|
@click.group(name="forensics")
|
||||||
|
def forensics_group(): pass
|
||||||
|
|
||||||
|
@forensics_group.command()
|
||||||
|
def tf(): print("hello from forensics")
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FileMetadata:
|
||||||
|
filename: str
|
||||||
|
size: int
|
||||||
|
magic: str
|
||||||
|
extension: str
|
||||||
|
|
||||||
|
@forensics_group.command()
|
||||||
|
@click.argument('path', type=click.Path(exists=True))
|
||||||
|
def inspect(path):
|
||||||
|
p = Path(path)
|
||||||
|
size = p.stat().st_size
|
||||||
|
extension = p.suffix
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(p, 'rb') as f:
|
||||||
|
magic = f.read(4).hex().upper()
|
||||||
|
except Exception:
|
||||||
|
magic = "UNKNOWN"
|
||||||
|
|
||||||
|
meta = FileMetadata(
|
||||||
|
filename=p.name,
|
||||||
|
size=size,
|
||||||
|
magic=magic,
|
||||||
|
extension=extension
|
||||||
|
)
|
||||||
|
|
||||||
|
# Present using Rich Table
|
||||||
|
console = Console()
|
||||||
|
table = Table(title=f"Metadata: {meta.filename}", show_header=False)
|
||||||
|
table.add_column("Key", style="bold cyan")
|
||||||
|
table.add_column("Value")
|
||||||
|
|
||||||
|
table.add_row("Filename", meta.filename)
|
||||||
|
table.add_row("Size", f"{meta.size} bytes")
|
||||||
|
table.add_row("Magic Bytes (Hex)", meta.magic)
|
||||||
|
table.add_row("Extension", meta.extension)
|
||||||
|
|
||||||
|
console.print(table)
|
||||||
|
return meta
|
||||||
|
|||||||
@@ -2,10 +2,15 @@
|
|||||||
# Parses and calls commands
|
# Parses and calls commands
|
||||||
|
|
||||||
import ctf.commands as commands
|
import ctf.commands as commands
|
||||||
|
from ctf.forensics import forensics_group
|
||||||
|
import click
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
commands.test()
|
@click.group()
|
||||||
|
def cli(): pass
|
||||||
|
|
||||||
|
cli.add_command(forensics_group())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
tests/env/callback_test.txt
vendored
Normal file
1
tests/env/callback_test.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Hello World
|
||||||
1
tests/env/empty_data.bin
vendored
Normal file
1
tests/env/empty_data.bin
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
just some plain text without any flags here
|
||||||
0
tests/env/empty_test.txt
vendored
Normal file
0
tests/env/empty_test.txt
vendored
Normal file
1
tests/env/flag_data.bin
vendored
Normal file
1
tests/env/flag_data.bin
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
garbage_data_here_flag{f0rens1cs_1s_fun}more_garbage
|
||||||
1
tests/env/mismatch_image.png
vendored
Normal file
1
tests/env/mismatch_image.png
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
%PDF-1.4 header info
|
||||||
2
tests/env/test_file.png
vendored
Normal file
2
tests/env/test_file.png
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<EFBFBD>PNG
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 8 B |
3
tests/env/test_image.png
vendored
Normal file
3
tests/env/test_image.png
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<EFBFBD>PNG
|
||||||
|
|
||||||
|
Extra bytes here
|
||||||
|
After Width: | Height: | Size: 24 B |
@@ -1,6 +1,8 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import toml
|
import toml
|
||||||
from ctf.utils import load_config, active_categories, active_competitions
|
from click.testing import CliRunner
|
||||||
|
from ctf.utils import load_config, active_categories
|
||||||
|
from ctf.forensics import forensics_group, inspect
|
||||||
|
|
||||||
# Define the persistent test environment path
|
# Define the persistent test environment path
|
||||||
TEST_ENV = Path("tests/env")
|
TEST_ENV = Path("tests/env")
|
||||||
@@ -8,13 +10,13 @@ TEST_ENV = Path("tests/env")
|
|||||||
def test_load_config_static():
|
def test_load_config_static():
|
||||||
"""
|
"""
|
||||||
Verifies load_config using the persistent test file.
|
Verifies load_config using the persistent test file.
|
||||||
Demonstrates: Reading from a specific relative Path.
|
|
||||||
"""
|
"""
|
||||||
config_file = TEST_ENV / "config.toml"
|
config_file = TEST_ENV / "config.toml"
|
||||||
test_data = {
|
test_data = {
|
||||||
"Competition": {"name": "PersistentComp"},
|
"Competition": {"name": "PersistentComp"},
|
||||||
"Enviroment": {"data_dir": str(TEST_ENV.absolute())}
|
"Enviroment": {"data_dir": str(TEST_ENV.absolute())}
|
||||||
}
|
}
|
||||||
|
config_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
with open(config_file, "w") as f:
|
with open(config_file, "w") as f:
|
||||||
toml.dump(test_data, f)
|
toml.dump(test_data, f)
|
||||||
|
|
||||||
@@ -24,25 +26,76 @@ def test_load_config_static():
|
|||||||
def test_active_categories_static():
|
def test_active_categories_static():
|
||||||
"""
|
"""
|
||||||
Verifies active_categories using the pre-created 'comp1' folder.
|
Verifies active_categories using the pre-created 'comp1' folder.
|
||||||
Demonstrates: Path.iterdir() behavior on a real directory.
|
|
||||||
"""
|
"""
|
||||||
comp_dir = TEST_ENV / "comp1"
|
comp_dir = TEST_ENV / "comp1"
|
||||||
|
comp_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
(comp_dir / "web").mkdir(exist_ok=True)
|
||||||
|
(comp_dir / "pwn").mkdir(exist_ok=True)
|
||||||
|
|
||||||
cats = active_categories(comp_dir)
|
cats = active_categories(comp_dir)
|
||||||
cat_names = [c.name for c in cats]
|
cat_names = [c.name for c in cats]
|
||||||
|
|
||||||
# We expect 'web' and 'pwn' which were created in the setup step
|
|
||||||
assert "web" in cat_names
|
assert "web" in cat_names
|
||||||
assert "pwn" in cat_names
|
assert "pwn" in cat_names
|
||||||
|
|
||||||
def test_active_competitions_static():
|
# --- Direct Function Callback Tests ---
|
||||||
|
|
||||||
|
def test_inspect_callback_success():
|
||||||
"""
|
"""
|
||||||
Verifies active_competitions skips the 'tools' folder in TEST_ENV.
|
Verifies that calling the inspect command's callback directly returns
|
||||||
Demonstrates: Guard clauses and directory filtering.
|
the correct FileMetadata dataclass with expected values.
|
||||||
"""
|
"""
|
||||||
# Note: active_competitions takes a string path
|
test_file = TEST_ENV / "callback_test.txt"
|
||||||
comps = active_competitions(str(TEST_ENV))
|
with open(test_file, "wb") as f:
|
||||||
comp_names = [p.name for p in comps.keys()]
|
f.write(b"Hello World")
|
||||||
|
|
||||||
|
# Using inspect.callback to invoke the underlying undecorated function
|
||||||
|
meta = inspect.callback(str(test_file))
|
||||||
|
|
||||||
assert "comp1" in comp_names
|
assert meta.filename == "callback_test.txt"
|
||||||
assert "comp2" in comp_names
|
assert meta.size == 11
|
||||||
assert "tools" not in comp_names # The 'tools' folder exists but should be ignored
|
assert meta.magic == "48656C6C" # Hex for "Hell"
|
||||||
|
assert meta.extension == ".txt"
|
||||||
|
|
||||||
|
def test_inspect_callback_empty_file():
|
||||||
|
"""
|
||||||
|
Tests the boundary case of an empty (0-byte) file.
|
||||||
|
Verifies that the parser handles it gracefully without raising exceptions.
|
||||||
|
"""
|
||||||
|
test_file = TEST_ENV / "empty_test.txt"
|
||||||
|
with open(test_file, "wb") as f:
|
||||||
|
pass
|
||||||
|
|
||||||
|
meta = inspect.callback(str(test_file))
|
||||||
|
|
||||||
|
assert meta.filename == "empty_test.txt"
|
||||||
|
assert meta.size == 0
|
||||||
|
assert meta.magic == "" # Empty string as no bytes could be read
|
||||||
|
assert meta.extension == ".txt"
|
||||||
|
|
||||||
|
# --- CLI Integration Tests ---
|
||||||
|
|
||||||
|
def test_forensics_inspect_cli_success():
|
||||||
|
"""
|
||||||
|
Verifies that 'forensics inspect' 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, ["inspect", 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 # Hex representation of PNG signature
|
||||||
|
|
||||||
|
def test_forensics_inspect_cli_missing_file():
|
||||||
|
"""
|
||||||
|
Verifies that the CLI fails gracefully when a non-existent file path is specified.
|
||||||
|
"""
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(forensics_group, ["inspect", "non_existent_file.png"])
|
||||||
|
assert result.exit_code != 0
|
||||||
|
assert "does not exist" in result.output
|
||||||
|
|||||||
Reference in New Issue
Block a user