Skip to main content
Glama
DecompiledTextSearchServiceTest.java14.7 kB
package com.ghidramcp.services; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.CsvSource; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import static org.junit.jupiter.api.Assertions.*; /** * Unit tests for DecompiledTextSearchService. * * These tests verify pattern compilation, parameter validation, JSON formatting, * and edge case handling for the decompiled text search functionality. * * Note: Full integration tests with actual Ghidra decompilation would require * the Ghidra test framework and Program fixtures. */ class DecompiledTextSearchServiceTest { // ==================== Pattern Compilation Tests ==================== @Test @DisplayName("Should compile valid regex patterns") void testValidRegexCompilation() { assertDoesNotThrow(() -> Pattern.compile("malloc\\s*\\("), "Should compile valid regex pattern"); assertDoesNotThrow(() -> Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*"), "Should compile identifier regex"); assertDoesNotThrow(() -> Pattern.compile("if\\s*\\([^)]*\\)\\s*\\{"), "Should compile complex regex"); } @Test @DisplayName("Should handle literal string patterns with special characters") void testLiteralStringWithSpecialChars() { String literal = "malloc()"; String quoted = Pattern.quote(literal); Pattern pattern = Pattern.compile(quoted); assertNotNull(pattern, "Pattern should be created"); assertTrue(pattern.matcher("malloc()").find(), "Should match exact literal"); assertFalse(pattern.matcher("mallocXY").find(), "Should not match partial"); } @Test @DisplayName("Should detect invalid regex patterns") void testInvalidRegexPatterns() { assertThrows(PatternSyntaxException.class, () -> Pattern.compile("["), "Should throw on unclosed bracket"); assertThrows(PatternSyntaxException.class, () -> Pattern.compile("("), "Should throw on unclosed parenthesis"); assertThrows(PatternSyntaxException.class, () -> Pattern.compile("*"), "Should throw on dangling quantifier"); } @ParameterizedTest @DisplayName("Should support case-insensitive flag") @CsvSource({ "HELLO, hello", "WORLD, world", "MaLlOc, malloc" }) void testCaseInsensitivityFlag(String pattern, String text) { Pattern caseInsensitive = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); Pattern caseSensitive = Pattern.compile(pattern); assertTrue(caseInsensitive.matcher(text).find(), "Case insensitive pattern should match different case"); assertFalse(caseSensitive.matcher(text).find(), "Case sensitive pattern should not match different case"); } @Test @DisplayName("Should support case-sensitive matching by default") void testCaseSensitiveDefault() { Pattern pattern = Pattern.compile("HELLO"); assertTrue(pattern.matcher("HELLO").find(), "Should match same case"); assertFalse(pattern.matcher("hello").find(), "Should not match different case"); } @Test @DisplayName("Should support multiline/DOTALL flag") void testMultilineFlag() { String text = "line1\nline2\nline3"; // Without DOTALL, . doesn't match newlines Pattern singleLine = Pattern.compile("line1.*line3"); assertFalse(singleLine.matcher(text).find(), "Without DOTALL, . should not match newlines"); // With DOTALL, . matches newlines Pattern multiLine = Pattern.compile("line1.*line3", Pattern.DOTALL); assertTrue(multiLine.matcher(text).find(), "With DOTALL, . should match newlines"); } // ==================== Parameter Validation Tests ==================== @Test @DisplayName("Should validate null pattern parameter") void testNullPattern() { // In real implementation, this should return error JSON String nullPattern = null; assertNull(nullPattern, "Null pattern should be detected"); // Expected result: {"error": "Pattern is required"} } @Test @DisplayName("Should validate empty pattern parameter") void testEmptyPattern() { String emptyPattern = ""; assertTrue(emptyPattern.isEmpty(), "Empty pattern should be detected"); // Expected result: {"error": "Pattern is required"} } @ParameterizedTest @DisplayName("Should accept valid pagination parameters") @CsvSource({ "0, 10", "0, 100", "50, 50", "100, 1" }) void testValidPaginationParams(int offset, int limit) { assertTrue(offset >= 0, "Offset should be non-negative"); assertTrue(limit > 0, "Limit should be positive"); } @Test @DisplayName("Should handle maxResults parameter correctly") void testMaxResultsParameter() { int unlimited = 0; int limited = 100; assertTrue(unlimited >= 0, "maxResults=0 means unlimited"); assertTrue(limited > 0, "maxResults>0 means limited"); } // ==================== Function Name Filtering Tests ==================== @Test @DisplayName("Should handle null function names list") void testNullFunctionNamesList() { List<String> functionNames = null; // Null should mean "search all functions" assertTrue(functionNames == null, "Null list should search all functions"); } @Test @DisplayName("Should handle empty function names list") void testEmptyFunctionNamesList() { List<String> functionNames = Arrays.asList(); // Empty list should also mean "search all functions" assertTrue(functionNames.isEmpty(), "Empty list should search all functions"); } @Test @DisplayName("Should parse comma-separated function names") void testFunctionNamesListParsing() { String input = "main,authenticate,login"; String[] parts = input.split(","); assertEquals(3, parts.length, "Should parse 3 function names"); assertEquals("main", parts[0].trim(), "First function name"); assertEquals("authenticate", parts[1].trim(), "Second function name"); assertEquals("login", parts[2].trim(), "Third function name"); } @Test @DisplayName("Should handle function names with spaces") void testFunctionNamesWithSpaces() { String input = " main , authenticate , login "; String[] parts = input.split(","); assertEquals("main", parts[0].trim(), "Should trim whitespace"); assertEquals("authenticate", parts[1].trim(), "Should trim whitespace"); assertEquals("login", parts[2].trim(), "Should trim whitespace"); } // ==================== JSON Output Format Tests ==================== @Test @DisplayName("Should format error response as valid JSON") void testErrorResponseFormat() { String errorMessage = "Pattern is required"; String expectedFormat = String.format("{\"error\": \"%s\"}", errorMessage); assertTrue(expectedFormat.startsWith("{"), "Should start with {"); assertTrue(expectedFormat.endsWith("}"), "Should end with }"); assertTrue(expectedFormat.contains("\"error\":"), "Should contain error field"); } @Test @DisplayName("Should format successful response with all required fields") void testSuccessResponseFormat() { // Expected format: // {"matches": [...], "count": N, "total_count": M, "offset": O, "limit": L} String format = "{\"matches\": [], \"count\": 0, \"total_count\": 0, \"offset\": 0, \"limit\": 100}"; assertTrue(format.contains("\"matches\":"), "Should contain matches field"); assertTrue(format.contains("\"count\":"), "Should contain count field"); assertTrue(format.contains("\"total_count\":"), "Should contain total_count field"); assertTrue(format.contains("\"offset\":"), "Should contain offset field"); assertTrue(format.contains("\"limit\":"), "Should contain limit field"); } @Test @DisplayName("Should format match object with all required fields") void testMatchObjectFormat() { // Expected format: // {"function_name": "...", "function_address": "...", "line_number": N, // "matched_text": "...", "context": "...", "is_multiline": false} String format = "{\"function_name\": \"main\", \"function_address\": \"0x401000\", " + "\"line_number\": 42, \"matched_text\": \"malloc\", " + "\"context\": \"ptr = malloc(100);\", \"is_multiline\": false}"; assertTrue(format.contains("\"function_name\":"), "Should contain function_name"); assertTrue(format.contains("\"function_address\":"), "Should contain function_address"); assertTrue(format.contains("\"line_number\":"), "Should contain line_number"); assertTrue(format.contains("\"matched_text\":"), "Should contain matched_text"); assertTrue(format.contains("\"context\":"), "Should contain context"); assertTrue(format.contains("\"is_multiline\":"), "Should contain is_multiline"); } @Test @DisplayName("Should escape special characters in JSON strings") void testJsonEscaping() { String withQuotes = "char *str = \"hello\";"; String withNewlines = "line1\nline2"; String withBackslash = "C:\\path\\to\\file"; // JSON escaping rules String escapedQuotes = withQuotes.replace("\"", "\\\""); String escapedNewlines = withNewlines.replace("\n", "\\n"); String escapedBackslash = withBackslash.replace("\\", "\\\\"); assertTrue(escapedQuotes.contains("\\\""), "Should escape quotes"); assertTrue(escapedNewlines.contains("\\n"), "Should escape newlines"); assertTrue(escapedBackslash.contains("\\\\"), "Should escape backslashes"); } // ==================== Edge Case Tests ==================== @Test @DisplayName("Should handle pattern matching nothing") void testNoMatches() { String pattern = "IMPOSSIBLE_PATTERN_XYZ123"; // Expected result: {"matches": [], "count": 0, "total_count": 0, ...} assertNotNull(pattern, "Pattern should be valid"); } @Test @DisplayName("Should handle very long patterns") void testLongPattern() { StringBuilder longPattern = new StringBuilder(); for (int i = 0; i < 1000; i++) { longPattern.append("a"); } assertDoesNotThrow(() -> Pattern.compile(longPattern.toString()), "Should compile very long pattern"); } @Test @DisplayName("Should handle pagination beyond available results") void testPaginationBeyondResults() { int totalResults = 10; int offset = 100; int limit = 10; if (offset >= totalResults) { // Should return empty array assertTrue(offset >= totalResults, "Offset beyond results should return empty"); } } @Test @DisplayName("Should handle single-line vs multi-line search") void testSingleVsMultiLineSearch() { String text = "if (x > 0)\n return true;"; // Single-line: search each line independently Pattern singleLine = Pattern.compile("if.*return"); assertFalse(singleLine.matcher(text).find(), "Single-line should not match across newlines"); // Multi-line: search entire text Pattern multiLine = Pattern.compile("if.*return", Pattern.DOTALL); assertTrue(multiLine.matcher(text).find(), "Multi-line should match across newlines"); } @Test @DisplayName("Should handle match context extraction") void testContextExtraction() { String line = " char *buffer = malloc(100);"; int matchStart = line.indexOf("malloc"); int matchEnd = matchStart + "malloc".length(); // Context should highlight the match String expectedContext = " char *buffer = [[malloc]](100);"; assertTrue(matchStart >= 0, "Match should be found"); assertTrue(matchEnd > matchStart, "Match end should be after start"); } @Test @DisplayName("Should handle line number calculation") void testLineNumberCalculation() { String text = "line1\nline2\nline3"; int positionOfLine3 = text.indexOf("line3"); // Count newlines before position int lineNumber = 1; for (int i = 0; i < positionOfLine3; i++) { if (text.charAt(i) == '\n') { lineNumber++; } } assertEquals(3, lineNumber, "Should calculate correct line number"); } // ==================== Security Pattern Tests ==================== @ParameterizedTest @DisplayName("Should match common security-relevant patterns") @ValueSource(strings = { "malloc\\s*\\(", "strcpy\\s*\\(", "gets\\s*\\(", "sprintf\\s*\\(", "memcpy\\s*\\(", "system\\s*\\(", "exec[vl]?p?\\s*\\(" }) void testSecurityPatterns(String pattern) { assertDoesNotThrow(() -> Pattern.compile(pattern), "Security pattern should compile: " + pattern); } @Test @DisplayName("Should match buffer allocation patterns") void testBufferAllocationPattern() { String pattern = "(malloc|calloc|realloc|alloca)\\s*\\("; Pattern compiled = Pattern.compile(pattern); assertTrue(compiled.matcher("ptr = malloc(100);").find(), "Should match malloc"); assertTrue(compiled.matcher("data = calloc(10, sizeof(int));").find(), "Should match calloc"); assertTrue(compiled.matcher("buf = realloc(buf, 200);").find(), "Should match realloc"); } @Test @DisplayName("Should match string operation patterns") void testStringOperationPattern() { String pattern = "(strcpy|strcat|sprintf|gets)\\s*\\("; Pattern compiled = Pattern.compile(pattern); assertTrue(compiled.matcher("strcpy(dest, src);").find(), "Should match strcpy"); assertTrue(compiled.matcher("strcat(buffer, str);").find(), "Should match strcat"); assertTrue(compiled.matcher("sprintf(buf, format, arg);").find(), "Should match sprintf"); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/HK47196/GhidraMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server