Skip to content

Commit 2cd06af

Browse files
HartornHartorn
andauthored
fix: dataset.create_test_case does not filter out attributes (#99)
* test: parametrize and document Dataset.create_chat_test_case filtering behavior - Add parameterized test for Dataset.create_chat_test_case to ensure extra fields (e.g., id/timestamps) from imported ChatTestCase objects are not forwarded. - Cover both inputs: - plain ChatTestCase constructed in-code - imported ChatTestCase via from_dict with id/timestamps - Document SimpleNamespace usage for lightweight client wiring. * fix(dataset): filter fields in Dataset.create_chat_test_case - Send only allowed fields (messages, demo_output, tags, checks) plus dataset_id instead of forwarding the full ChatTestCase dict (which included id/timestamps). - Prevents TypeError when creating test cases from objects copied across environments. --------- Co-authored-by: Hartorn <[email protected]*>
1 parent 089740d commit 2cd06af

File tree

2 files changed

+102
-1
lines changed

2 files changed

+102
-1
lines changed

src/giskard_hub/data/dataset.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,13 @@ def create_chat_test_case(self, chat_test_case: ChatTestCase):
3131
"This dataset instance is detached or unsaved, cannot add chat test case."
3232
)
3333

34+
# Only pass allowed fields to the API; do not forward id/timestamps
3435
return self._client.chat_test_cases.create(
35-
dataset_id=self.id, **chat_test_case.to_dict()
36+
dataset_id=self.id,
37+
messages=chat_test_case.messages,
38+
demo_output=chat_test_case.demo_output,
39+
tags=chat_test_case.tags,
40+
checks=chat_test_case.checks,
3641
)
3742

3843
@classmethod

tests/test_datasets.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import pytest
44

5+
from giskard_hub.data.chat import ChatMessage
6+
from giskard_hub.data.chat_test_case import ChatTestCase
57
from giskard_hub.data.dataset import Dataset
68
from giskard_hub.resources.datasets import DatasetsResource
79

@@ -479,3 +481,97 @@ def test_dataset_create_chat_test_case_without_client(self):
479481

480482
with pytest.raises(ValueError, match="detached or unsaved"):
481483
dataset.create_chat_test_case(chat_test_case)
484+
485+
@pytest.mark.parametrize(
486+
"chat_test_case",
487+
[
488+
ChatTestCase(
489+
messages=[ChatMessage(role="user", content="Hello")],
490+
checks=[],
491+
demo_output=None,
492+
tags=["tag-a"],
493+
),
494+
ChatTestCase.from_dict(
495+
{
496+
"id": "tc-123",
497+
"created_at": "2025-06-17T12:46:52.424Z",
498+
"updated_at": "2025-06-17T12:46:52.424Z",
499+
"messages": [
500+
{"role": "user", "content": "Hello"},
501+
],
502+
"demo_output": None,
503+
"tags": ["tag-a"],
504+
"checks": [],
505+
}
506+
),
507+
],
508+
ids=["plain", "imported"],
509+
) # plain: constructed object; imported: from another env with id/timestamps
510+
def test_dataset_create_chat_test_case_filters_extra_fields(self, chat_test_case):
511+
"""Dataset.create_chat_test_case should ignore id/timestamps from an imported test case.
512+
513+
Note: SimpleNamespace is used here as a lightweight container to simulate a client with a
514+
`chat_test_cases` attribute, so we can wire our `ChatTestCasesResource` without defining a full class.
515+
"""
516+
from types import SimpleNamespace
517+
518+
from giskard_hub.resources.chat_test_cases import ChatTestCasesResource
519+
520+
# HTTP-level client mock used by the resource
521+
http_client = MagicMock()
522+
523+
# Mimic casting behavior of the lower-level client
524+
def mock_post(path, json=None, cast_to=None, **kwargs):
525+
data = http_client.post.return_value
526+
if cast_to and data:
527+
return cast_to.from_dict(data)
528+
return data
529+
530+
http_client.post.side_effect = mock_post
531+
532+
# Resource that enforces the create signature (will raise if unexpected kwargs are passed)
533+
resource = ChatTestCasesResource(http_client)
534+
535+
# Dataset client exposing the resource
536+
client = SimpleNamespace(chat_test_cases=resource)
537+
538+
dataset = Dataset.from_dict(
539+
{
540+
"id": "dataset-1",
541+
"name": "Test Dataset",
542+
},
543+
_client=client,
544+
)
545+
546+
# chat_test_case comes from parametrize above (either plain or imported)
547+
548+
# Mock server returns the created object
549+
http_client.post.return_value = {
550+
"id": "tc-new",
551+
"created_at": "2025-06-17T12:46:52.424Z",
552+
"updated_at": "2025-06-17T12:46:52.424Z",
553+
"messages": [
554+
{"role": "user", "content": "Hello"},
555+
],
556+
"demo_output": None,
557+
"tags": ["tag-a"],
558+
"checks": [],
559+
}
560+
561+
result = dataset.create_chat_test_case(chat_test_case)
562+
563+
# Ensure we called the backend with only allowed fields
564+
assert http_client.post.called
565+
call = http_client.post.call_args
566+
assert call[0][0] == "/chat-test-cases"
567+
payload = call[1]["json"]
568+
assert payload == {
569+
"dataset_id": "dataset-1",
570+
"messages": [{"role": "user", "content": "Hello"}],
571+
"demo_output": None,
572+
"tags": ["tag-a"],
573+
"checks": [],
574+
}
575+
576+
# And we get a proper ChatTestCase back
577+
assert result.id == "tc-new"

0 commit comments

Comments
 (0)