Skip to main content
Glama
joshuadavidthomas

Django Shell MCP Server

test_export.py7.37 kB
from __future__ import annotations import os from pathlib import Path import pytest from mcp_django.shell.core import DjangoShell @pytest.fixture def shell(): shell = DjangoShell() yield shell shell.clear_history() class TestExportHistory: def test_export_empty_history(self, shell): """Export with no history returns empty comment.""" result = shell.export_history() assert "No history" in result def test_export_basic_code(self, shell): """Export basic execution to script.""" shell._execute("x = 2 + 2") script = shell.export_history() assert "# Django Shell Session Export" in script assert "# Step 1" in script assert "x = 2 + 2" in script def test_export_excludes_output(self, shell): """Export does not include execution results.""" shell._execute("print(2 + 2)") script = shell.export_history() assert "# → 4" not in script assert "print(2 + 2)" in script def test_export_excludes_errors(self, shell): """Export excludes error results.""" shell._execute("1 / 0") script = shell.export_history() # Should have header but no steps assert "# Django Shell Session Export" in script assert "1 / 0" not in script def test_export_continuous_step_numbers(self, shell): """Export has continuous step numbers even when errors are skipped.""" # Execute: success, error, success shell._execute("x = 2 + 2") shell._execute("1 / 0") shell._execute("y = 3 + 3") script = shell.export_history() # Should have Step 1 and Step 2 (not Step 1 and Step 3) assert "# Step 1" in script assert "# Step 2" in script assert "# Step 3" not in script assert "x = 2 + 2" in script assert "1 / 0" not in script assert "y = 3 + 3" in script def test_export_deduplicates_imports(self, shell): """Export always consolidates imports at the top.""" # Execute code with same import twice (without DB access) code1 = "from datetime import datetime\nx = datetime.now()" shell._execute(code1) code2 = "from datetime import datetime\ny = datetime.now()" shell._execute(code2) script = shell.export_history() # Import should appear at top before steps lines = script.split("\n") # Find where steps start and where consolidated imports are first_step_idx = next(i for i, l in enumerate(lines) if "# Step 1" in l) # The consolidated import should be before the first step consolidated_section = "\n".join(lines[:first_step_idx]) assert "from datetime import datetime" in consolidated_section # Steps should still have the full code (imports aren't removed from steps) steps_section = "\n".join(lines[first_step_idx:]) assert "from datetime import datetime" in steps_section def test_export_to_file(self, shell, tmp_path): """Export saves to file when filename provided.""" shell._execute("x = 2 + 2") # Use temp directory old_cwd = Path.cwd() os.chdir(tmp_path) try: result = shell.export_history(filename="test_export") # Should return confirmation assert "Exported" in result assert "test_export.py" in result # File should exist filepath = tmp_path / "test_export.py" assert filepath.exists() # File should contain code content = filepath.read_text() assert "x = 2 + 2" in content finally: os.chdir(old_cwd) def test_export_rejects_absolute_paths(self, shell): """Export rejects absolute paths for security.""" shell._execute("x = 2 + 2") with pytest.raises(ValueError, match="Absolute paths not allowed"): shell.export_history(filename="/tmp/evil.py") def test_export_adds_py_extension(self, shell, tmp_path): """Export adds .py extension if not present.""" shell._execute("x = 2 + 2") old_cwd = Path.cwd() os.chdir(tmp_path) try: shell.export_history(filename="test_export") # Should create test_export.py filepath = tmp_path / "test_export.py" assert filepath.exists() finally: os.chdir(old_cwd) def test_export_excludes_stdout(self, shell): """Export does not include stdout output.""" shell._execute('print("Hello, World!")') script = shell.export_history() assert "# Hello, World!" not in script assert 'print("Hello, World!")' in script def test_export_multiple_steps(self, shell): """Export handles multiple execution steps.""" # Execute multiple times for i in range(3): shell._execute(f"x{i} = {i} + {i}") script = shell.export_history() # Should have all three steps assert "# Step 1" in script assert "# Step 2" in script assert "# Step 3" in script assert "x0 = 0 + 0" in script assert "x1 = 1 + 1" in script assert "x2 = 2 + 2" in script def test_export_to_file_with_long_output(self, shell, tmp_path): """Export truncates preview for files with more than 20 lines.""" shell._execute("x = 2 + 2") # Execute enough times to create > 20 lines (header + steps) for i in range(10): shell._execute(f"x{i} = {i}") old_cwd = Path.cwd() os.chdir(tmp_path) try: result = shell.export_history(filename="test_long") # Should mention truncation assert "more lines" in result finally: os.chdir(old_cwd) def test_export_with_invalid_syntax_in_history(self, shell): """Export handles code with syntax errors gracefully.""" from mcp_django.shell.core import StatementResult # Manually add a result with code that can't be parsed # (This simulates a defensive case that shouldn't normally happen) invalid_result = StatementResult( code="if x == 1:", # Missing body, invalid syntax stdout="", stderr="", ) shell.history.append(invalid_result) # Should not crash, just include the code as-is script = shell.export_history() assert "if x == 1:" in script class TestClearHistory: def test_clear_history_clears_entries(self, shell): """Clear history removes all entries.""" shell._execute("x = 2 + 2") shell._execute("x = 2 + 2") assert len(shell.history) == 2 shell.clear_history() assert len(shell.history) == 0 def test_clear_history_allows_fresh_export(self, shell): """Clear history allows clean export after messy exploration.""" # Messy exploration shell._execute("1 / 0") shell._execute("1 / 0") # Clear shell.clear_history() # Clean solution shell._execute("x = 2 + 2") # Export should only have clean solution script = shell.export_history() assert "1 / 0" not in script assert "x = 2 + 2" in script

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/joshuadavidthomas/mcp-django-shell'

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