Skip to content

Commit b2280da

Browse files
committed
test(conformance): add offline Anthropic shared public-contract baseline
Task: 1789897105
1 parent 28f06be commit b2280da

1 file changed

Lines changed: 151 additions & 0 deletions

File tree

‎tests/test_public_contract.py‎

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
from __future__ import annotations
2+
3+
import importlib.metadata
4+
5+
import httpx
6+
import pytest
7+
8+
import qca
9+
from qca import (
10+
NOT_GIVEN,
11+
APIConnectionError,
12+
APIStatusError,
13+
APITimeoutError,
14+
AsyncForward,
15+
AsyncManaged,
16+
AuthenticationError,
17+
BadRequestError,
18+
ConflictError,
19+
Forward,
20+
InternalServerError,
21+
Managed,
22+
NotFoundError,
23+
NotGiven,
24+
PermissionDeniedError,
25+
RateLimitError,
26+
UnprocessableEntityError,
27+
__version__,
28+
)
29+
30+
# Fixed upstream provenance for this shared-semantics baseline:
31+
# repository: anthropics/anthropic-sdk-python
32+
# tag: v1.7.0
33+
# commit: 0af0190679a9e80388bd1b0328d557c9a91a11b2
34+
# Consistency here means offline semantics ALREADY shared by QCA and the pinned
35+
# Anthropic baseline. It does NOT mirror Anthropic's full public surface. QCA's
36+
# intentional differences are NOT asserted: Forward/Managed + async clients, PAT
37+
# and Qoder headers, resumable stream, httpx, the qca-sdk/qca package + version,
38+
# the safe-retry policy, and the server-side error envelope. Anthropic public
39+
# APIs that QCA lacks get their own tasks; they are never shimmed, skipped, or
40+
# xfail'd here.
41+
42+
SYNC_CLIENTS = [Forward, Managed]
43+
ASYNC_CLIENTS = [AsyncForward, AsyncManaged]
44+
ALL_CLIENTS = SYNC_CLIENTS + ASYNC_CLIENTS
45+
46+
PUBLIC_STATUS_ERRORS = [
47+
BadRequestError,
48+
AuthenticationError,
49+
PermissionDeniedError,
50+
NotFoundError,
51+
ConflictError,
52+
UnprocessableEntityError,
53+
RateLimitError,
54+
InternalServerError,
55+
]
56+
57+
58+
def test_top_level_clients_sentinel_version_and_exceptions_are_public():
59+
# (1) The four clients, the missing-argument sentinel and its type, the
60+
# version and the shared public exceptions are importable from qca.
61+
for client_cls in ALL_CLIENTS:
62+
assert isinstance(client_cls, type)
63+
assert isinstance(NOT_GIVEN, NotGiven)
64+
assert isinstance(__version__, str) and __version__
65+
for exc in [APIConnectionError, APITimeoutError, APIStatusError, *PUBLIC_STATUS_ERRORS]:
66+
assert isinstance(exc, type)
67+
68+
69+
@pytest.mark.parametrize("exc", PUBLIC_STATUS_ERRORS)
70+
def test_status_errors_inherit_api_status_error(exc):
71+
# (2) Every shared status error subclasses APIStatusError; the timeout error
72+
# is a connection error. We do NOT require Anthropic-only error subclasses.
73+
assert issubclass(exc, APIStatusError)
74+
75+
76+
def test_timeout_is_a_connection_error():
77+
assert issubclass(APITimeoutError, APIConnectionError)
78+
79+
80+
def test_not_given_is_falsy_stable_repr_and_distinct_from_none():
81+
# (3) NOT_GIVEN is falsy, has a stable repr, and is not None.
82+
assert bool(NOT_GIVEN) is False
83+
assert repr(NOT_GIVEN) == "NOT_GIVEN"
84+
assert NOT_GIVEN is not None
85+
86+
87+
@pytest.mark.parametrize("client_cls", SYNC_CLIENTS)
88+
def test_sync_client_rejects_non_httpx_client(client_cls):
89+
# (4) Keyword-only construction validates the injected transport type.
90+
with pytest.raises(TypeError, match="httpx.Client"):
91+
client_cls(pat="test-token", base_url="https://api.test/prefix/", http_client=object())
92+
93+
94+
@pytest.mark.parametrize("client_cls", ASYNC_CLIENTS)
95+
def test_async_client_rejects_non_async_httpx_client(client_cls):
96+
with pytest.raises(TypeError, match="httpx.AsyncClient"):
97+
client_cls(pat="test-token", base_url="https://api.test/prefix/", http_client=httpx.Client())
98+
99+
100+
@pytest.mark.parametrize("client_cls", ALL_CLIENTS)
101+
def test_client_construction_is_keyword_only(client_cls):
102+
with pytest.raises(TypeError):
103+
client_cls("positional-pat") # type: ignore[misc]
104+
105+
106+
@pytest.mark.parametrize("client_cls", SYNC_CLIENTS)
107+
@pytest.mark.parametrize("bad", [-1, True, 1.5])
108+
def test_invalid_max_retries_is_rejected(client_cls, bad):
109+
# (5) Non-negative-int guard on max_retries and absolute-HTTP(S) guard on base_url.
110+
with pytest.raises(ValueError, match="max_retries"):
111+
client_cls(
112+
pat="test-token",
113+
base_url="https://api.test/prefix/",
114+
http_client=httpx.Client(transport=httpx.MockTransport(lambda _: httpx.Response(200, json={}))),
115+
max_retries=bad,
116+
)
117+
118+
119+
@pytest.mark.parametrize("client_cls", SYNC_CLIENTS)
120+
@pytest.mark.parametrize(
121+
"bad_url",
122+
["ftp://api.test/x", "https://user:pass@api.test/x", "https://api.test/x?a=1", "https://api.test/x#frag"],
123+
)
124+
def test_invalid_base_url_is_rejected(client_cls, bad_url):
125+
with pytest.raises(ValueError, match="base_url"):
126+
client_cls(
127+
pat="test-token",
128+
base_url=bad_url,
129+
http_client=httpx.Client(transport=httpx.MockTransport(lambda _: httpx.Response(200, json={}))),
130+
)
131+
132+
133+
@pytest.mark.parametrize("client_cls", SYNC_CLIENTS)
134+
def test_context_manager_closes_the_transport(client_cls):
135+
# (5) Context-manager lifecycle closes the underlying transport; is_closed()
136+
# is a method that reports the httpx client state.
137+
http = httpx.Client(transport=httpx.MockTransport(lambda _: httpx.Response(200, json={})))
138+
with client_cls(pat="test-token", base_url="https://api.test/prefix/", http_client=http) as client:
139+
assert client.is_closed() is False
140+
assert client.is_closed() is True
141+
142+
143+
def test_version_matches_installed_metadata_or_source_fallback():
144+
# (6) When installed, qca.__version__ mirrors the qca-sdk distribution;
145+
# from an uninstalled source tree the fallback string is used.
146+
try:
147+
installed = importlib.metadata.version("qca-sdk")
148+
except importlib.metadata.PackageNotFoundError:
149+
assert __version__ # source fallback keeps a non-empty version string
150+
else:
151+
assert __version__ == installed

0 commit comments

Comments
 (0)