Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions python/tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""Tests for the Redis Cloud Python client."""

import json
import os
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer

import pytest
from redis_cloud import CloudClient, RedisCloudError

Expand Down Expand Up @@ -252,3 +256,130 @@ def test_version_exported(self):

assert hasattr(redis_cloud, "__version__")
assert isinstance(redis_cloud.__version__, str)


class _JsonMockHandler(BaseHTTPRequestHandler):
"""Dispatch GET requests to pre-registered JSON routes."""

routes: dict = {}

def do_GET(self):
body = self.routes.get(self.path)
if body is None:
self.send_response(404)
self.end_headers()
return
data = json.dumps(body).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)

def log_message(self, *_args):
pass # silence


@pytest.fixture(scope="class")
def mock_server():
# NOTE: list endpoints (/fixed/subscriptions, .../databases) deserialize
# into wrapper *objects* on the Rust side, not bare arrays, so their
# fixtures are `{}` rather than `[]`.
routes = {
"/tasks": {"tasks": []},
"/tasks/task-1": {"taskId": "task-1", "status": "processing-completed"},
"/users": {"account": 1},
"/users/1": {"id": 1, "name": "Test User", "email": "t@example.com"},
"/acl/redisRules": {},
"/acl/roles": {},
"/acl/users": {},
"/acl/users/1": {"id": 1},
"/cloud-accounts": {"accountId": 1},
"/cloud-accounts/1": {
"id": 1,
"name": "test",
"accessKeyId": "AKID",
"status": "active",
"provider": "AWS",
},
"/fixed/subscriptions": {},
"/fixed/subscriptions/1": {"id": 1, "name": "test-fixed"},
"/fixed/subscriptions/1/databases": {},
"/fixed/subscriptions/1/databases/1": {"id": 1, "name": "test-db"},
}

class _Handler(_JsonMockHandler):
pass

_Handler.routes = routes

server = HTTPServer(("127.0.0.1", 0), _Handler)
port = server.server_address[1]
t = threading.Thread(target=server.serve_forever, daemon=True)
t.start()
yield f"http://127.0.0.1:{port}"
server.shutdown()


class TestDomainCallsSync:
"""Smoke: each new domain binding makes a real HTTP call through the Rust layer."""

@pytest.fixture
def client(self, mock_server):
return CloudClient(
api_key="test-key",
api_secret="test-secret",
base_url=mock_server,
)

def test_tasks_list_sync(self, client):
result = client.tasks_sync()
assert isinstance(result, list)

def test_task_get_sync(self, client):
result = client.task_sync("task-1")
assert result is not None

def test_users_list_sync(self, client):
result = client.users_sync()
assert result is not None

def test_user_get_sync(self, client):
result = client.user_sync(1)
assert result is not None

def test_acl_redis_rules_sync(self, client):
result = client.acl_redis_rules_sync()
assert result is not None

def test_acl_roles_sync(self, client):
result = client.acl_roles_sync()
assert result is not None

def test_acl_users_sync(self, client):
result = client.acl_users_sync()
assert result is not None

def test_cloud_accounts_sync(self, client):
result = client.cloud_accounts_sync()
assert result is not None

def test_cloud_account_get_sync(self, client):
result = client.cloud_account_sync(1)
assert result is not None

def test_fixed_subscriptions_sync(self, client):
result = client.fixed_subscriptions_sync()
assert result is not None

def test_fixed_subscription_get_sync(self, client):
result = client.fixed_subscription_sync(1)
assert result is not None

def test_fixed_databases_sync(self, client):
result = client.fixed_databases_sync(1)
assert result is not None

def test_fixed_database_get_sync(self, client):
result = client.fixed_database_sync(1, 1)
assert result is not None
86 changes: 86 additions & 0 deletions tests/acl_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -854,3 +854,89 @@ async fn test_create_user_with_full_response() {
assert_eq!(response.resource_id, Some(999));
assert_eq!(response.additional_resource_id, Some(888));
}

// Helper: build a Cloud client wired to the given mock server URI.
fn test_client(uri: String) -> CloudClient {
CloudClient::builder()
.api_key("test-key".to_string())
.api_secret("test-secret".to_string())
.base_url(uri)
.build()
.unwrap()
}

// Alias round-trip: `list_redis_rules()` delegates to `get_all_redis_rules()`.
#[tokio::test]
async fn test_acl_list_redis_rules_alias() {
let mock_server = MockServer::start().await;

Mock::given(method("GET"))
.and(path("/acl/redisRules"))
.and(header("x-api-key", "test-key"))
.and(header("x-api-secret-key", "test-secret"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.mount(&mock_server)
.await;

let handler = AclHandler::new(test_client(mock_server.uri()));
let result = handler.list_redis_rules().await.unwrap();

assert_eq!(result.account_id, None);
}

// Alias round-trip: `list_roles()` delegates to `get_all_roles()`.
#[tokio::test]
async fn test_acl_list_roles_alias() {
let mock_server = MockServer::start().await;

Mock::given(method("GET"))
.and(path("/acl/roles"))
.and(header("x-api-key", "test-key"))
.and(header("x-api-secret-key", "test-secret"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.mount(&mock_server)
.await;

let handler = AclHandler::new(test_client(mock_server.uri()));
let result = handler.list_roles().await.unwrap();

assert_eq!(result.account_id, None);
}

// Alias round-trip: `list_acl_users()` delegates to `get_all_acl_users()`.
#[tokio::test]
async fn test_acl_list_acl_users_alias() {
let mock_server = MockServer::start().await;

Mock::given(method("GET"))
.and(path("/acl/users"))
.and(header("x-api-key", "test-key"))
.and(header("x-api-secret-key", "test-secret"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.mount(&mock_server)
.await;

let handler = AclHandler::new(test_client(mock_server.uri()));
let result = handler.list_acl_users().await.unwrap();

assert_eq!(result.account_id, None);
}

// Alias round-trip: `get_acl_user(id)` delegates to `get_acl_user_by_id()`.
#[tokio::test]
async fn test_acl_get_acl_user_alias() {
let mock_server = MockServer::start().await;

Mock::given(method("GET"))
.and(path("/acl/users/5"))
.and(header("x-api-key", "test-key"))
.and(header("x-api-secret-key", "test-secret"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "id": 5 })))
.mount(&mock_server)
.await;

let handler = AclHandler::new(test_client(mock_server.uri()));
let result = handler.get_acl_user(5).await.unwrap();

assert_eq!(result.id, Some(5));
}
76 changes: 76 additions & 0 deletions tests/cloud_accounts_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,3 +793,79 @@ async fn test_error_handling_500() {
_ => panic!("Expected InternalServerError error"),
}
}

// Helper: build a Cloud client wired to the given mock server URI.
fn test_client(uri: String) -> CloudClient {
CloudClient::builder()
.api_key("test-key".to_string())
.api_secret("test-secret".to_string())
.base_url(uri)
.build()
.unwrap()
}

// Alias round-trip: `list()` delegates to `get_cloud_accounts()`.
#[tokio::test]
async fn test_cloud_accounts_list_alias() {
let mock_server = MockServer::start().await;

Mock::given(method("GET"))
.and(path("/cloud-accounts"))
.and(header("x-api-key", "test-key"))
.and(header("x-api-secret-key", "test-secret"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "accountId": 1 })))
.mount(&mock_server)
.await;

let handler = CloudAccountHandler::new(test_client(mock_server.uri()));
let result = handler.list().await.unwrap();

assert_eq!(result.account_id, Some(1));
}

// Alias round-trip: `get(id)` delegates to `get_cloud_account_by_id()`.
#[tokio::test]
async fn test_cloud_account_get_alias() {
let mock_server = MockServer::start().await;

Mock::given(method("GET"))
.and(path("/cloud-accounts/42"))
.and(header("x-api-key", "test-key"))
.and(header("x-api-secret-key", "test-secret"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": 42,
"name": "my-account",
"accessKeyId": "AKID",
"status": "active",
"provider": "AWS"
})))
.mount(&mock_server)
.await;

let handler = CloudAccountHandler::new(test_client(mock_server.uri()));
let result = handler.get(42).await.unwrap();

assert_eq!(result.id, Some(42));
}

// Alias round-trip: `delete(id)` delegates to `delete_cloud_account()`.
#[tokio::test]
async fn test_cloud_account_delete_alias() {
let mock_server = MockServer::start().await;

Mock::given(method("DELETE"))
.and(path("/cloud-accounts/43"))
.and(header("x-api-key", "test-key"))
.and(header("x-api-secret-key", "test-secret"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"taskId": "task-del-ca",
"status": "processing-completed"
})))
.mount(&mock_server)
.await;

let handler = CloudAccountHandler::new(test_client(mock_server.uri()));
let result = handler.delete(43).await.unwrap();

assert_eq!(result.task_id, Some("task-del-ca".to_string()));
}
22 changes: 22 additions & 0 deletions tests/connectivity_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -967,3 +967,25 @@ async fn test_get_psc_service_endpoints_active_active() {

assert_eq!(result.task_id.as_deref(), Some("task-aa-get-psc-endpoints"));
}

// Alias round-trip: `list_tgw_attachments(id)` delegates to `get_tgws(id)`.
#[tokio::test]
async fn test_list_tgw_attachments_alias() {
let mock_server = MockServer::start().await;

Mock::given(method("GET"))
.and(path("/subscriptions/123/transitGateways"))
.and(header("x-api-key", "test-key"))
.and(header("x-api-secret-key", "test-secret"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"taskId": "task-tgw-list",
"status": "processing-completed"
})))
.mount(&mock_server)
.await;

let handler = ConnectivityHandler::new(test_client(mock_server.uri()));
let result = handler.list_tgw_attachments(123).await.unwrap();

assert_eq!(result.task_id, Some("task-tgw-list".to_string()));
}
Loading
Loading