"""Tests for the Z.ai OpenAI-chat Coding Plan provider.""" from unittest.mock import AsyncMock, MagicMock import pytest from free_claude_code.application.errors import InvalidRequestError from free_claude_code.config.constants import ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS from free_claude_code.config.provider_catalog import ZAI_DEFAULT_BASE from free_claude_code.core.anthropic.models import Message, MessagesRequest from free_claude_code.providers.base import ProviderConfig from free_claude_code.providers.openai_chat import OpenAIChatProvider from tests.providers.support import ( immediate_admission, profiled_provider, reasoning_for, ) @pytest.fixture def zai_provider(): return profiled_provider( "zai", ProviderConfig( api_key="test_zai_key", base_url=ZAI_DEFAULT_BASE, rate_limit=10, rate_window=60, ), admission=immediate_admission(), ) def test_init_uses_openai_chat_coding_endpoint(zai_provider): assert isinstance(zai_provider, OpenAIChatProvider) assert zai_provider._api_key == "test_zai_key" assert zai_provider._base_url == "https://api.z.ai/api/coding/paas/v4" def test_build_request_body_openai_chat(zai_provider): request = MessagesRequest.model_validate( { "model": "glm-5.2", "max_tokens": 100, "messages": [Message(role="user", content="Hello")], "thinking": {"type": "enabled"}, } ) body = zai_provider._build_request_body(request, reasoning=reasoning_for(request)) assert body["model"] == "glm-5.2" assert body["max_tokens"] == 100 assert body["messages"] == [{"role": "user", "content": "Hello"}] assert body["extra_body"]["thinking"] == { "type": "enabled", "clear_thinking": False, } def test_build_request_body_default_max_tokens(zai_provider): request = MessagesRequest( model="m", messages=[Message(role="user", content="x")], ) body = zai_provider._build_request_body(request, reasoning=reasoning_for(request)) assert body["max_tokens"] == ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS def test_build_request_body_rejects_caller_extra_body(zai_provider): request = MessagesRequest.model_validate( { "model": "m", "messages": [{"role": "user", "content": "x"}], "extra_body": {"x": 1}, } ) with pytest.raises(InvalidRequestError, match=r"Z\.ai Chat Completions"): zai_provider._build_request_body(request, reasoning=reasoning_for(request)) def test_build_request_body_disables_zai_thinking(zai_provider): request = MessagesRequest.model_validate( { "model": "m", "messages": [{"role": "user", "content": "x"}], "thinking": {"type": "disabled"}, } ) body = zai_provider._build_request_body(request, reasoning=reasoning_for(request)) assert body["extra_body"]["thinking"] == {"type": "disabled"} def test_build_request_body_replays_prior_reasoning_content(zai_provider): request = MessagesRequest.model_validate( { "model": "glm-5.2", "messages": [ { "role": "assistant", "content": [{"type": "thinking", "thinking": "prior"}], }, {"role": "user", "content": "continue"}, ], } ) body = zai_provider._build_request_body(request, reasoning=reasoning_for(request)) assert body["messages"][0]["reasoning_content"] == "prior" assert "extra_body" not in body @pytest.mark.asyncio async def test_cleanup_closes_openai_client(zai_provider): zai_provider._client = MagicMock() zai_provider._client.close = AsyncMock() await zai_provider.cleanup() zai_provider._client.close.assert_awaited_once()