"""Unit tests for SynorCompute client.""" import pytest from unittest.mock import AsyncMock, MagicMock, patch import numpy as np from synor_compute import SynorCompute, SynorError, Tensor from synor_compute.types import ( ProcessorType, Precision, JobStatus, SynorConfig, ) @pytest.fixture def mock_response(): """Create a mock HTTP response.""" def _create_response(data, status=200): response = AsyncMock() response.status = status response.json = AsyncMock(return_value=data) response.text = AsyncMock(return_value=str(data)) return response return _create_response @pytest.fixture def client(): """Create a test client.""" return SynorCompute(api_key="test-api-key") class TestClientInitialization: """Tests for client initialization.""" def test_create_with_api_key(self): """Should create client with API key.""" client = SynorCompute(api_key="my-key") assert client is not None def test_create_with_config(self): """Should create client with config.""" config = SynorConfig( api_key="my-key", base_url="https://custom.api.com", timeout=60, ) client = SynorCompute(config=config) assert client is not None def test_raise_without_api_key(self): """Should raise error without API key.""" with pytest.raises((ValueError, TypeError)): SynorCompute(api_key="") class TestMatMul: """Tests for matrix multiplication.""" @pytest.mark.asyncio async def test_matmul_request(self, client, mock_response): """Should send matmul request.""" response_data = { "job_id": "job-123", "status": "completed", "result": {"data": "base64", "shape": [2, 4], "dtype": "fp32"}, "execution_time_ms": 15, "processor": "gpu", } with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = response_data a = Tensor.random((2, 3)) b = Tensor.random((3, 4)) result = await client.matmul(a, b) mock_req.assert_called_once() assert result.status == JobStatus.COMPLETED assert result.job_id == "job-123" @pytest.mark.asyncio async def test_matmul_with_precision(self, client): """Should support precision option.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = { "job_id": "job-456", "status": "completed", "result": {"data": "base64", "shape": [2, 2], "dtype": "fp16"}, } a = Tensor.random((2, 2)) b = Tensor.random((2, 2)) await client.matmul(a, b, precision=Precision.FP16) call_args = mock_req.call_args assert call_args[1].get("precision") == "fp16" or \ call_args[0][1].get("precision") == "fp16" @pytest.mark.asyncio async def test_matmul_with_processor(self, client): """Should support processor selection.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = { "job_id": "job-789", "status": "completed", "result": {"data": "base64", "shape": [2, 2], "dtype": "fp32"}, } a = Tensor.random((2, 2)) b = Tensor.random((2, 2)) await client.matmul(a, b, processor=ProcessorType.TPU) mock_req.assert_called_once() class TestConv2d: """Tests for 2D convolution.""" @pytest.mark.asyncio async def test_conv2d_request(self, client): """Should send conv2d request.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = { "job_id": "conv-123", "status": "completed", "result": {"data": "base64", "shape": [1, 32, 30, 30], "dtype": "fp32"}, } input_tensor = Tensor.random((1, 3, 32, 32)) kernel = Tensor.random((32, 3, 3, 3)) result = await client.conv2d(input_tensor, kernel) assert result.status == JobStatus.COMPLETED @pytest.mark.asyncio async def test_conv2d_with_options(self, client): """Should support stride and padding options.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = { "job_id": "conv-456", "status": "completed", "result": {"data": "base64", "shape": [1, 32, 16, 16], "dtype": "fp32"}, } input_tensor = Tensor.random((1, 3, 32, 32)) kernel = Tensor.random((32, 3, 3, 3)) await client.conv2d( input_tensor, kernel, stride=(2, 2), padding=(1, 1), ) mock_req.assert_called_once() class TestAttention: """Tests for attention operation.""" @pytest.mark.asyncio async def test_attention_request(self, client): """Should send attention request.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = { "job_id": "attn-123", "status": "completed", "result": {"data": "base64", "shape": [1, 8, 64, 64], "dtype": "fp16"}, } query = Tensor.random((1, 8, 64, 64)) key = Tensor.random((1, 8, 64, 64)) value = Tensor.random((1, 8, 64, 64)) result = await client.attention(query, key, value) assert result.status == JobStatus.COMPLETED @pytest.mark.asyncio async def test_attention_flash(self, client): """Should support flash attention option.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = { "job_id": "attn-456", "status": "completed", "result": {"data": "base64", "shape": [1, 8, 64, 64], "dtype": "fp16"}, } query = Tensor.random((1, 8, 64, 64)) key = Tensor.random((1, 8, 64, 64)) value = Tensor.random((1, 8, 64, 64)) await client.attention(query, key, value, flash=True) mock_req.assert_called_once() class TestInference: """Tests for LLM inference.""" @pytest.mark.asyncio async def test_inference_request(self, client): """Should send inference request.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = { "job_id": "inf-123", "status": "completed", "result": "The answer to your question is...", "execution_time_ms": 2500, "processor": "gpu", } result = await client.inference( model="llama-3-70b", prompt="What is the capital of France?", ) assert result.status == JobStatus.COMPLETED assert "answer" in result.result @pytest.mark.asyncio async def test_inference_with_options(self, client): """Should support inference options.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = { "job_id": "inf-456", "status": "completed", "result": "Generated text...", } await client.inference( model="llama-3-70b", prompt="Hello", max_tokens=512, temperature=0.8, top_p=0.95, top_k=40, ) mock_req.assert_called_once() class TestModelRegistry: """Tests for model registry operations.""" @pytest.mark.asyncio async def test_list_models(self, client): """Should list available models.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = { "models": [ {"id": "llama-3-70b", "name": "Llama 3 70B", "category": "llm"}, {"id": "mistral-7b", "name": "Mistral 7B", "category": "llm"}, ] } models = await client.list_models() assert len(models) == 2 assert models[0]["id"] == "llama-3-70b" @pytest.mark.asyncio async def test_list_models_by_category(self, client): """Should filter models by category.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = { "models": [ {"id": "sd-xl", "name": "Stable Diffusion XL", "category": "image_generation"}, ] } models = await client.list_models(category="image_generation") assert len(models) == 1 assert models[0]["category"] == "image_generation" @pytest.mark.asyncio async def test_get_model(self, client): """Should get model by ID.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = { "id": "llama-3-70b", "name": "Llama 3 70B", "category": "llm", "parameters": 70000000000, "context_length": 8192, } model = await client.get_model("llama-3-70b") assert model["id"] == "llama-3-70b" assert model["parameters"] == 70000000000 class TestPricingAndUsage: """Tests for pricing and usage APIs.""" @pytest.mark.asyncio async def test_get_pricing(self, client): """Should get pricing information.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = { "pricing": [ {"processor": "gpu", "price_per_second": 0.0001, "available_units": 100}, {"processor": "tpu", "price_per_second": 0.0002, "available_units": 50}, ] } pricing = await client.get_pricing() assert len(pricing) == 2 assert pricing[0]["processor"] == "gpu" @pytest.mark.asyncio async def test_get_usage(self, client): """Should get usage statistics.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = { "total_jobs": 1000, "completed_jobs": 950, "failed_jobs": 50, "total_compute_seconds": 3600, "total_cost": 0.36, } usage = await client.get_usage() assert usage["total_jobs"] == 1000 assert usage["completed_jobs"] == 950 class TestErrorHandling: """Tests for error handling.""" @pytest.mark.asyncio async def test_api_error(self, client): """Should handle API errors.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.side_effect = SynorError("Invalid API key", status_code=401) a = Tensor.random((2, 2)) b = Tensor.random((2, 2)) with pytest.raises(SynorError) as exc_info: await client.matmul(a, b) assert exc_info.value.status_code == 401 @pytest.mark.asyncio async def test_network_error(self, client): """Should handle network errors.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.side_effect = Exception("Network error") a = Tensor.random((2, 2)) b = Tensor.random((2, 2)) with pytest.raises(Exception, match="Network error"): await client.matmul(a, b) class TestHealthCheck: """Tests for health check.""" @pytest.mark.asyncio async def test_healthy_service(self, client): """Should return True for healthy service.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.return_value = {"status": "healthy"} healthy = await client.health_check() assert healthy is True @pytest.mark.asyncio async def test_unhealthy_service(self, client): """Should return False for unhealthy service.""" with patch.object(client, "_request", new_callable=AsyncMock) as mock_req: mock_req.side_effect = Exception("Service unavailable") healthy = await client.health_check() assert healthy is False