From 9e91f8ef0f4f162ada55531d2d7496b6396158b6 Mon Sep 17 00:00:00 2001 From: venus Date: Fri, 17 Jul 2026 02:06:34 -0500 Subject: [PATCH] docs: update architecture and add tests for flag regex generation --- ARCHITECTURE.md | 3 ++ tests/test_commands.py | 54 ++++++++++++++++++++++ tests/test_utils.py | 100 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 tests/test_utils.py diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 5af0446..bcbe6b5 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -39,10 +39,13 @@ Provides helper functions for filesystem management and configuration parsing: * `write_config(data, path)`: Serializes/updates a dictionary to a TOML file. * `active_categories(path)`: Iterates over a competition path to return all active categories. * `active_competitions(dir)`: Scans the base directory for active competitions, skipping designated helper directories (like `tools`). +* `suggest_patterns(flag)`: Analyzes a provided sample flag string and generates a prioritized, deduplicated list of regular expression pattern candidates (ranging from specific to general). ### C. Commands ([commands.py](file:///home/venus/code/ctf/src/ctf/commands.py)) Houses the logic for generic CLI context and active competition commands: * `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-flag-format (pattern)`: Sets the flag regex format in configuration. Supports optional `PATTERN` positional argument or an interactive choice using `-o`/`--original` which takes a sample flag and prompts the user to select from suggested regex patterns. + ### 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: diff --git a/tests/test_commands.py b/tests/test_commands.py index bcddb3a..a474ab9 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -47,3 +47,57 @@ def test_basic_set_flag_format_cli(): # Restore original config from ctf.utils import write_config write_config(original_config, str(config_file)) + +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. + """ + from ctf.utils import load_config + + runner = CliRunner() + config_file = Path("/home/venus/code/ctf/config.toml") + + # Save the original config to restore later + original_config = load_config(str(config_file)) + + try: + # We pass -o SKY-1111-000 and input "2" to choose the second suggested pattern + result = runner.invoke(basic_group, ["set-flag-format", "-o", "SKY-1111-000"], input="2\n") + assert result.exit_code == 0 + assert "Suggested regex patterns:" in result.output + assert "Select a pattern index" in result.output + assert "Flag format set to" in result.output + + # Verify it was written to config.toml + updated_config = load_config(str(config_file)) + selected_pattern = updated_config["Competition"]["flag_format"] + assert selected_pattern.startswith("^") and selected_pattern.endswith("$") + finally: + # Restore original config + from ctf.utils import write_config + write_config(original_config, str(config_file)) + +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 + diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..cbbceec --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,100 @@ +from ctf.utils import suggest_patterns +import pytest + +# ===================================================================== +# 1. Test Strategy Summary +# ===================================================================== +# This suite verifies the `suggest_patterns` utility function which parses +# a sample flag string and generates a prioritized, deduplicated list of +# candidate regular expression patterns. +# +# Core scenarios tested: +# A. Happy Path: +# - Verifying pattern suggestion output for typical flags like 'SKY-1111-000' +# and 'CTF{easy_flag_123}'. +# - Verifying order and counts of specific vs. general patterns. +# +# B. Boundary Conditions: +# - Handling single character inputs (e.g. 'A', '1', '{'). +# - Handling strings containing only delimiter/special characters (e.g. '---'). +# - Handling inputs with no numeric values or no alphabetic values. +# +# C. Edge Cases: +# - Mixing case strings (e.g., 'AbCdEf') and verifying case-insensitive +# or case-specific group behavior. +# +# D. Error Handling: +# - Empty strings or strings containing only whitespace characters should +# raise ValueError. +# ===================================================================== + +# ===================================================================== +# 2. Mocking/Setup Requirements +# ===================================================================== +# - No external database, network APIs, or filesystem mocks are needed +# as `suggest_patterns` is a pure string-processing utility. +# ===================================================================== + +def test_suggest_patterns_happy_path_sky(): + """ + Verifies that the suggestions for a hyphenated alpha-numeric flag like 'SKY-1111-000' + include specific character group counts, variable length matches, and the literal match. + """ + patterns = suggest_patterns("SKY-1111-000") + + # Asserting we get a list of patterns + assert isinstance(patterns, list) + assert len(patterns) > 0 + + # Expected options: + # 1. Specific count: ^[A-Z]{3}-\d{4}-\d{3}$ + assert "^[A-Z]{3}-\\d{4}-\\d{3}$" in patterns + # 2. Variable: ^[A-Z]+-\d+-\d+$ + assert "^[A-Z]+-\\d+-\\d+$" in patterns + # 3. Case-insensitive count: ^[A-Za-z]{3}-\d{4}-\d{3}$ + assert "^[A-Za-z]{3}-\\d{4}-\\d{3}$" in patterns + # 4. Alphanumeric + delimiter class: ^[A-Za-z0-9\-]+$ + assert "^[A-Za-z0-9\\-]+$" in patterns + # 5. Literal matching: ^SKY-1111-000$ + assert "^SKY-1111-000$" in patterns + +def test_suggest_patterns_happy_path_ctf(): + """ + Verifies suggestions for flags with braces and underscores like 'CTF{easy_flag_123}'. + Note that special regex characters like '{' and '}' are correctly escaped. + """ + patterns = suggest_patterns("CTF{easy_flag_123}") + + # 1. Specific count: ^[A-Z]{3}\{[a-z]{4}_[a-z]{4}_\d{3}\}$ + assert "^[A-Z]{3}\\{[a-z]{4}_[a-z]{4}_\\d{3}\\}$" in patterns + # 2. Variable: ^[A-Z]+\{[a-z]+_[a-z]+_\d+\}$ + assert "^[A-Z]+\\{[a-z]+_[a-z]+_\\d+\\}$" in patterns + # 3. Alphanumeric class: ^[A-Za-z0-9_{\}]+$ + assert "^[A-Za-z0-9_\\{\\}]+$" in patterns + # 4. Literal matching: ^CTF\{easy_flag_123\}$ + assert "^CTF\\{easy_flag_123\\}$" in patterns + +def test_suggest_patterns_only_specials(): + """ + Verifies that a string with only special characters works and generates valid patterns. + """ + patterns = suggest_patterns("---") + # All patterns should deduplicate to just the exact literal match or similar + assert "^---$" in patterns + +def test_suggest_patterns_single_char(): + """ + Verifies pattern suggestion on single character flags. + """ + assert "^[A-Z]{1}$" in suggest_patterns("A") + assert "^\\d{1}$" in suggest_patterns("5") + +def test_suggest_patterns_empty_and_whitespace(): + """ + Verifies that empty string or whitespace-only inputs trigger a ValueError. + """ + with pytest.raises(ValueError, match="Input string cannot be empty or whitespace only."): + suggest_patterns("") + + with pytest.raises(ValueError, match="Input string cannot be empty or whitespace only."): + suggest_patterns(" ")