Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,43 +1,45 @@
name: CI

on:
pull_request:
branches: [ main ]
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.11"]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v6
with:
version: "latest"
enable-cache: true
cache-dependency-glob: "uv.lock"

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync --all-extras --dev
run: uv sync --dev

- name: Run tests
run: uv run pytest -v --tb=short

- name: Run type checking
run: uv run mypy src/
run: uv run mypy src

- name: Run linting
run: uv run ruff check
run: uv run ruff check .

- name: Check formatting
run: uv run ruff format --check
Expand All @@ -51,12 +53,15 @@ jobs:
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
cache-dependency-glob: "uv.lock"

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
python-version: "3.11"

- name: Install dependencies
run: uv sync --dev
Expand Down Expand Up @@ -84,12 +89,15 @@ jobs:
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
cache-dependency-glob: "uv.lock"

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
python-version: "3.11"

- name: Install dependencies
run: uv sync --dev
Expand Down
6 changes: 0 additions & 6 deletions main.py

This file was deleted.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "diffchunk"
version = "0.1.5"
version = "0.1.6"
description = "MCP server for navigating large diff files with intelligent chunking"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
8 changes: 5 additions & 3 deletions src/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,14 @@ def _load_diff_internal(
resolved_file_path = os.path.realpath(os.path.expanduser(absolute_file_path))

# Validate file exists and is readable
if os.path.exists(resolved_file_path) and not os.path.isfile(
resolved_file_path
):
raise ValueError(f"Path is not a file: {resolved_file_path}")

if not os.path.exists(resolved_file_path):
raise ValueError(f"Diff file not found: {absolute_file_path}")

if not os.path.isfile(resolved_file_path):
raise ValueError(f"Path is not a file: {resolved_file_path}")

if not os.access(resolved_file_path, os.R_OK):
raise ValueError(f"Cannot read file: {resolved_file_path}")

Expand Down
5 changes: 5 additions & 0 deletions tests/test_cli_integration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""CLI integration tests for main.py entry point."""

import platform
import subprocess
import sys
import time
Expand Down Expand Up @@ -86,6 +87,10 @@ def test_cli_server_startup_basic(self):
process.kill()
process.wait()

@pytest.mark.skipif(
platform.system() == "Windows",
reason="Signal handling unreliable on Windows CI",
)
def test_cli_keyboard_interrupt_handling(self):
"""Test CLI handles KeyboardInterrupt gracefully."""
process = subprocess.Popen(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_filesystem_edge_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_load_diff_nonexistent_file(self, tools):
def test_load_diff_directory_instead_of_file(self, tools):
"""Test loading directory instead of file."""
with pytest.raises(ValueError, match="not a file"):
tools.load_diff("/tmp")
tools.load_diff(tempfile.gettempdir())

def test_load_diff_empty_file_path(self, tools):
"""Test loading with empty file path."""
Expand Down
90 changes: 90 additions & 0 deletions tests/test_handle_call_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Test handle_call_tool for code coverage."""

import json
from pathlib import Path

import pytest
from mcp.types import CallToolRequest

from src.server import DiffChunkServer


class TestHandleCallTool:
"""Test handle_call_tool for coverage."""

@pytest.fixture
def server(self):
return DiffChunkServer()

@pytest.fixture
def react_diff_file(self):
diff_file = Path(__file__).parent / "test_data" / "react_18.0_to_18.3.diff"
if not diff_file.exists():
pytest.skip("React test diff not found")
return str(diff_file)

@pytest.mark.asyncio
async def test_handle_call_tool_coverage(self, server, react_diff_file):
"""Test all paths in handle_call_tool for coverage."""
handler = server.app.request_handlers[CallToolRequest]

# Test load_diff
request = CallToolRequest(
method="tools/call",
params={
"name": "load_diff",
"arguments": {"absolute_file_path": react_diff_file},
},
)
result = await handler(request)
data = json.loads(result.root.content[0].text)
assert data["chunks"] > 0

# Test list_chunks
request = CallToolRequest(
method="tools/call",
params={
"name": "list_chunks",
"arguments": {"absolute_file_path": react_diff_file},
},
)
result = await handler(request)
chunks = json.loads(result.root.content[0].text)
assert len(chunks) > 0

# Test get_chunk
request = CallToolRequest(
method="tools/call",
params={
"name": "get_chunk",
"arguments": {"absolute_file_path": react_diff_file, "chunk_number": 1},
},
)
result = await handler(request)
assert "=== Chunk 1 of" in result.root.content[0].text

# Test find_chunks_for_files
request = CallToolRequest(
method="tools/call",
params={
"name": "find_chunks_for_files",
"arguments": {"absolute_file_path": react_diff_file, "pattern": "*"},
},
)
result = await handler(request)
chunk_nums = json.loads(result.root.content[0].text)
assert isinstance(chunk_nums, list)

# Test unknown tool
request = CallToolRequest(
method="tools/call", params={"name": "unknown_tool", "arguments": {}}
)
result = await handler(request)
assert "Unknown tool: unknown_tool" in result.root.content[0].text

# Test None arguments (validation error from MCP layer)
request = CallToolRequest(
method="tools/call", params={"name": "load_diff", "arguments": None}
)
result = await handler(request)
assert "Input validation error" in result.root.content[0].text
4 changes: 3 additions & 1 deletion tests/test_integration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Integration tests using real diff files from test_data."""

import tempfile

import pytest
from pathlib import Path

Expand Down Expand Up @@ -200,7 +202,7 @@ def test_invalid_file_errors(self, tools):

# Directory instead of file
with pytest.raises(ValueError, match="not a file"):
tools.load_diff("/tmp")
tools.load_diff(tempfile.gettempdir())

def test_chunk_size_consistency(self, tools, test_data_dir):
"""Test that chunk sizes are respected reasonably."""
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading