#
tokens: 4969/50000 1/71 files (page 2/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 2. Use http://codebase.md/ai-zerolab/mcp-toolbox?page={x} to view the full context.

# Directory Structure

```
├── .github
│   ├── actions
│   │   └── setup-python-env
│   │       └── action.yml
│   └── workflows
│       ├── main.yml
│       ├── on-release-main.yml
│       └── validate-codecov-config.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .vscode
│   └── settings.json
├── codecov.yaml
├── CONTRIBUTING.md
├── Dockerfile
├── docs
│   ├── index.md
│   └── modules.md
├── generate_config_template.py
├── LICENSE
├── llms.txt
├── Makefile
├── mcp_toolbox
│   ├── __init__.py
│   ├── app.py
│   ├── audio
│   │   ├── __init__.py
│   │   └── tools.py
│   ├── cli.py
│   ├── command_line
│   │   ├── __init__.py
│   │   └── tools.py
│   ├── config.py
│   ├── enhance
│   │   ├── __init__.py
│   │   ├── memory.py
│   │   └── tools.py
│   ├── figma
│   │   ├── __init__.py
│   │   └── tools.py
│   ├── file_ops
│   │   ├── __init__.py
│   │   └── tools.py
│   ├── flux
│   │   ├── __init__.py
│   │   ├── api.py
│   │   └── tools.py
│   ├── log.py
│   ├── markitdown
│   │   ├── __init__.py
│   │   └── tools.py
│   ├── web
│   │   ├── __init__.py
│   │   └── tools.py
│   └── xiaoyuzhoufm
│       ├── __init__.py
│       └── tools.py
├── mkdocs.yml
├── pyproject.toml
├── pytest.ini
├── README.md
├── smithery.yaml
├── tests
│   ├── audio
│   │   └── test_audio_tools.py
│   ├── command_line
│   │   └── test_command_line_tools.py
│   ├── enhance
│   │   ├── test_enhance_tools.py
│   │   └── test_memory.py
│   ├── figma
│   │   └── test_figma_tools.py
│   ├── file_ops
│   │   └── test_file_ops_tools.py
│   ├── flux
│   │   └── test_flux_tools.py
│   ├── markitdown
│   │   └── test_markitdown_tools.py
│   ├── mock
│   │   └── figma
│   │       ├── delete_comment.json
│   │       ├── get_comments.json
│   │       ├── get_component.json
│   │       ├── get_file_components.json
│   │       ├── get_file_nodes.json
│   │       ├── get_file_styles.json
│   │       ├── get_file.json
│   │       ├── get_image_fills.json
│   │       ├── get_image.json
│   │       ├── get_project_files.json
│   │       ├── get_style.json
│   │       ├── get_team_component_sets.json
│   │       ├── get_team_components.json
│   │       ├── get_team_projects.json
│   │       ├── get_team_styles.json
│   │       └── post_comment.json
│   ├── web
│   │   └── test_web_tools.py
│   └── xiaoyuzhoufm
│       └── test_xiaoyuzhoufm_tools.py
├── tox.ini
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/tests/file_ops/test_file_ops_tools.py:
--------------------------------------------------------------------------------

```python
"""Tests for file operations tools."""

import os
import stat
import tempfile
from pathlib import Path
from unittest.mock import patch

import pytest

from mcp_toolbox.file_ops.tools import (
    _format_mode,
    _get_file_info,
    list_directory,
    read_file_content,
    replace_in_file,
    write_file_content,
)


@pytest.mark.asyncio
async def test_read_file_content():
    """Test reading file content."""
    # Create a temporary file
    with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file:
        temp_file.write("Test content")
        temp_path = temp_file.name

    try:
        # Test reading the entire file
        result = await read_file_content(temp_path)
        assert result["success"] is True
        assert result["content"] == "Test content"
        assert "size" in result
        assert "last_modified" in result
        assert result["total_chunks"] == 1
        assert result["chunk_index"] == 0
        assert result["is_last_chunk"] is True

        # Test reading a non-existent file
        result = await read_file_content("/non/existent/file")
        assert result["success"] is False
        assert "File not found" in result["error"]

        # Test reading a directory
        temp_dir = tempfile.mkdtemp()
        try:
            result = await read_file_content(temp_dir)
            assert result["success"] is False
            assert "Path is not a file" in result["error"]
        finally:
            os.rmdir(temp_dir)

        # Test reading a file with tilde in path
        with patch("pathlib.Path.expanduser", return_value=Path(temp_path)) as mock_expanduser:
            result = await read_file_content("~/test_file.txt")
            assert result["success"] is True
            assert result["content"] == "Test content"
            mock_expanduser.assert_called_once()

        # Test reading with custom chunk size
        result = await read_file_content(temp_path, chunk_size=5)
        assert result["success"] is True
        assert result["content"] == "Test "
        assert result["chunk_size"] == 5
        assert result["chunk_index"] == 0
        assert result["total_chunks"] == 3  # "Test content" is 12 chars, so 3 chunks of 5 bytes
        assert result["is_last_chunk"] is False

        # Test reading second chunk
        result = await read_file_content(temp_path, chunk_size=5, chunk_index=1)
        assert result["success"] is True
        assert result["content"] == "conte"
        assert result["chunk_index"] == 1
        assert result["is_last_chunk"] is False

        # Test reading last chunk
        result = await read_file_content(temp_path, chunk_size=5, chunk_index=2)
        assert result["success"] is True
        assert result["content"] == "nt"
        assert result["chunk_index"] == 2
        assert result["is_last_chunk"] is True
        assert result["chunk_actual_size"] == 2  # Only 2 bytes in the last chunk

        # Test reading with invalid chunk index
        result = await read_file_content(temp_path, chunk_size=5, chunk_index=3)
        assert result["success"] is False
        assert "Invalid chunk index" in result["error"]

    finally:
        # Clean up
        os.unlink(temp_path)


@pytest.mark.asyncio
async def test_write_file_content():
    """Test writing file content."""
    # Create a temporary directory
    with tempfile.TemporaryDirectory() as temp_dir:
        # Test writing to a new file
        file_path = os.path.join(temp_dir, "test_file.txt")
        result = await write_file_content(file_path, "Test content")
        assert result["success"] is True
        assert os.path.exists(file_path)
        with open(file_path) as f:
            assert f.read() == "Test content"

        # Test appending to a file
        result = await write_file_content(file_path, " appended", append=True)
        assert result["success"] is True
        with open(file_path) as f:
            assert f.read() == "Test content appended"

        # Test writing to a nested path
        nested_path = os.path.join(temp_dir, "nested", "dir", "test_file.txt")
        result = await write_file_content(nested_path, "Nested content")
        assert result["success"] is True
        assert os.path.exists(nested_path)
        with open(nested_path) as f:
            assert f.read() == "Nested content"

        # Test writing to a file with tilde in path
        tilde_path = "~/test_file_tilde.txt"
        expanded_path = os.path.join(temp_dir, "test_file_tilde.txt")

        with patch("pathlib.Path.expanduser", return_value=Path(expanded_path)) as mock_expanduser:
            result = await write_file_content(tilde_path, "Tilde content")
            assert result["success"] is True
            mock_expanduser.assert_called_once()
            # Verify the file was created at the expanded path
            assert os.path.exists(expanded_path)
            with open(expanded_path) as f:
                assert f.read() == "Tilde content"


@pytest.mark.asyncio
async def test_replace_in_file():
    """Test replacing content in a file."""
    # Create a temporary file
    with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file:
        temp_file.write("Hello world! This is a test.")
        temp_path = temp_file.name

    try:
        # Test replacing content
        result = await replace_in_file(temp_path, r"world", "universe")
        assert result["success"] is True
        assert result["replacements"] == 1
        with open(temp_path) as f:
            assert f.read() == "Hello universe! This is a test."

        # Test replacing with count
        result = await replace_in_file(temp_path, r"[aeiou]", "X", count=2)
        assert result["success"] is True
        assert result["replacements"] == 2
        with open(temp_path) as f:
            assert f.read() == "HXllX universe! This is a test."

        # Test replacing with invalid regex
        result = await replace_in_file(temp_path, r"[unclosed", "X")
        assert result["success"] is False
        assert "Invalid regular expression" in result["error"]

        # Test replacing in non-existent file
        result = await replace_in_file("/non/existent/file", r"test", "replacement")
        assert result["success"] is False
        assert "File not found" in result["error"]

        # Test replacing in a file with tilde in path
        with patch("pathlib.Path.expanduser", return_value=Path(temp_path)) as mock_expanduser:
            result = await replace_in_file("~/test_file.txt", r"HXllX", "Hello")
            assert result["success"] is True
            assert result["replacements"] == 1
            mock_expanduser.assert_called_once()
            with open(temp_path) as f:
                assert f.read() == "Hello universe! This is a test."

    finally:
        # Clean up
        os.unlink(temp_path)


def test_format_mode():
    """Test formatting file mode."""
    # Directory with full permissions
    dir_mode = stat.S_IFDIR | 0o777
    assert _format_mode(dir_mode) == "drwxrwxrwx"

    # Regular file with read-only permissions
    file_mode = stat.S_IFREG | 0o444
    assert _format_mode(file_mode) == "-r--r--r--"

    # Executable file with owner-only permissions
    exec_mode = stat.S_IFREG | 0o700
    assert _format_mode(exec_mode) == "-rwx------"

    # Symlink with mixed permissions
    link_mode = stat.S_IFLNK | 0o751
    assert _format_mode(link_mode) == "lrwxr-x--x"


@pytest.mark.asyncio
async def test_list_directory():
    """Test listing directory contents."""
    # Create a temporary directory structure
    with tempfile.TemporaryDirectory() as temp_dir:
        # Create some files and subdirectories
        file1_path = os.path.join(temp_dir, "file1.txt")
        with open(file1_path, "w") as f:
            f.write("File 1 content")

        file2_path = os.path.join(temp_dir, "file2.txt")
        with open(file2_path, "w") as f:
            f.write("File 2 content")

        hidden_file_path = os.path.join(temp_dir, ".hidden_file")
        with open(hidden_file_path, "w") as f:
            f.write("Hidden file content")

        subdir_path = os.path.join(temp_dir, "subdir")
        os.mkdir(subdir_path)

        subfile_path = os.path.join(subdir_path, "subfile.txt")
        with open(subfile_path, "w") as f:
            f.write("Subfile content")

        # Create files for testing ignore patterns
        temp_file_path = os.path.join(temp_dir, "temp.tmp")
        with open(temp_file_path, "w") as f:
            f.write("Temporary file content")

        node_modules_path = os.path.join(temp_dir, "node_modules")
        os.mkdir(node_modules_path)

        node_module_file_path = os.path.join(node_modules_path, "package.json")
        with open(node_module_file_path, "w") as f:
            f.write('{"name": "test-package"}')

        cache_file_path = os.path.join(temp_dir, "cache.pyc")
        with open(cache_file_path, "w") as f:
            f.write("Python cache file")

        # Test basic directory listing
        result = await list_directory(temp_dir)
        assert result["success"] is True
        assert result["path"] == temp_dir
        assert (
            len(result["entries"]) == 5
        )  # 4 files + 1 directory (node_modules is ignored by default), no hidden files
        assert result["count"] == 5

        # Test with hidden files
        result = await list_directory(temp_dir, include_hidden=True)
        assert result["success"] is True
        assert len(result["entries"]) == 6  # 4 files + 1 directory + 1 hidden file (node_modules is ignored by default)
        assert result["count"] == 6

        # Test recursive listing with explicit empty ignore patterns to see all files
        result = await list_directory(temp_dir, recursive=True, ignore_patterns=[])
        assert result["success"] is True
        assert len(result["entries"]) == 8  # 4 files + 2 directories + 1 subfile + 1 node_module file
        assert result["count"] == 8

        # Test with max depth (with empty ignore patterns to ensure consistent behavior)
        result = await list_directory(temp_dir, recursive=True, max_depth=0, ignore_patterns=[])
        assert result["success"] is True
        assert len(result["entries"]) == 6  # Only top-level entries
        assert result["count"] == 6

        # Test with ignore patterns - single pattern
        result = await list_directory(temp_dir, ignore_patterns=["*.tmp"])
        assert result["success"] is True
        assert len(result["entries"]) == 5  # Excluding temp.tmp
        assert result["count"] == 5
        # Verify temp.tmp is not in the results
        assert not any(entry["name"] == "temp.tmp" for entry in result["entries"])

        # Test with ignore patterns - directory pattern
        result = await list_directory(temp_dir, recursive=True, ignore_patterns=["node_modules"])
        assert result["success"] is True
        assert len(result["entries"]) == 6  # Excluding node_modules directory and its contents
        assert result["count"] == 6
        # Verify node_modules is not in the results
        assert not any(entry["name"] == "node_modules" for entry in result["entries"])

        # Test with multiple ignore patterns
        result = await list_directory(temp_dir, ignore_patterns=["*.tmp", "*.pyc"])
        assert result["success"] is True
        assert len(result["entries"]) == 4  # Excluding temp.tmp and cache.pyc
        assert result["count"] == 4
        # Verify neither temp.tmp nor cache.pyc are in the results
        assert not any(entry["name"] == "temp.tmp" for entry in result["entries"])
        assert not any(entry["name"] == "cache.pyc" for entry in result["entries"])

        # Test default ignore patterns
        # Create directories and files that should be ignored by default
        git_dir_path = os.path.join(temp_dir, ".git")
        os.mkdir(git_dir_path)
        git_file_path = os.path.join(git_dir_path, "config")
        with open(git_file_path, "w") as f:
            f.write("Git config content")

        # Create a .DS_Store file that should be ignored by default
        ds_store_path = os.path.join(temp_dir, ".DS_Store")
        with open(ds_store_path, "w") as f:
            f.write("DS_Store content")

        # Create a node_modules directory that should be ignored by default
        node_modules_dir = os.path.join(temp_dir, "node_modules")
        os.makedirs(node_modules_dir, exist_ok=True)
        node_modules_file = os.path.join(node_modules_dir, "package.json")
        with open(node_modules_file, "w") as f:
            f.write('{"name": "test-package"}')

        # Test with explicit ignore patterns that match the default ones
        result = await list_directory(
            temp_dir, recursive=True, include_hidden=True, ignore_patterns=[".git", ".DS_Store", "node_modules"]
        )
        assert result["success"] is True
        # Should not include .git directory or .DS_Store file due to specified ignore patterns
        assert not any(entry["name"] == ".git" for entry in result["entries"])
        assert not any(entry["name"] == ".DS_Store" for entry in result["entries"])
        assert not any(entry["name"] == "node_modules" for entry in result["entries"])
        assert any(entry["name"] == ".hidden_file" for entry in result["entries"])  # Should include other hidden files

        # Test with explicit None for ignore_patterns (should use defaults)
        result = await list_directory(temp_dir, recursive=True, include_hidden=True, ignore_patterns=None)
        assert result["success"] is True
        # Should not include node_modules directory due to default ignore patterns
        assert not any(entry["name"] == "node_modules" for entry in result["entries"])
        # Should not include .git directory due to default ignore patterns
        assert not any(entry["name"] == ".git" for entry in result["entries"])
        # Should not include .DS_Store file due to default ignore patterns
        assert not any(entry["name"] == ".DS_Store" for entry in result["entries"])

        # Test with empty list for ignore_patterns (should override defaults)
        result = await list_directory(temp_dir, recursive=True, include_hidden=True, ignore_patterns=[])
        assert result["success"] is True
        # Should include .git directory and .DS_Store file since we're overriding defaults with empty list
        assert any(entry["name"] == ".git" for entry in result["entries"])
        assert any(entry["name"] == ".DS_Store" for entry in result["entries"])

        # Test combining ignore patterns with other parameters
        result = await list_directory(
            temp_dir,
            recursive=True,
            include_hidden=True,
            ignore_patterns=["node_modules", "*.tmp", "*.pyc", ".git", ".DS_Store"],
        )
        assert result["success"] is True
        # Should include only .hidden_file, file1.txt, file2.txt, subdir, and subfile.txt
        assert len(result["entries"]) == 5
        assert result["count"] == 5
        # Verify specific files are included/excluded
        assert any(entry["name"] == ".hidden_file" for entry in result["entries"])
        assert any(entry["name"] == "file1.txt" for entry in result["entries"])
        assert any(entry["name"] == "file2.txt" for entry in result["entries"])
        assert any(entry["name"] == "subdir" for entry in result["entries"])
        assert any(entry["name"] == "subfile.txt" for entry in result["entries"])
        # Verify excluded files
        assert not any(entry["name"] == "node_modules" for entry in result["entries"])
        assert not any(entry["name"] == "temp.tmp" for entry in result["entries"])
        assert not any(entry["name"] == "cache.pyc" for entry in result["entries"])
        assert not any(entry["name"] == ".git" for entry in result["entries"])
        assert not any(entry["name"] == ".DS_Store" for entry in result["entries"])

        # Test non-existent directory
        result = await list_directory("/non/existent/dir")
        assert result["success"] is False
        assert "Directory not found" in result["error"]

        # Test file path instead of directory
        result = await list_directory(file1_path)
        assert result["success"] is False
        assert "Path is not a directory" in result["error"]

        # Test directory with tilde in path (with empty ignore patterns to ensure consistent behavior)
        with patch("pathlib.Path.expanduser", return_value=Path(temp_dir)) as mock_expanduser:
            result = await list_directory("~/test_dir", ignore_patterns=[])
            assert result["success"] is True
            assert len(result["entries"]) == 6  # 4 files + 2 directories, no hidden files
            assert result["count"] == 6
            mock_expanduser.assert_called_once()


def test_get_file_info():
    """Test getting file information."""
    # Create a temporary file
    with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file:
        temp_file.write("Test content")
        temp_path = temp_file.name

    try:
        # Get file info
        file_path = Path(temp_path)
        file_info = _get_file_info(file_path)

        # Check basic properties
        assert file_info["name"] == file_path.name
        assert file_info["path"] == str(file_path)
        assert file_info["type"] == "file"
        assert file_info["size"] == len("Test content")
        assert "size_formatted" in file_info
        assert "permissions" in file_info
        assert "mode" in file_info
        assert "owner" in file_info
        assert "group" in file_info
        assert "created" in file_info
        assert "modified" in file_info
        assert "accessed" in file_info

    finally:
        # Clean up
        os.unlink(temp_path)

```
Page 2/2FirstPrevNextLast