From b748453f3a590ebdf7cbc1e43248fa9f486dbb0a Mon Sep 17 00:00:00 2001 From: "cmeans-claude-dev[bot]" <3223881+cmeans-claude-dev[bot]@users.noreply.github.com> Date: Sun, 12 Apr 2026 12:57:05 -0500 Subject: [PATCH] feat: add opt-in integration test suite for real clipboard tools Integration tests exercise wl-paste/xclip/pbpaste/PowerShell against a real clipboard daemon. Skipped by default via pytest marker config; run with `uv run pytest -m integration`. Tests cover: text round-trip, unicode, multiline, special characters, format listing, and unavailable MIME type handling. Closes #23 Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 9 +++++ CLAUDE.md | 5 ++- CONTRIBUTING.md | 5 ++- pyproject.toml | 2 + tests/test_integration.py | 77 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 tests/test_integration.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 591b30a..77d950e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented here. +## [Unreleased] + +### Added +- Opt-in integration test suite (`tests/test_integration.py`) that + exercises real clipboard tools. Skipped by default; run with + `uv run pytest -m integration`. Covers text round-trip, unicode, + multiline, special characters, format listing, and unavailable + MIME types. Closes #23. + ## [2.1.1] - 2026-04-12 ### Fixed diff --git a/CLAUDE.md b/CLAUDE.md index 67a3521..07e6bd5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,7 @@ mcp-clipboard is an MCP (Model Context Protocol) server that reads and writes th # Install dependencies uv sync -# Run all tests +# Run all tests (mocked, no clipboard needed) uv run pytest # Run a single test file @@ -21,6 +21,9 @@ uv run pytest tests/test_parser.py # Run a single test function uv run pytest tests/test_parser.py::test_parse_google_sheets_html +# Run integration tests (requires real clipboard daemon) +uv run pytest -m integration + # Run the MCP server (stdio mode) uv run mcp-clipboard diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01cb093..33fddc7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,11 +60,14 @@ For anything bigger than a one-line fix: ```bash uv sync --extra dev # install runtime + dev dependencies -uv run pytest # run the full test suite +uv run pytest # run the full test suite (mocked, no clipboard needed) uv run pytest tests/test_parser.py # single test file uv run pytest -k "test_format_html" # single test by name +uv run pytest -m integration # integration tests (real clipboard) ``` +**Integration tests** (`tests/test_integration.py`) exercise real clipboard tools (`wl-paste`, `xclip`, `pbpaste`, etc.) and are skipped by default. Run them with `-m integration` if you have a clipboard daemon available. These are especially useful for platform testers (#5, #10). + Requires **Python 3.11+**. See the [`README → Development`](README.md#development) section for additional commands (build, debug logging, MCP Inspector). ## PR requirements diff --git a/pyproject.toml b/pyproject.toml index 4117acb..0b6b739 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,8 @@ artifacts = ["src/mcp_clipboard/instructions/*.md", "src/mcp_clipboard/icons/*.s pythonpath = ["src"] testpaths = ["tests"] asyncio_mode = "auto" +markers = ["integration: requires real clipboard tools (skipped by default)"] +addopts = "-m 'not integration'" [tool.ruff] target-version = "py311" diff --git a/tests/test_integration.py b/tests/test_integration.py new file mode 100644 index 0000000..8688c6a --- /dev/null +++ b/tests/test_integration.py @@ -0,0 +1,77 @@ +"""Integration tests that exercise real clipboard tools. + +Skipped by default. Run with: + + uv run pytest -m integration + +Requires a running clipboard daemon (Wayland compositor, X11 server, +or macOS/Windows desktop session). These tests write to and read from +the actual system clipboard. +""" + +from __future__ import annotations + +import pytest + +from mcp_clipboard.clipboard import ( + list_clipboard_formats, + read_clipboard, + write_clipboard, +) + +pytestmark = pytest.mark.integration + + +@pytest.mark.asyncio +async def test_round_trip_plain_text(): + """Write plain text and read it back.""" + test_value = "mcp-clipboard integration test" + await write_clipboard(test_value) + result = await read_clipboard("text/plain") + assert result.strip() == test_value + + +@pytest.mark.asyncio +async def test_round_trip_unicode(): + """Write unicode text and read it back.""" + test_value = "Hello \U0001f30d \u4f60\u597d" + await write_clipboard(test_value) + result = await read_clipboard("text/plain") + assert result.strip() == test_value + + +@pytest.mark.asyncio +async def test_round_trip_multiline(): + """Write multiline text and read it back.""" + test_value = "line one\nline two\nline three" + await write_clipboard(test_value) + result = await read_clipboard("text/plain") + assert result.strip() == test_value + + +@pytest.mark.asyncio +async def test_round_trip_special_chars(): + """Write text with special characters and read it back.""" + test_value = 'pipes | and "quotes" and & ampersands' + await write_clipboard(test_value) + result = await read_clipboard("text/plain") + assert result.strip() == test_value + + +@pytest.mark.asyncio +async def test_list_formats_includes_text(): + """After writing text, list_clipboard_formats should include a text type.""" + await write_clipboard("format check") + formats = await list_clipboard_formats() + assert isinstance(formats, list) + assert len(formats) > 0 + # At least one text format should be present + text_formats = [f for f in formats if "text" in f.lower() or "string" in f.lower()] + assert text_formats, f"No text format found in: {formats}" + + +@pytest.mark.asyncio +async def test_read_empty_mime_returns_empty(): + """Reading an unavailable MIME type should return an empty string.""" + result = await read_clipboard("application/x-nonexistent-format") + assert result == ""