Skip to content

Commit 9d4c04d

Browse files
ashwin-antclaude
andcommitted
feat: Add CI workflow for Claude Code SDK integration testing
- Add GitHub Actions workflow that runs on PR open/synchronize - Install Claude Code CLI via official install script - Run quickstart example and dedicated e2e tests - Test across Python 3.10-3.13 - Add basic integration tests and file operation tests 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent ff4fe89 commit 9d4c04d

File tree

3 files changed

+226
-0
lines changed

3 files changed

+226
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Claude Code Integration Test
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize]
6+
7+
jobs:
8+
integration-test:
9+
runs-on: ubuntu-latest
10+
strategy:
11+
matrix:
12+
python-version: ["3.10", "3.11", "3.12", "3.13"]
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Set up Python ${{ matrix.python-version }}
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: ${{ matrix.python-version }}
21+
22+
- name: Install Claude Code
23+
run: |
24+
curl -fsSL https://claude.ai/install.sh | bash
25+
echo "$HOME/.local/bin" >> $GITHUB_PATH
26+
27+
- name: Verify Claude Code installation
28+
run: |
29+
export PATH="$HOME/.local/bin:$PATH"
30+
claude -v
31+
32+
- name: Install Python dependencies
33+
run: |
34+
python -m pip install --upgrade pip
35+
pip install -e .
36+
37+
- name: Run quickstart example
38+
run: |
39+
export PATH="$HOME/.local/bin:$PATH"
40+
cd examples
41+
python quick_start.py
42+
env:
43+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
44+
45+
- name: Run basic integration tests
46+
run: |
47+
export PATH="$HOME/.local/bin:$PATH"
48+
python e2e-tests/test_basic_integration.py
49+
env:
50+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
51+
52+
- name: Run file operation tests
53+
run: |
54+
export PATH="$HOME/.local/bin:$PATH"
55+
python e2e-tests/test_file_operations.py
56+
env:
57+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/usr/bin/env python3
2+
"""Basic integration test for Claude Code SDK."""
3+
4+
import anyio
5+
6+
from claude_code_sdk import AssistantMessage, TextBlock, query
7+
8+
9+
async def test_basic_query():
10+
"""Test basic query functionality."""
11+
print("Testing basic query...")
12+
found_response = False
13+
14+
async for message in query(prompt="What is 2 + 2?"):
15+
if isinstance(message, AssistantMessage):
16+
for block in message.content:
17+
if isinstance(block, TextBlock):
18+
print(f"Response: {block.text}")
19+
found_response = True
20+
21+
if not found_response:
22+
raise Exception("No response received from Claude")
23+
24+
print("✅ Basic query test passed")
25+
26+
27+
async def test_with_system_prompt():
28+
"""Test query with custom system prompt."""
29+
print("\nTesting with system prompt...")
30+
found_response = False
31+
32+
from claude_code_sdk import ClaudeCodeOptions
33+
34+
options = ClaudeCodeOptions(
35+
system_prompt="You are a helpful assistant that answers concisely.",
36+
max_turns=1,
37+
)
38+
39+
async for message in query(
40+
prompt="What is Python in one sentence?", options=options
41+
):
42+
if isinstance(message, AssistantMessage):
43+
for block in message.content:
44+
if isinstance(block, TextBlock):
45+
print(f"Response: {block.text}")
46+
found_response = True
47+
48+
if not found_response:
49+
raise Exception("No response received from Claude")
50+
51+
print("✅ System prompt test passed")
52+
53+
54+
async def main():
55+
"""Run all integration tests."""
56+
print("=" * 50)
57+
print("Running Claude Code SDK Integration Tests")
58+
print("=" * 50)
59+
60+
try:
61+
await test_basic_query()
62+
await test_with_system_prompt()
63+
64+
print("\n" + "=" * 50)
65+
print("✅ All integration tests passed!")
66+
print("=" * 50)
67+
except Exception as e:
68+
print(f"\n❌ Integration test failed: {e}")
69+
raise
70+
71+
72+
if __name__ == "__main__":
73+
anyio.run(main())

e2e-tests/test_file_operations.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/env python3
2+
"""Test file operations with Claude Code SDK."""
3+
4+
import os
5+
import tempfile
6+
import anyio
7+
from pathlib import Path
8+
9+
from claude_code_sdk import AssistantMessage, ClaudeCodeOptions, TextBlock, query
10+
11+
12+
async def test_file_creation():
13+
"""Test file creation through SDK."""
14+
print("Testing file creation...")
15+
16+
with tempfile.TemporaryDirectory() as tmpdir:
17+
test_file = Path(tmpdir) / "test_output.txt"
18+
19+
options = ClaudeCodeOptions(
20+
allowed_tools=["Write"],
21+
system_prompt="You are a helpful file assistant. Be concise.",
22+
max_turns=1,
23+
)
24+
25+
prompt = f"Create a file at {test_file} with content 'Hello from Claude Code SDK!'"
26+
27+
async for message in query(prompt=prompt, options=options):
28+
if isinstance(message, AssistantMessage):
29+
for block in message.content:
30+
if isinstance(block, TextBlock):
31+
print(f"Claude: {block.text[:100]}...") # Print first 100 chars
32+
33+
# Verify file was created
34+
if not test_file.exists():
35+
raise Exception(f"File {test_file} was not created")
36+
37+
content = test_file.read_text()
38+
if "Hello from Claude Code SDK!" not in content:
39+
raise Exception(f"File content incorrect: {content}")
40+
41+
print(f"✅ File created successfully with correct content")
42+
43+
44+
async def test_file_reading():
45+
"""Test file reading through SDK."""
46+
print("\nTesting file reading...")
47+
48+
with tempfile.TemporaryDirectory() as tmpdir:
49+
test_file = Path(tmpdir) / "input.txt"
50+
test_content = "This is a test file for Claude Code SDK e2e testing."
51+
test_file.write_text(test_content)
52+
53+
options = ClaudeCodeOptions(
54+
allowed_tools=["Read"],
55+
system_prompt="You are a helpful file assistant. Be concise.",
56+
max_turns=1,
57+
)
58+
59+
prompt = f"Read the file at {test_file} and tell me the first word in the file"
60+
61+
found_response = False
62+
async for message in query(prompt=prompt, options=options):
63+
if isinstance(message, AssistantMessage):
64+
for block in message.content:
65+
if isinstance(block, TextBlock):
66+
print(f"Claude: {block.text[:100]}...")
67+
# Check if Claude identified "This" as the first word
68+
if "This" in block.text or "this" in block.text.lower():
69+
found_response = True
70+
71+
if not found_response:
72+
raise Exception("Claude did not identify the first word correctly")
73+
74+
print("✅ File reading test passed")
75+
76+
77+
async def main():
78+
"""Run file operation tests."""
79+
print("=" * 50)
80+
print("Running File Operation Tests")
81+
print("=" * 50)
82+
83+
try:
84+
await test_file_creation()
85+
await test_file_reading()
86+
87+
print("\n" + "=" * 50)
88+
print("✅ All file operation tests passed!")
89+
print("=" * 50)
90+
except Exception as e:
91+
print(f"\n❌ File operation test failed: {e}")
92+
raise
93+
94+
95+
if __name__ == "__main__":
96+
anyio.run(main())

0 commit comments

Comments
 (0)