9201ef759e
CI / lint (push) Waiting to run
CI / mypy (push) Waiting to run
CI / docs (push) Waiting to run
CI / test on 3.10 (standard) (push) Waiting to run
CI / test on 3.11 (standard) (push) Waiting to run
CI / test on 3.12 (standard) (push) Waiting to run
CI / test on 3.10 (all-extras) (push) Waiting to run
CI / test on 3.11 (all-extras) (push) Waiting to run
CI / test on 3.12 (all-extras) (push) Waiting to run
CI / test on 3.13 (all-extras) (push) Waiting to run
CI / test on 3.14 (pydantic-evals) (push) Waiting to run
Harness Compat / harness compat (push) Waiting to run
CI / test on 3.13 (standard) (push) Waiting to run
CI / test on 3.14 (standard) (push) Waiting to run
CI / test on 3.14 (all-extras) (push) Waiting to run
CI / test on 3.10 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.11 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.12 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.13 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.14 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.10 (pydantic-evals) (push) Waiting to run
CI / test on 3.11 (pydantic-evals) (push) Waiting to run
CI / test on 3.12 (pydantic-evals) (push) Waiting to run
CI / test on 3.13 (pydantic-evals) (push) Waiting to run
CI / test on 3.10 (lowest-versions) (push) Waiting to run
CI / test on 3.11 (lowest-versions) (push) Waiting to run
CI / test on 3.12 (lowest-versions) (push) Waiting to run
CI / test on 3.13 (lowest-versions) (push) Waiting to run
CI / test on 3.14 (lowest-versions) (push) Waiting to run
CI / test examples on 3.11 (push) Waiting to run
CI / test examples on 3.12 (push) Waiting to run
CI / test examples on 3.13 (push) Waiting to run
CI / test examples on 3.14 (push) Waiting to run
CI / coverage (push) Blocked by required conditions
CI / check (push) Blocked by required conditions
CI / deploy-docs (push) Blocked by required conditions
CI / deploy-docs-preview (push) Blocked by required conditions
CI / build release artifacts (push) Blocked by required conditions
CI / publish to PyPI (push) Blocked by required conditions
CI / Send tweet (push) Blocked by required conditions
166 lines
6.2 KiB
Python
166 lines
6.2 KiB
Python
"""Tests for cassette_utils coverage gaps."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
from tests.cassette_utils import (
|
|
CassetteContext,
|
|
_get_cassette_bodies_from_yaml, # pyright: ignore[reportPrivateUsage]
|
|
_get_xai_cassette_request_bodies, # pyright: ignore[reportPrivateUsage]
|
|
get_first_post_body,
|
|
)
|
|
|
|
from ._inline_snapshot import snapshot
|
|
|
|
|
|
def _make_ctx(tmp_path: Path, provider: str = 'openai') -> CassetteContext:
|
|
return CassetteContext(
|
|
provider=provider,
|
|
vcr=None,
|
|
test_name='fake_test',
|
|
test_module='fake_module',
|
|
test_dir=tmp_path,
|
|
)
|
|
|
|
|
|
class TestGetCassetteBodiesFromYaml:
|
|
def test_dict_body(self, tmp_path: Path) -> None:
|
|
cassette_data: dict[str, Any] = {
|
|
'interactions': [
|
|
{'request': {'parsed_body': {'model': 'gpt-4', 'messages': []}}},
|
|
]
|
|
}
|
|
path = tmp_path / 'cassette.yaml'
|
|
path.write_text(yaml.dump(cassette_data), encoding='utf-8')
|
|
bodies = _get_cassette_bodies_from_yaml(path)
|
|
assert len(bodies) == 1
|
|
assert '"model": "gpt-4"' in bodies[0]
|
|
|
|
def test_string_body(self, tmp_path: Path) -> None:
|
|
cassette_data: dict[str, Any] = {
|
|
'interactions': [
|
|
{'request': {'body': 'raw request body'}},
|
|
]
|
|
}
|
|
path = tmp_path / 'cassette.yaml'
|
|
path.write_text(yaml.dump(cassette_data), encoding='utf-8')
|
|
bodies = _get_cassette_bodies_from_yaml(path)
|
|
assert bodies == ['raw request body']
|
|
|
|
def test_none_and_empty_bodies_skipped(self, tmp_path: Path) -> None:
|
|
cassette_data: dict[str, Any] = {
|
|
'interactions': [
|
|
{'request': {'body': None}},
|
|
{'request': {'body': ''}},
|
|
{'request': {}},
|
|
]
|
|
}
|
|
path = tmp_path / 'cassette.yaml'
|
|
path.write_text(yaml.dump(cassette_data), encoding='utf-8')
|
|
bodies = _get_cassette_bodies_from_yaml(path)
|
|
assert bodies == []
|
|
|
|
def test_list_body(self, tmp_path: Path) -> None:
|
|
cassette_data: dict[str, Any] = {
|
|
'interactions': [
|
|
{'request': {'parsed_body': [{'role': 'user'}]}},
|
|
]
|
|
}
|
|
path = tmp_path / 'cassette.yaml'
|
|
path.write_text(yaml.dump(cassette_data), encoding='utf-8')
|
|
bodies = _get_cassette_bodies_from_yaml(path)
|
|
assert len(bodies) == 1
|
|
assert '"role": "user"' in bodies[0]
|
|
|
|
|
|
def test_get_first_post_body_skips_non_post_request() -> None:
|
|
from vcr.cassette import Cassette
|
|
from vcr.request import Request
|
|
|
|
cassette = Cassette('fake.yaml')
|
|
cassette.append(Request('GET', 'https://example.com', None, dict[str, str]()), {}) # pyright: ignore[reportUnknownMemberType]
|
|
cassette.append( # pyright: ignore[reportUnknownMemberType]
|
|
Request('POST', 'https://example.com', b'{"key": "value"}', dict[str, str]()),
|
|
{},
|
|
)
|
|
|
|
assert get_first_post_body(cassette) == {'key': 'value'}
|
|
|
|
|
|
class TestCassetteContextGetBodies:
|
|
def test_xai_no_cassette(self, tmp_path: Path) -> None:
|
|
ctx = _make_ctx(tmp_path, provider='xai')
|
|
assert ctx._get_bodies() == [] # pyright: ignore[reportPrivateUsage]
|
|
|
|
def test_yaml_fallback(self, tmp_path: Path) -> None:
|
|
ctx = _make_ctx(tmp_path)
|
|
cassette_dir = tmp_path / 'cassettes' / 'fake_module'
|
|
cassette_dir.mkdir(parents=True)
|
|
cassette_data: dict[str, Any] = {
|
|
'interactions': [
|
|
{'request': {'parsed_body': {'key': 'value'}}},
|
|
]
|
|
}
|
|
(cassette_dir / 'fake_test.yaml').write_text(yaml.dump(cassette_data), encoding='utf-8')
|
|
bodies = ctx._get_bodies() # pyright: ignore[reportPrivateUsage]
|
|
assert len(bodies) == 1
|
|
assert '"key": "value"' in bodies[0]
|
|
|
|
def test_no_cassette_file(self, tmp_path: Path) -> None:
|
|
ctx = _make_ctx(tmp_path)
|
|
assert ctx._get_bodies() == [] # pyright: ignore[reportPrivateUsage]
|
|
|
|
|
|
class TestVerifyWithEmptyBodies:
|
|
@pytest.fixture()
|
|
def ctx(self, tmp_path: Path) -> CassetteContext:
|
|
return _make_ctx(tmp_path)
|
|
|
|
def test_verify_contains_no_bodies(self, ctx: CassetteContext) -> None:
|
|
ctx.verify_contains('anything')
|
|
|
|
def test_verify_ordering_no_bodies(self, ctx: CassetteContext) -> None:
|
|
ctx.verify_ordering('a', 'b', 'c')
|
|
|
|
|
|
class TestGetXaiCassetteRequestBodies:
|
|
def test_sdk_unavailable(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr('tests.models.xai_proto_cassettes.xai_sdk_available', lambda: False)
|
|
assert _get_xai_cassette_request_bodies(tmp_path / 'fake.yaml') == []
|
|
|
|
def test_sdk_available_with_interactions(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
from tests.models.xai_proto_cassettes import SampleInteraction, StreamInteraction, XaiProtoCassette
|
|
|
|
sample = SampleInteraction(
|
|
request_raw=b'',
|
|
response_raw=b'',
|
|
request_json={'model': 'grok'},
|
|
response_json={'text': 'hi'},
|
|
)
|
|
stream = StreamInteraction(
|
|
request_raw=b'',
|
|
chunks_raw=[],
|
|
request_json={'model': 'grok-stream'},
|
|
chunks_json=[{'chunk': 'data'}],
|
|
)
|
|
sample_no_json = SampleInteraction(request_raw=b'', response_raw=b'')
|
|
stream_no_json = StreamInteraction(request_raw=b'', chunks_raw=[])
|
|
cassette = XaiProtoCassette(interactions=[sample, stream, sample_no_json, stream_no_json])
|
|
|
|
monkeypatch.setattr('tests.models.xai_proto_cassettes.xai_sdk_available', lambda: True)
|
|
monkeypatch.setattr(XaiProtoCassette, 'load', staticmethod(lambda _path: cassette)) # pyright: ignore[reportUnknownArgumentType,reportUnknownLambdaType]
|
|
|
|
bodies = _get_xai_cassette_request_bodies(tmp_path / 'fake.yaml')
|
|
assert bodies == snapshot(
|
|
['{"model": "grok"}', '{"text": "hi"}', '{"model": "grok-stream"}', '{"chunk": "data"}']
|
|
)
|
|
assert '{"model": "grok"}' in bodies[0]
|
|
assert '{"text": "hi"}' in bodies[1]
|
|
assert '{"model": "grok-stream"}' in bodies[2]
|
|
assert '{"chunk": "data"}' in bodies[3]
|