Skip to content

Commit 6eed693

Browse files
committed
Add tests, make README.MD similar to other projects, remove safety_check
1 parent 3e3717e commit 6eed693

22 files changed

+3920
-22
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,12 @@ jobs:
6666
run: |
6767
uv python install 3.12
6868
uv sync --all-extras --dev
69-
uv add --dev bandit safety
69+
uv add --dev bandit
7070
7171
- name: ⚙️ Run security scan with bandit
7272
run: |
7373
uv run bandit -r src/ -f json -o bandit-report.json || true
7474
uv run bandit -r src/
75-
uv run safety check --output json > safety-report.json || true
76-
uv run safety check
7775
7876
- name: ⚙️ Upload security reports
7977
uses: actions/upload-artifact@v4
@@ -82,7 +80,6 @@ jobs:
8280
name: security-reports
8381
path: |
8482
bandit-report.json
85-
safety-report.json
8683
retention-days: 30
8784

8885

.github/workflows/release.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,11 @@ jobs:
5555
run: |
5656
uv python install 3.12
5757
uv sync --all-extras --dev
58-
uv add --dev bandit safety
58+
uv add --dev bandit
5959
6060
- name: ⚙️ Run security scan with bandit
6161
run: |
6262
uv run bandit -r src/
63-
uv run safety check
6463
6564
test:
6665
runs-on: ubuntu-latest

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
# Redis MCP Server
2-
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
2+
[![Integration](https://github.com/redis/mcp-redis/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/redis/lettuce/actions/workflows/integration.yml)
33
[![Python Version](https://img.shields.io/badge/python-3.13%2B-blue)](https://www.python.org/downloads/)
4+
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE.txt)
45
[![smithery badge](https://smithery.ai/badge/@redis/mcp-redis)](https://smithery.ai/server/@redis/mcp-redis)
56
[![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/70102150-efe0-4705-9f7d-87980109a279)
7+
[![codecov](https://codecov.io/gh/redis/mcp-redis/branch/master/graph/badge.svg?token=yenl5fzxxr)](https://codecov.io/gh/redis/mcp-redis)
8+
9+
10+
[![Discord](https://img.shields.io/discord/697882427875393627.svg?style=social&logo=discord)](https://discord.gg/redis)
11+
[![Twitch](https://img.shields.io/twitch/status/redisinc?style=social)](https://www.twitch.tv/redisinc)
12+
[![YouTube](https://img.shields.io/youtube/channel/views/UCD78lHSwYqMlyetR0_P4Vig?style=social)](https://www.youtube.com/redisinc)
13+
[![Twitter](https://img.shields.io/twitter/follow/redisinc?style=social)](https://twitter.com/redisinc)
14+
[![Stack Exchange questions](https://img.shields.io/stackexchange/stackoverflow/t/mcp-redis?style=social&logo=stackoverflow&label=Stackoverflow)](https://stackoverflow.com/questions/tagged/mcp-redis)
615

716
## Overview
817
The Redis MCP Server is a **natural language interface** designed for agentic applications to efficiently manage and search data in Redis. It integrates seamlessly with **MCP (Model Content Protocol) clients**, enabling AI-driven workflows to interact with structured and unstructured data in Redis. Using this MCP Server, you can ask questions like:

pyproject.toml

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,7 @@ skips = ["B101", "B601"] # Skip assert_used and shell_injection_process_args if
5252
[tool.bandit.assert_used]
5353
skips = ["*_test.py", "*/test_*.py"]
5454

55-
# Test configuration
56-
[tool.pytest.ini_options]
57-
testpaths = ["tests"]
58-
python_files = ["test_*.py"]
59-
python_classes = ["Test*"]
60-
python_functions = ["test_*"]
61-
addopts = [
62-
"--strict-markers",
63-
"--strict-config",
64-
"--verbose",
65-
]
66-
markers = [
67-
"slow: marks tests as slow",
68-
"integration: marks tests as integration tests",
69-
]
55+
7056

7157
[dependency-groups]
7258
dev = [
@@ -78,7 +64,69 @@ dev = [
7864
"pytest>=8.4.1",
7965
"pytest-asyncio>=1.1.0",
8066
"pytest-cov>=6.2.1",
67+
"pytest-mock>=3.12.0",
8168
"ruff>=0.12.5",
8269
"safety>=3.6.0",
8370
"twine>=4.0",
8471
]
72+
73+
test = [
74+
"pytest>=8.4.1",
75+
"pytest-asyncio>=1.1.0",
76+
"pytest-cov>=6.2.1",
77+
"pytest-mock>=3.12.0",
78+
"coverage>=7.10.1",
79+
]
80+
81+
# Testing configuration
82+
[tool.pytest.ini_options]
83+
testpaths = ["tests"]
84+
python_files = ["test_*.py"]
85+
python_classes = ["Test*"]
86+
python_functions = ["test_*"]
87+
addopts = [
88+
"--strict-markers",
89+
"--strict-config",
90+
"--verbose",
91+
"--cov=src",
92+
"--cov-report=html",
93+
"--cov-report=term",
94+
"--cov-report=xml",
95+
"--cov-fail-under=80",
96+
]
97+
markers = [
98+
"unit: marks tests as unit tests",
99+
"integration: marks tests as integration tests",
100+
"slow: marks tests as slow running",
101+
]
102+
asyncio_mode = "auto"
103+
filterwarnings = [
104+
"ignore::DeprecationWarning",
105+
"ignore::PendingDeprecationWarning",
106+
]
107+
108+
[tool.coverage.run]
109+
source = ["src"]
110+
omit = [
111+
"*/tests/*",
112+
"*/test_*.py",
113+
"*/__pycache__/*",
114+
"*/venv/*",
115+
"*/.venv/*",
116+
]
117+
118+
[tool.coverage.report]
119+
exclude_lines = [
120+
"pragma: no cover",
121+
"def __repr__",
122+
"if self.debug:",
123+
"if settings.DEBUG",
124+
"raise AssertionError",
125+
"raise NotImplementedError",
126+
"if 0:",
127+
"if __name__ == .__main__.:",
128+
"class .*\\bProtocol\\):",
129+
"@(abc\\.)?abstractmethod",
130+
]
131+
132+

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Tests package for Redis MCP Server

tests/conftest.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""
2+
Pytest configuration and fixtures for Redis MCP Server tests.
3+
"""
4+
5+
import pytest
6+
from unittest.mock import Mock, patch
7+
import redis
8+
from redis.exceptions import RedisError, ConnectionError, TimeoutError
9+
10+
11+
@pytest.fixture
12+
def mock_redis():
13+
"""Create a mock Redis connection."""
14+
mock = Mock(spec=redis.Redis)
15+
return mock
16+
17+
18+
@pytest.fixture
19+
def mock_redis_cluster():
20+
"""Create a mock Redis Cluster connection."""
21+
mock = Mock(spec=redis.cluster.RedisCluster)
22+
return mock
23+
24+
25+
@pytest.fixture
26+
def mock_redis_connection_manager():
27+
"""Mock the RedisConnectionManager to return a mock Redis connection."""
28+
with patch('src.common.connection.RedisConnectionManager.get_connection') as mock_get_conn:
29+
mock_redis = Mock(spec=redis.Redis)
30+
mock_get_conn.return_value = mock_redis
31+
yield mock_redis
32+
33+
34+
@pytest.fixture
35+
def redis_config():
36+
"""Sample Redis configuration for testing."""
37+
return {
38+
"host": "localhost",
39+
"port": 6379,
40+
"db": 0,
41+
"username": None,
42+
"password": "",
43+
"ssl": False,
44+
"ssl_ca_path": None,
45+
"ssl_keyfile": None,
46+
"ssl_certfile": None,
47+
"ssl_cert_reqs": "required",
48+
"ssl_ca_certs": None,
49+
"cluster_mode": False,
50+
}
51+
52+
53+
@pytest.fixture
54+
def redis_uri_samples():
55+
"""Sample Redis URIs for testing."""
56+
return {
57+
"basic": "redis://localhost:6379/0",
58+
"with_auth": "redis://user:pass@localhost:6379/0",
59+
"ssl": "rediss://user:pass@localhost:6379/0",
60+
"with_query": "redis://localhost:6379/0?ssl_cert_reqs=required",
61+
"cluster": "redis://localhost:6379/0?cluster_mode=true",
62+
}
63+
64+
65+
@pytest.fixture
66+
def sample_vector():
67+
"""Sample vector for testing vector operations."""
68+
return [0.1, 0.2, 0.3, 0.4, 0.5]
69+
70+
71+
@pytest.fixture
72+
def sample_json_data():
73+
"""Sample JSON data for testing."""
74+
return {
75+
"name": "John Doe",
76+
"age": 30,
77+
"city": "New York",
78+
"hobbies": ["reading", "swimming"]
79+
}
80+
81+
82+
@pytest.fixture
83+
def redis_error_scenarios():
84+
"""Common Redis error scenarios for testing."""
85+
return {
86+
"connection_error": ConnectionError("Connection refused"),
87+
"timeout_error": TimeoutError("Operation timed out"),
88+
"generic_error": RedisError("Generic Redis error"),
89+
"auth_error": RedisError("NOAUTH Authentication required"),
90+
"wrong_type": RedisError("WRONGTYPE Operation against a key holding the wrong kind of value"),
91+
}
92+
93+
94+
@pytest.fixture(autouse=True)
95+
def reset_connection_manager():
96+
"""Reset the RedisConnectionManager singleton before each test."""
97+
from src.common.connection import RedisConnectionManager
98+
RedisConnectionManager._instance = None
99+
yield
100+
RedisConnectionManager._instance = None
101+
102+
103+
@pytest.fixture
104+
def mock_numpy_array():
105+
"""Mock numpy array for vector testing."""
106+
with patch('numpy.array') as mock_array:
107+
mock_array.return_value.tobytes.return_value = b'mock_binary_data'
108+
yield mock_array
109+
110+
111+
@pytest.fixture
112+
def mock_numpy_frombuffer():
113+
"""Mock numpy frombuffer for vector testing."""
114+
with patch('numpy.frombuffer') as mock_frombuffer:
115+
mock_frombuffer.return_value.tolist.return_value = [0.1, 0.2, 0.3]
116+
yield mock_frombuffer
117+
118+
119+
# Async test helpers
120+
@pytest.fixture
121+
def event_loop():
122+
"""Create an event loop for async tests."""
123+
import asyncio
124+
loop = asyncio.new_event_loop()
125+
yield loop
126+
loop.close()
127+
128+
129+
# Mark configurations
130+
def pytest_configure(config):
131+
"""Configure pytest markers."""
132+
config.addinivalue_line("markers", "unit: mark test as a unit test")
133+
config.addinivalue_line("markers", "integration: mark test as an integration test")
134+
config.addinivalue_line("markers", "slow: mark test as slow running")

0 commit comments

Comments
 (0)