test_grep_tool_steps.py•18.2 kB
"""Step definitions for grep_tool.feature tests."""
import os
import pytest
import tempfile
import shutil
import re
from pathlib import Path
from pytest_bdd import given, when, then, parsers
from typing import Dict, List
from mcp_grep.core import MCPGrep
@pytest.fixture
def test_file_content():
"""Fixture to store test file content."""
return ""
@pytest.fixture
def test_file_path():
"""Fixture to create a temporary test file."""
temp_dir = tempfile.mkdtemp()
temp_file = os.path.join(temp_dir, "test_file.txt")
yield temp_file
# Cleanup
shutil.rmtree(temp_dir)
@pytest.fixture
def grep_results():
"""Fixture to store grep results."""
return {}
@pytest.fixture
def mcp_connection():
"""Mock fixture for MCP connection."""
# In real implementation, this would connect to the MCP server
return {"connected": True}
@given("I'm connected to the MCP grep server")
def connected_to_server(mcp_connection):
"""Verify connection to MCP grep server."""
assert mcp_connection["connected"] is True
@given(parsers.parse('a file with content "{content}"'))
def create_test_file(content, test_file_path):
"""Create a test file with specified content."""
# Replace escaped newlines with actual newlines
actual_content = content.replace('\\n', '\n')
# Create the file with the specified content
with open(test_file_path, 'w', encoding='utf-8') as f:
f.write(actual_content)
return actual_content
@given("a directory with multiple files containing the word \"secret\"")
def create_test_directory_with_files(test_dir):
"""Create a test directory with multiple files containing 'secret'."""
# Create a few files with the word "secret"
files = {
"file1.txt": "This file has a secret in it",
"file2.txt": "No secrets here",
"subdir/file3.txt": "Another secret document",
}
for path, content in files.items():
full_path = os.path.join(test_dir, path)
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, 'w', encoding='utf-8') as f:
f.write(content)
return test_dir
@given("multiple files with extensions \".txt\" and \".log\"")
def create_files_with_extensions(test_dir):
"""Create test files with different extensions."""
# Create files with different extensions
files = {
"file1.txt": "Text file content",
"file2.log": "Log file with error",
"file3.txt": "Another text file",
"file4.log": "Another log with error",
}
for path, content in files.items():
full_path = os.path.join(test_dir, path)
with open(full_path, 'w', encoding='utf-8') as f:
f.write(content)
return test_dir
@when(parsers.parse('I invoke the grep tool with pattern "{pattern}" and the file path'))
def invoke_grep_with_pattern_and_path(pattern, test_file_path, grep_results):
"""Invoke grep with a pattern and file path."""
# Use the actual MCPGrep implementation
grep = MCPGrep(pattern)
# Perform the search
results = list(grep.search_file(test_file_path))
# Store results for verification
grep_results["results"] = results
grep_results["match_count"] = len(results)
@when(parsers.parse('I invoke the grep tool with pattern "{pattern}" and ignore_case=True'))
def invoke_grep_with_pattern_and_ignore_case(pattern, test_file_path, grep_results):
"""Invoke grep with case-insensitive pattern matching."""
# Use the actual MCPGrep implementation with ignore_case=True
grep = MCPGrep(pattern, ignore_case=True)
# Perform the search
results = list(grep.search_file(test_file_path))
# Store results for verification
grep_results["results"] = results
grep_results["match_count"] = len(results)
@when(parsers.parse('I invoke the grep tool with pattern "{pattern}" and before_context={before:d} and after_context={after:d}'))
def invoke_grep_with_context(pattern, before, after, test_file_path, grep_results):
"""Invoke grep with context lines."""
# Use the MCPGrep implementation with context settings
grep = MCPGrep(
pattern=pattern,
before_context=before,
after_context=after
)
# Perform the search
results = list(grep.search_file(test_file_path))
# Store results for verification
grep_results["results"] = results
grep_results["match_count"] = len(results)
@when(parsers.parse('I invoke the grep tool with pattern "{pattern}" and fixed_strings=True'))
def invoke_grep_with_fixed_strings(pattern, test_file_path, grep_results):
"""Invoke grep with fixed string matching."""
# For fixed strings, we'll escape all regex special characters
escaped_pattern = re.escape(pattern)
grep = MCPGrep(escaped_pattern)
# Perform the search
results = list(grep.search_file(test_file_path))
# Store results for verification
grep_results["results"] = results
grep_results["match_count"] = len(results)
@when(parsers.parse('I invoke the grep tool with pattern "{pattern}" and recursive=True'))
def invoke_grep_with_recursive(pattern, test_dir, grep_results):
"""Invoke grep with recursive search."""
grep = MCPGrep(pattern)
# Collect all files in the directory recursively
all_files = []
for root, _, files in os.walk(test_dir):
for file in files:
all_files.append(os.path.join(root, file))
# Search all files
all_results = []
for file_path in all_files:
try:
results = list(grep.search_file(file_path))
all_results.extend(results)
except Exception as e:
print(f"Error searching {file_path}: {e}")
# Store results for verification
grep_results["results"] = all_results
grep_results["match_count"] = len(all_results)
@when(parsers.parse('I invoke the grep tool with pattern "{pattern}" and max_count={count:d}'))
def invoke_grep_with_max_count(pattern, count, test_file_path, grep_results):
"""Invoke grep with maximum result count."""
grep = MCPGrep(pattern)
# Perform the search but limit results
results = []
for result in grep.search_file(test_file_path):
results.append(result)
if len(results) >= count:
break
# Store results for verification
grep_results["results"] = results
grep_results["match_count"] = len(results)
@when(parsers.parse('I invoke the grep tool with pattern "{pattern}" and regexp=True'))
def invoke_grep_with_regexp(pattern, test_file_path, grep_results):
"""Invoke grep with regular expression pattern."""
# MCPGrep already uses regex by default
grep = MCPGrep(pattern)
# Perform the search
results = list(grep.search_file(test_file_path))
# Store results for verification
grep_results["results"] = results
grep_results["match_count"] = len(results)
@when(parsers.parse('I invoke the grep tool with pattern "{pattern}" and invert_match=True'))
def invoke_grep_with_invert_match(pattern, test_file_path, grep_results):
"""Invoke grep with inverted matching."""
grep = MCPGrep(pattern, invert_match=True)
# Perform the search with proper invert_match flag
results = list(grep.search_file(test_file_path))
# Store results for verification
grep_results["results"] = results
grep_results["match_count"] = len(results)
@when(parsers.parse('I invoke the grep tool with pattern "{pattern}" and line_number=False'))
def invoke_grep_without_line_numbers(pattern, test_file_path, grep_results):
"""Invoke grep without line numbers."""
grep = MCPGrep(pattern)
# Perform the search
results = list(grep.search_file(test_file_path))
# Remove line numbers from results
for result in results:
if 'line_num' in result:
del result['line_num']
# Store results for verification
grep_results["results"] = results
grep_results["match_count"] = len(results)
grep_results["no_line_numbers"] = True
@when(parsers.parse('I invoke the grep tool with pattern "{pattern}" and file_pattern="{file_pattern}"'))
def invoke_grep_with_file_pattern(pattern, file_pattern, test_dir, grep_results):
"""Invoke grep with a file pattern filter."""
grep = MCPGrep(pattern)
# Find files matching the pattern
import fnmatch
matching_files = []
for root, _, files in os.walk(test_dir):
for file in files:
if fnmatch.fnmatch(file, file_pattern):
matching_files.append(os.path.join(root, file))
# Search in matching files
all_results = []
for file_path in matching_files:
try:
results = list(grep.search_file(file_path))
all_results.extend(results)
except Exception as e:
print(f"Error searching {file_path}: {e}")
# Store results for verification
grep_results["results"] = all_results
grep_results["match_count"] = len(all_results)
@when(parsers.parse('I invoke the grep tool with pattern "{pattern}" and context={context:d}'))
def invoke_grep_with_equal_context(pattern, context, test_file_path, grep_results):
"""Invoke grep with equal before and after context."""
# Reuse the same logic from invoke_grep_with_context
invoke_grep_with_context(pattern, context, context, test_file_path, grep_results)
@then(parsers.parse("I should receive results with {count:d} matching line"))
@then(parsers.parse("I should receive results with {count:d} matching lines"))
def verify_match_count(count, grep_results):
"""Verify the number of matching lines in the results."""
assert grep_results["match_count"] == count, \
f"Expected {count} matches, got {grep_results['match_count']}"
@then(parsers.parse("I should receive results with no more than {count:d} matching lines"))
def verify_max_match_count(count, grep_results):
"""Verify the number of matching lines doesn't exceed the limit."""
assert grep_results["match_count"] <= count, \
f"Expected no more than {count} matches, got {grep_results['match_count']}"
@then(parsers.parse("the result should include line number {line_num:d}"))
def verify_line_number(line_num, grep_results):
"""Verify a specific line number is in the results."""
found = False
for result in grep_results["results"]:
# Handle context-style results
if isinstance(result, dict) and "match" in result:
if result["match"]["line_num"] == line_num:
found = True
break
# Handle simple results
elif isinstance(result, dict) and "line_num" in result:
if result["line_num"] == line_num:
found = True
break
assert found, f"Line number {line_num} not found in results"
@then(parsers.parse('the result should contain "{text}"'))
def verify_result_contains_text(text, grep_results):
"""Verify the result contains specific text."""
found = False
for result in grep_results["results"]:
# Handle context-style results
if isinstance(result, dict) and "match" in result:
if text in result["match"]["line"]:
found = True
break
# Handle simple results
elif isinstance(result, dict) and "line" in result:
if text in result["line"]:
found = True
break
assert found, f"Text '{text}' not found in any result"
@then("each match should include context lines")
def verify_context_lines(grep_results):
"""Verify that matches include context lines."""
for match_with_context in grep_results["results"]:
assert "before_context" in match_with_context, "Before context not found"
assert "after_context" in match_with_context, "After context not found"
# At least one of before or after context should have lines
has_context = len(match_with_context["before_context"]) > 0 or len(match_with_context["after_context"]) > 0
assert has_context, "No context lines found"
@then("I should receive results from multiple files")
def verify_multiple_file_results(grep_results):
"""Verify that results come from multiple files."""
files = set()
for result in grep_results["results"]:
# Handle context-style results
if isinstance(result, dict) and "match" in result:
files.add(result["match"]["file"])
# Handle simple results
elif isinstance(result, dict) and "file" in result:
files.add(result["file"])
assert len(files) > 1, f"Results only from {len(files)} file(s): {files}"
@then('the results should contain "{text1}" and "{text2}"')
def verify_results_contain_multiple_texts(text1, text2, grep_results):
"""Verify results contain multiple specific texts."""
found_text1 = False
found_text2 = False
for result in grep_results["results"]:
# Handle context-style results
if isinstance(result, dict) and "match" in result:
if text1 in result["match"]["line"]:
found_text1 = True
if text2 in result["match"]["line"]:
found_text2 = True
# Handle simple results
elif isinstance(result, dict) and "line" in result:
if text1 in result["line"]:
found_text1 = True
if text2 in result["line"]:
found_text2 = True
assert found_text1, f"Text '{text1}' not found in any result"
assert found_text2, f"Text '{text2}' not found in any result"
@then('the results should contain "{text1}", "{text2}", and "{text3}"')
def verify_results_contain_three_texts(text1, text2, text3, grep_results):
"""Verify results contain three specific texts."""
found_text1 = False
found_text2 = False
found_text3 = False
for result in grep_results["results"]:
# Handle context-style results
if isinstance(result, dict) and "match" in result:
if text1 in result["match"]["line"]:
found_text1 = True
if text2 in result["match"]["line"]:
found_text2 = True
if text3 in result["match"]["line"]:
found_text3 = True
# Handle simple results
elif isinstance(result, dict) and "line" in result:
if text1 in result["line"]:
found_text1 = True
if text2 in result["line"]:
found_text2 = True
if text3 in result["line"]:
found_text3 = True
assert found_text1, f"Text '{text1}' not found in any result"
assert found_text2, f"Text '{text2}' not found in any result"
assert found_text3, f"Text '{text3}' not found in any result"
@then("the result should not include line numbers")
def verify_no_line_numbers(grep_results):
"""Verify that results don't include line numbers."""
assert grep_results.get("no_line_numbers", False), "Results should not include line numbers"
for result in grep_results["results"]:
assert "line_num" not in result, "Line number found in result"
@then("I should receive results only from log files")
def verify_results_from_log_files_only(grep_results):
"""Verify that results only come from files matching a pattern."""
for result in grep_results["results"]:
file_path = result["file"] if "file" in result else result["match"]["file"]
assert file_path.endswith(".log"), f"Result from non-matching file: {file_path}"
@then(parsers.parse("the result should include {count:d} lines before and {count:d} lines after the match"))
def verify_equal_context_lines(count, grep_results):
"""Verify that results include the specified number of context lines before and after."""
for match_with_context in grep_results["results"]:
# Using <= because there might be fewer lines available in the file
assert len(match_with_context["before_context"]) <= count, \
f"Expected {count} or fewer lines before, got {len(match_with_context['before_context'])}"
assert len(match_with_context["after_context"]) <= count, \
f"Expected {count} or fewer lines after, got {len(match_with_context['after_context'])}"
# Alternative step definition for two-text results with exact wording match
@then('the results should contain "apple123" and "apple789"')
def verify_results_contain_apple_results(grep_results):
"""Verify results contain apple123 and apple789."""
found_apple123 = False
found_apple789 = False
for result in grep_results["results"]:
# Handle various result formats
line = result.get("line", "")
if "match" in result and "line" in result["match"]:
line = result["match"]["line"]
if "apple123" in line:
found_apple123 = True
if "apple789" in line:
found_apple789 = True
assert found_apple123, "Text 'apple123' not found in any result"
assert found_apple789, "Text 'apple789' not found in any result"
# Alternative step definition for three-text results with exact wording match
@then('the results should contain "banana", "orange", and "grape"')
def verify_results_contain_fruit_results(grep_results):
"""Verify results contain banana, orange, and grape."""
found_banana = False
found_orange = False
found_grape = False
for result in grep_results["results"]:
# Handle various result formats
line = result.get("line", "")
if "match" in result and "line" in result["match"]:
line = result["match"]["line"]
if "banana" in line:
found_banana = True
if "orange" in line:
found_orange = True
if "grape" in line:
found_grape = True
assert found_banana, "Text 'banana' not found in any result"
assert found_orange, "Text 'orange' not found in any result"
assert found_grape, "Text 'grape' not found in any result"