mirror of
https://github.com/lukaszraczylo/filepuff-mcp.git
synced 2026-06-14 02:51:27 +00:00
fixup! Update, bugfixes on diff and edit handling
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/lukaszraczylo/mcp-filepuff/internal/config"
|
||||
@@ -381,3 +382,155 @@ func Hello() {
|
||||
t.Error("handleEdit(apply) should modify the file")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleFileReadMaxFileSize verifies that handleFileRead rejects files
|
||||
// exceeding MaxFileSize via os.Stat before loading them into memory (T-03, S-01).
|
||||
func TestHandleFileReadMaxFileSize(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create a test file
|
||||
testFile := filepath.Join(tmpDir, "big.txt")
|
||||
content := make([]byte, 1024) // 1KB file
|
||||
for i := range content {
|
||||
content[i] = 'A'
|
||||
}
|
||||
if err := os.WriteFile(testFile, content, 0600); err != nil {
|
||||
t.Fatalf("failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
// Set MaxFileSize smaller than the file
|
||||
cfg := &config.Config{
|
||||
WorkspaceRoot: tmpDir,
|
||||
EnableLSP: false,
|
||||
MaxFileSize: 512, // 512 bytes — smaller than our 1KB file
|
||||
}
|
||||
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
|
||||
|
||||
srv, err := New(cfg, logger)
|
||||
if err != nil {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := mcp.CallToolRequest{}
|
||||
req.Params.Arguments = map[string]interface{}{
|
||||
"path": testFile,
|
||||
}
|
||||
|
||||
result, err := srv.handleFileRead(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatalf("handleFileRead() returned Go error: %v", err)
|
||||
}
|
||||
|
||||
// The result should indicate an error (file too large)
|
||||
if result == nil {
|
||||
t.Fatal("handleFileRead() returned nil result")
|
||||
}
|
||||
if !result.IsError {
|
||||
t.Error("expected IsError=true for file exceeding MaxFileSize")
|
||||
}
|
||||
contents := result.Content
|
||||
if len(contents) == 0 {
|
||||
t.Fatal("expected non-empty content with error message")
|
||||
}
|
||||
textContent, ok := contents[0].(mcp.TextContent)
|
||||
if !ok {
|
||||
t.Fatal("expected text content")
|
||||
}
|
||||
if !strings.Contains(textContent.Text, "too large") {
|
||||
t.Errorf("expected 'too large' error message, got: %s", textContent.Text)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSplitLinesLargeFile tests the splitLines function with a large file (>1MB)
|
||||
// to exercise the bufio.Scanner path including the scanner.Err() check (T-07, C-05).
|
||||
func TestSplitLinesLargeFile(t *testing.T) {
|
||||
// Build a string >1MB with known line count
|
||||
lineCount := 20000
|
||||
var sb strings.Builder
|
||||
for i := 0; i < lineCount; i++ {
|
||||
sb.WriteString(strings.Repeat("x", 60))
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
largeContent := sb.String()
|
||||
|
||||
// Verify it's large enough to trigger the scanner path
|
||||
if len(largeContent) <= 1024*1024 {
|
||||
t.Fatalf("test content too small: %d bytes, need >1MB", len(largeContent))
|
||||
}
|
||||
|
||||
lines := splitLines(largeContent)
|
||||
|
||||
// String ends with \n, so splitLines adds an empty trailing element
|
||||
// (matching the behavior of strings.Split for the small-file path)
|
||||
expectedLines := lineCount + 1 // lineCount lines + 1 trailing empty
|
||||
if len(lines) != expectedLines {
|
||||
t.Errorf("splitLines returned %d lines, expected %d", len(lines), expectedLines)
|
||||
}
|
||||
|
||||
// Check first and last actual lines
|
||||
if lines[0] != strings.Repeat("x", 60) {
|
||||
t.Errorf("first line mismatch: got %q", lines[0][:20])
|
||||
}
|
||||
if lines[lineCount-1] != strings.Repeat("x", 60) {
|
||||
t.Errorf("last content line mismatch: got %q", lines[lineCount-1][:20])
|
||||
}
|
||||
if lines[lineCount] != "" {
|
||||
t.Errorf("expected empty trailing line, got %q", lines[lineCount])
|
||||
}
|
||||
}
|
||||
|
||||
// TestSplitLinesLargeFileNoTrailingNewline verifies splitLines for large files
|
||||
// without a trailing newline.
|
||||
func TestSplitLinesLargeFileNoTrailingNewline(t *testing.T) {
|
||||
lineCount := 20000
|
||||
var sb strings.Builder
|
||||
for i := 0; i < lineCount; i++ {
|
||||
if i > 0 {
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
sb.WriteString(strings.Repeat("y", 60))
|
||||
}
|
||||
largeContent := sb.String()
|
||||
|
||||
if len(largeContent) <= 1024*1024 {
|
||||
t.Fatalf("test content too small: %d bytes", len(largeContent))
|
||||
}
|
||||
|
||||
lines := splitLines(largeContent)
|
||||
if len(lines) != lineCount {
|
||||
t.Errorf("splitLines returned %d lines, expected %d", len(lines), lineCount)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSplitLinesLongLine verifies the scanner gracefully handles very long lines
|
||||
// (up to the 1MB buffer limit set in C-05 fix).
|
||||
func TestSplitLinesLongLine(t *testing.T) {
|
||||
// Create content with one very long line (500KB) embedded in a >1MB file
|
||||
shortLines := strings.Repeat("short line content\n", 60000) // ~60KB * ~1 = ~1.08MB
|
||||
longLine := strings.Repeat("L", 500*1024) // 500KB line
|
||||
largeContent := shortLines + longLine + "\n"
|
||||
|
||||
if len(largeContent) <= 1024*1024 {
|
||||
t.Fatalf("test content too small: %d bytes", len(largeContent))
|
||||
}
|
||||
|
||||
lines := splitLines(largeContent)
|
||||
|
||||
// Should not crash and should return some lines
|
||||
if len(lines) == 0 {
|
||||
t.Fatal("splitLines returned no lines for valid content")
|
||||
}
|
||||
|
||||
// The long line should be present somewhere in the output
|
||||
foundLong := false
|
||||
for _, line := range lines {
|
||||
if len(line) >= 500*1024 {
|
||||
foundLong = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundLong {
|
||||
t.Error("the 500KB long line was not found in splitLines output")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user