Skip to content

Commit 4194ce1

Browse files
committed
scorer tests
1 parent d2910e7 commit 4194ce1

7 files changed

Lines changed: 550 additions & 2 deletions

File tree

tests/sdk/conftest.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"snapshot": "snap_123",
2121
"blueprint": "bp_123",
2222
"object": "obj_123",
23+
"scorer": "scorer_123",
2324
}
2425

2526
# Test URL constants
@@ -86,6 +87,15 @@ class MockObjectView:
8687
name: str = "test-object"
8788

8889

90+
@dataclass
91+
class MockScorerView:
92+
"""Mock ScorerView for testing."""
93+
94+
id: str = "scorer_123"
95+
bash_script: str = "echo 'score=1.0'"
96+
type: str = "test_scorer"
97+
98+
8999
def create_mock_httpx_client(methods: dict[str, Any] | None = None) -> AsyncMock:
90100
"""
91101
Create a mock httpx.AsyncClient with proper context manager setup.
@@ -170,6 +180,12 @@ def object_view() -> MockObjectView:
170180
return MockObjectView()
171181

172182

183+
@pytest.fixture
184+
def scorer_view() -> MockScorerView:
185+
"""Create a mock ScorerView."""
186+
return MockScorerView()
187+
188+
173189
@pytest.fixture
174190
def mock_httpx_response() -> Mock:
175191
"""Create a mock httpx.Response."""

tests/sdk/test_async_clients.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
from tests.sdk.conftest import (
1414
MockDevboxView,
1515
MockObjectView,
16+
MockScorerView,
1617
MockSnapshotView,
1718
MockBlueprintView,
1819
create_mock_httpx_response,
1920
)
20-
from runloop_api_client.sdk import AsyncDevbox, AsyncSnapshot, AsyncBlueprint, AsyncStorageObject
21+
from runloop_api_client.sdk import AsyncDevbox, AsyncScorer, AsyncSnapshot, AsyncBlueprint, AsyncStorageObject
2122
from runloop_api_client.sdk.async_ import (
2223
AsyncDevboxOps,
24+
AsyncScorerOps,
2325
AsyncRunloopSDK,
2426
AsyncSnapshotOps,
2527
AsyncBlueprintOps,
@@ -515,6 +517,53 @@ async def test_upload_from_dir_with_string_path(
515517
mock_async_client.objects.complete.assert_awaited_once()
516518

517519

520+
class TestAsyncScorerClient:
521+
"""Tests for AsyncScorerClient class."""
522+
523+
@pytest.mark.asyncio
524+
async def test_create(self, mock_async_client: AsyncMock, scorer_view: MockScorerView) -> None:
525+
"""Test create method."""
526+
mock_async_client.scenarios.scorers.create = AsyncMock(return_value=scorer_view)
527+
528+
client = AsyncScorerOps(mock_async_client)
529+
scorer = await client.create(
530+
bash_script="echo 'score=1.0'",
531+
type="test_scorer",
532+
)
533+
534+
assert isinstance(scorer, AsyncScorer)
535+
assert scorer.id == "scorer_123"
536+
mock_async_client.scenarios.scorers.create.assert_called_once()
537+
538+
def test_from_id(self, mock_async_client: AsyncMock) -> None:
539+
"""Test from_id method."""
540+
client = AsyncScorerOps(mock_async_client)
541+
scorer = client.from_id("scorer_123")
542+
543+
assert isinstance(scorer, AsyncScorer)
544+
assert scorer.id == "scorer_123"
545+
546+
@pytest.mark.asyncio
547+
async def test_list(self, mock_async_client: AsyncMock, scorer_view: MockScorerView) -> None:
548+
"""Test list method."""
549+
550+
async def async_iter():
551+
yield scorer_view
552+
553+
mock_async_client.scenarios.scorers.list = AsyncMock(return_value=async_iter())
554+
555+
client = AsyncScorerOps(mock_async_client)
556+
scorers = await client.list(
557+
limit=10,
558+
starting_after="scorer_000",
559+
)
560+
561+
assert len(scorers) == 1
562+
assert isinstance(scorers[0], AsyncScorer)
563+
assert scorers[0].id == "scorer_123"
564+
mock_async_client.scenarios.scorers.list.assert_called_once()
565+
566+
518567
class TestAsyncRunloopSDK:
519568
"""Tests for AsyncRunloopSDK class."""
520569

@@ -523,6 +572,7 @@ def test_init(self) -> None:
523572
sdk = AsyncRunloopSDK(bearer_token="test-token")
524573
assert sdk.api is not None
525574
assert isinstance(sdk.devbox, AsyncDevboxOps)
575+
assert isinstance(sdk.scorer, AsyncScorerOps)
526576
assert isinstance(sdk.snapshot, AsyncSnapshotOps)
527577
assert isinstance(sdk.blueprint, AsyncBlueprintOps)
528578
assert isinstance(sdk.storage_object, AsyncStorageObjectOps)

tests/sdk/test_async_scorer.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"""Comprehensive tests for async AsyncScorer class."""
2+
3+
from __future__ import annotations
4+
5+
from types import SimpleNamespace
6+
from unittest.mock import AsyncMock
7+
8+
import pytest
9+
10+
from tests.sdk.conftest import MockScorerView
11+
from runloop_api_client.sdk import AsyncScorer
12+
13+
14+
class TestAsyncScorer:
15+
"""Tests for AsyncScorer class."""
16+
17+
def test_init(self, mock_async_client: AsyncMock) -> None:
18+
"""Test AsyncScorer initialization."""
19+
scorer = AsyncScorer(mock_async_client, "scorer_123")
20+
assert scorer.id == "scorer_123"
21+
22+
def test_repr(self, mock_async_client: AsyncMock) -> None:
23+
"""Test AsyncScorer string representation."""
24+
scorer = AsyncScorer(mock_async_client, "scorer_123")
25+
assert repr(scorer) == "<AsyncScorer id='scorer_123'>"
26+
27+
def test_id_property(self, mock_async_client: AsyncMock) -> None:
28+
"""Test id property returns the scorer ID."""
29+
scorer = AsyncScorer(mock_async_client, "scorer_123")
30+
assert scorer.id == "scorer_123"
31+
32+
@pytest.mark.asyncio
33+
async def test_get_info(self, mock_async_client: AsyncMock, scorer_view: MockScorerView) -> None:
34+
"""Test get_info method."""
35+
mock_async_client.scenarios.scorers.retrieve = AsyncMock(return_value=scorer_view)
36+
37+
scorer = AsyncScorer(mock_async_client, "scorer_123")
38+
result = await scorer.get_info(
39+
extra_headers={"X-Custom": "value"},
40+
extra_query={"param": "value"},
41+
extra_body={"key": "value"},
42+
timeout=30.0,
43+
)
44+
45+
assert result == scorer_view
46+
mock_async_client.scenarios.scorers.retrieve.assert_called_once()
47+
48+
@pytest.mark.asyncio
49+
async def test_update(self, mock_async_client: AsyncMock) -> None:
50+
"""Test update method."""
51+
update_response = SimpleNamespace(id="scorer_123", name="updated-scorer")
52+
mock_async_client.scenarios.scorers.update = AsyncMock(return_value=update_response)
53+
54+
scorer = AsyncScorer(mock_async_client, "scorer_123")
55+
result = await scorer.update(
56+
name="updated-scorer",
57+
extra_headers={"X-Custom": "value"},
58+
extra_query={"param": "value"},
59+
extra_body={"key": "value"},
60+
timeout=30.0,
61+
)
62+
63+
assert result == update_response
64+
mock_async_client.scenarios.scorers.update.assert_called_once()
65+
66+
@pytest.mark.asyncio
67+
async def test_validate(self, mock_async_client: AsyncMock) -> None:
68+
"""Test validate method."""
69+
validate_response = SimpleNamespace(
70+
is_valid=True,
71+
score=0.95,
72+
reasoning="The output matches expected criteria.",
73+
)
74+
mock_async_client.scenarios.scorers.validate = AsyncMock(return_value=validate_response)
75+
76+
scorer = AsyncScorer(mock_async_client, "scorer_123")
77+
result = await scorer.validate(
78+
bash_command_output="test output",
79+
expected_output="test output",
80+
extra_headers={"X-Custom": "value"},
81+
extra_query={"param": "value"},
82+
extra_body={"key": "value"},
83+
timeout=30.0,
84+
)
85+
86+
assert result == validate_response
87+
assert result.is_valid is True
88+
assert result.score == 0.95
89+
mock_async_client.scenarios.scorers.validate.assert_called_once()

tests/sdk/test_clients.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
from tests.sdk.conftest import (
1212
MockDevboxView,
1313
MockObjectView,
14+
MockScorerView,
1415
MockSnapshotView,
1516
MockBlueprintView,
1617
create_mock_httpx_response,
1718
)
18-
from runloop_api_client.sdk import Devbox, Snapshot, Blueprint, StorageObject
19+
from runloop_api_client.sdk import Devbox, Scorer, Snapshot, Blueprint, StorageObject
1920
from runloop_api_client.sdk.sync import (
2021
DevboxOps,
22+
ScorerOps,
2123
RunloopSDK,
2224
SnapshotOps,
2325
BlueprintOps,
@@ -472,6 +474,47 @@ def test_upload_from_dir_with_string_path(
472474
mock_client.objects.complete.assert_called_once()
473475

474476

477+
class TestScorerClient:
478+
"""Tests for ScorerClient class."""
479+
480+
def test_create(self, mock_client: Mock, scorer_view: MockScorerView) -> None:
481+
"""Test create method."""
482+
mock_client.scenarios.scorers.create.return_value = scorer_view
483+
484+
client = ScorerOps(mock_client)
485+
scorer = client.create(
486+
bash_script="echo 'score=1.0'",
487+
type="test_scorer",
488+
)
489+
490+
assert isinstance(scorer, Scorer)
491+
assert scorer.id == "scorer_123"
492+
mock_client.scenarios.scorers.create.assert_called_once()
493+
494+
def test_from_id(self, mock_client: Mock) -> None:
495+
"""Test from_id method."""
496+
client = ScorerOps(mock_client)
497+
scorer = client.from_id("scorer_123")
498+
499+
assert isinstance(scorer, Scorer)
500+
assert scorer.id == "scorer_123"
501+
502+
def test_list(self, mock_client: Mock, scorer_view: MockScorerView) -> None:
503+
"""Test list method."""
504+
mock_client.scenarios.scorers.list.return_value = [scorer_view]
505+
506+
client = ScorerOps(mock_client)
507+
scorers = client.list(
508+
limit=10,
509+
starting_after="scorer_000",
510+
)
511+
512+
assert len(scorers) == 1
513+
assert isinstance(scorers[0], Scorer)
514+
assert scorers[0].id == "scorer_123"
515+
mock_client.scenarios.scorers.list.assert_called_once()
516+
517+
475518
class TestRunloopSDK:
476519
"""Tests for RunloopSDK class."""
477520

@@ -480,6 +523,7 @@ def test_init(self) -> None:
480523
sdk = RunloopSDK(bearer_token="test-token")
481524
assert sdk.api is not None
482525
assert isinstance(sdk.devbox, DevboxOps)
526+
assert isinstance(sdk.scorer, ScorerOps)
483527
assert isinstance(sdk.snapshot, SnapshotOps)
484528
assert isinstance(sdk.blueprint, BlueprintOps)
485529
assert isinstance(sdk.storage_object, StorageObjectOps)

tests/sdk/test_scorer.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""Comprehensive tests for sync Scorer class."""
2+
3+
from __future__ import annotations
4+
5+
from types import SimpleNamespace
6+
from unittest.mock import Mock
7+
8+
from tests.sdk.conftest import MockScorerView
9+
from runloop_api_client.sdk import Scorer
10+
11+
12+
class TestScorer:
13+
"""Tests for Scorer class."""
14+
15+
def test_init(self, mock_client: Mock) -> None:
16+
"""Test Scorer initialization."""
17+
scorer = Scorer(mock_client, "scorer_123")
18+
assert scorer.id == "scorer_123"
19+
20+
def test_repr(self, mock_client: Mock) -> None:
21+
"""Test Scorer string representation."""
22+
scorer = Scorer(mock_client, "scorer_123")
23+
assert repr(scorer) == "<Scorer id='scorer_123'>"
24+
25+
def test_id_property(self, mock_client: Mock) -> None:
26+
"""Test id property returns the scorer ID."""
27+
scorer = Scorer(mock_client, "scorer_123")
28+
assert scorer.id == "scorer_123"
29+
30+
def test_get_info(self, mock_client: Mock, scorer_view: MockScorerView) -> None:
31+
"""Test get_info method."""
32+
mock_client.scenarios.scorers.retrieve.return_value = scorer_view
33+
34+
scorer = Scorer(mock_client, "scorer_123")
35+
result = scorer.get_info(
36+
extra_headers={"X-Custom": "value"},
37+
extra_query={"param": "value"},
38+
extra_body={"key": "value"},
39+
timeout=30.0,
40+
)
41+
42+
assert result == scorer_view
43+
mock_client.scenarios.scorers.retrieve.assert_called_once_with(
44+
"scorer_123",
45+
extra_headers={"X-Custom": "value"},
46+
extra_query={"param": "value"},
47+
extra_body={"key": "value"},
48+
timeout=30.0,
49+
)
50+
51+
def test_update(self, mock_client: Mock) -> None:
52+
"""Test update method."""
53+
update_response = SimpleNamespace(id="scorer_123", name="updated-scorer")
54+
mock_client.scenarios.scorers.update.return_value = update_response
55+
56+
scorer = Scorer(mock_client, "scorer_123")
57+
result = scorer.update(
58+
name="updated-scorer",
59+
extra_headers={"X-Custom": "value"},
60+
extra_query={"param": "value"},
61+
extra_body={"key": "value"},
62+
timeout=30.0,
63+
)
64+
65+
assert result == update_response
66+
mock_client.scenarios.scorers.update.assert_called_once_with(
67+
"scorer_123",
68+
name="updated-scorer",
69+
extra_headers={"X-Custom": "value"},
70+
extra_query={"param": "value"},
71+
extra_body={"key": "value"},
72+
timeout=30.0,
73+
)
74+
75+
def test_validate(self, mock_client: Mock) -> None:
76+
"""Test validate method."""
77+
validate_response = SimpleNamespace(
78+
is_valid=True,
79+
score=0.95,
80+
reasoning="The output matches expected criteria.",
81+
)
82+
mock_client.scenarios.scorers.validate.return_value = validate_response
83+
84+
scorer = Scorer(mock_client, "scorer_123")
85+
result = scorer.validate(
86+
bash_command_output="test output",
87+
expected_output="test output",
88+
extra_headers={"X-Custom": "value"},
89+
extra_query={"param": "value"},
90+
extra_body={"key": "value"},
91+
timeout=30.0,
92+
)
93+
94+
assert result == validate_response
95+
assert result.is_valid is True
96+
assert result.score == 0.95
97+
mock_client.scenarios.scorers.validate.assert_called_once_with(
98+
"scorer_123",
99+
bash_command_output="test output",
100+
expected_output="test output",
101+
extra_headers={"X-Custom": "value"},
102+
extra_query={"param": "value"},
103+
extra_body={"key": "value"},
104+
timeout=30.0,
105+
)

0 commit comments

Comments
 (0)