Skip to content

Commit 7a6f2c2

Browse files
committed
feat: add end-to-end tests for Quip API integration
1 parent 11d5974 commit 7a6f2c2

File tree

6 files changed

+184
-2
lines changed

6 files changed

+184
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ wheels/
2222
.coverage
2323

2424
# Environment
25+
!.env.example
2526
.env
27+
.env.*
2628
.venv
2729
env/
2830
venv/

README.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,11 @@ quip-mcp-server/
128128
│ └── tools.py # Tool definitions and handlers
129129
├── tests/
130130
│ ├── __init__.py
131-
│ └── test_server.py # Unit tests for the server
131+
│ ├── test_server.py # Unit tests for the server
132+
│ └── e2e/ # End-to-end tests
133+
│ ├── __init__.py
134+
│ ├── conftest.py # Test fixtures for e2e tests
135+
│ └── test_quip_integration.py # Integration tests with Quip API
132136
├── .uv/
133137
│ └── config.toml # uv configuration settings
134138
├── pyproject.toml # Project metadata and dependencies (includes pytest config)
@@ -191,7 +195,6 @@ python -m src.server
191195
#### Running Tests
192196

193197
The project uses pytest for testing. To run the tests:
194-
195198
```bash
196199
# Install development dependencies
197200
uv pip install -e ".[dev]"
@@ -201,6 +204,38 @@ pytest
201204

202205
# Run tests with coverage
203206
pytest --cov=src
207+
208+
# Run only e2e tests
209+
pytest tests/e2e
210+
211+
# Run a specific e2e test
212+
pytest tests/e2e/test_quip_integration.py::test_connection
213+
```
214+
215+
### End-to-End (e2e) Testing
216+
217+
The project includes end-to-end tests that verify integration with the actual Quip API. To run these tests:
218+
219+
1. Create a `.env.local` file in the project root with your test configuration:
220+
```
221+
# Quip API token (required)
222+
QUIP_TOKEN=your_actual_quip_token_here
223+
224+
# Test configuration
225+
TEST_THREAD_ID=your_test_spreadsheet_thread_id
226+
TEST_SHEET_NAME=Sheet1 # Optional: specific sheet name to test
227+
```
228+
229+
2. Run the e2e tests:
230+
```bash
231+
# Run all e2e tests
232+
pytest tests/e2e
233+
234+
# Run with verbose output
235+
pytest -v tests/e2e
236+
```
237+
238+
Note: The e2e tests will be skipped automatically if `.env.local` is missing or if required environment variables are not set.
204239
```
205240
206241
#### Debugging

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ python_files = "test_*.py"
4949
python_classes = "Test*"
5050
python_functions = "test_*"
5151
addopts = "-v"
52+
markers = [
53+
"e2e: marks tests as end-to-end tests that require external resources",
54+
]
5255

5356
[tool.coverage.run]
5457
source = ["src"]

tests/e2e/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Mark tests/e2e as a Python package

tests/e2e/conftest.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import os
2+
import pytest
3+
from dotenv import load_dotenv
4+
from src.quip_client import QuipClient
5+
@pytest.fixture(scope="session", autouse=True)
6+
def load_env():
7+
"""
8+
Load test environment variables
9+
10+
Priority:
11+
1. System environment variables
12+
2. Variables from .env.local file (if exists)
13+
14+
This supports both local development and CI/CD environments
15+
"""
16+
# Try to load .env.local file if it exists
17+
local_env = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), '.env.local')
18+
if os.path.exists(local_env):
19+
load_dotenv(local_env)
20+
print(f"Loaded environment variables from {local_env}")
21+
22+
# Check required environment variables (either from .env.local or system)
23+
if not os.environ.get("QUIP_TOKEN"):
24+
pytest.skip("QUIP_TOKEN environment variable is not set, skipping e2e tests")
25+
if not os.environ.get("TEST_THREAD_ID"):
26+
pytest.skip("TEST_THREAD_ID environment variable is not set, skipping e2e tests")
27+
pytest.skip("TEST_THREAD_ID环境变量未设置,跳过e2e测试")
28+
29+
@pytest.fixture
30+
def quip_client():
31+
"""Create a QuipClient instance"""
32+
token = os.environ.get("QUIP_TOKEN")
33+
base_url = os.environ.get("QUIP_BASE_URL", "https://platform.quip.com")
34+
return QuipClient(access_token=token, base_url=base_url)
35+
36+
@pytest.fixture
37+
def test_thread_id():
38+
"""Get the test thread ID"""
39+
return os.environ.get("TEST_THREAD_ID")
40+
41+
@pytest.fixture
42+
def test_sheet_name():
43+
"""Get the test sheet name"""
44+
return os.environ.get("TEST_SHEET_NAME")

tests/e2e/test_quip_integration.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import os
2+
import tempfile
3+
import pytest
4+
import csv
5+
import io
6+
from src.quip_client import convert_xlsx_to_csv
7+
8+
@pytest.mark.e2e
9+
def test_connection(quip_client, test_thread_id):
10+
"""Test connection to Quip API"""
11+
thread = quip_client.get_thread(test_thread_id)
12+
assert thread is not None
13+
assert "thread" in thread
14+
print(thread["thread"])
15+
assert thread["thread"]["id"] == test_thread_id or \
16+
("secret_path" in thread["thread"] and thread["thread"]["secret_path"] == test_thread_id) or \
17+
test_thread_id in thread["thread"]["link"]
18+
19+
@pytest.mark.e2e
20+
def test_is_spreadsheet(quip_client, test_thread_id):
21+
"""Test if the thread is correctly identified as a spreadsheet"""
22+
is_spreadsheet = quip_client.is_spreadsheet(test_thread_id)
23+
assert is_spreadsheet is True
24+
25+
@pytest.mark.e2e
26+
def test_export_to_xlsx(quip_client, test_thread_id):
27+
"""Test exporting to XLSX format"""
28+
with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as temp_file:
29+
xlsx_path = temp_file.name
30+
31+
try:
32+
# Export to XLSX
33+
quip_client.export_thread_to_xlsx(test_thread_id, xlsx_path)
34+
assert os.path.exists(xlsx_path)
35+
assert os.path.getsize(xlsx_path) > 0
36+
finally:
37+
# Clean up temporary file
38+
if os.path.exists(xlsx_path):
39+
os.remove(xlsx_path)
40+
41+
@pytest.mark.e2e
42+
def test_convert_xlsx_to_csv(quip_client, test_thread_id, test_sheet_name):
43+
"""Test converting XLSX to CSV and validate content"""
44+
with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as temp_file:
45+
xlsx_path = temp_file.name
46+
47+
try:
48+
# Export to XLSX
49+
quip_client.export_thread_to_xlsx(test_thread_id, xlsx_path)
50+
51+
# Convert to CSV
52+
csv_data = convert_xlsx_to_csv(xlsx_path, test_sheet_name)
53+
54+
# Validate CSV data
55+
assert csv_data is not None
56+
assert len(csv_data) > 0
57+
58+
# Parse CSV data for more detailed validation
59+
csv_reader = csv.reader(io.StringIO(csv_data))
60+
rows = list(csv_reader)
61+
assert len(rows) > 0 # At least one row of data
62+
63+
# You can add more specific validations for your data
64+
finally:
65+
# Clean up temporary file
66+
if os.path.exists(xlsx_path):
67+
os.remove(xlsx_path)
68+
69+
@pytest.mark.e2e
70+
def test_export_to_csv_fallback(quip_client, test_thread_id, test_sheet_name):
71+
"""Test exporting to CSV format using fallback method"""
72+
csv_data = quip_client.export_thread_to_csv_fallback(test_thread_id, test_sheet_name)
73+
assert csv_data is not None
74+
assert len(csv_data) > 0
75+
76+
# Parse CSV data for more detailed validation
77+
csv_reader = csv.reader(io.StringIO(csv_data))
78+
rows = list(csv_reader)
79+
assert len(rows) > 0 # At least one row of data
80+
81+
@pytest.mark.e2e
82+
def test_error_handling_invalid_thread(quip_client):
83+
"""Test handling of invalid threadId"""
84+
invalid_thread_id = "invalid_thread_id_123456"
85+
86+
# Test is_spreadsheet method
87+
is_spreadsheet = quip_client.is_spreadsheet(invalid_thread_id)
88+
assert is_spreadsheet is False
89+
90+
# Test export_thread_to_xlsx method
91+
with tempfile.NamedTemporaryFile(suffix=".xlsx") as temp_file:
92+
with pytest.raises(Exception):
93+
quip_client.export_thread_to_xlsx(invalid_thread_id, temp_file.name)
94+
95+
# Test export_thread_to_csv_fallback method
96+
with pytest.raises(Exception):
97+
quip_client.export_thread_to_csv_fallback(invalid_thread_id)

0 commit comments

Comments
 (0)