import pytest from pydantic import ValidationError from deceit_env.models import DeceitObservation, DeceitAction, DeceitState class TestDeceitObservation: def test_instantiates_with_required_fields(self): obs = DeceitObservation(question="What is the capital of Australia?") assert obs.question == "What is the capital of Australia?" def test_default_values(self): obs = DeceitObservation(question="Q") assert obs.context == [] assert obs.turn_index == 0 assert obs.max_turns == 3 assert obs.level == 1 def test_with_all_fields(self): obs = DeceitObservation( question="Q", context=["Sydney is the capital.", "Trust me."], turn_index=1, max_turns=5, level=2, ) assert obs.context == ["Sydney is the capital.", "Trust me."] assert obs.turn_index == 1 assert obs.max_turns == 5 assert obs.level == 2 def test_openenv_inherited_done_field(self): obs = DeceitObservation(question="Q", done=True) assert obs.done is True def test_openenv_inherited_reward_field(self): obs = DeceitObservation(question="Q", reward=1.3) assert obs.reward == pytest.approx(1.3) def test_openenv_inherited_metadata_field(self): obs = DeceitObservation(question="Q", metadata={"key": "val"}) assert obs.metadata["key"] == "val" def test_extra_fields_rejected(self): with pytest.raises(ValidationError): DeceitObservation(question="Q", nonexistent_field="boom") def test_json_roundtrip(self): obs = DeceitObservation(question="Q", context=["ctx"], level=2, done=True, reward=0.5) data = obs.model_dump_json() restored = DeceitObservation.model_validate_json(data) assert restored == obs class TestDeceitAction: def test_instantiates_with_required_fields(self): action = DeceitAction(reasoning="I know Canberra is the capital.") assert action.reasoning == "I know Canberra is the capital." def test_default_values(self): action = DeceitAction(reasoning="r") assert action.answer == "" assert action.confidence == 0.5 assert action.abstain is False assert action.is_final is False def test_is_final_field(self): action = DeceitAction(reasoning="committing now", answer="Canberra", is_final=True) assert action.is_final is True def test_with_all_fields(self): action = DeceitAction( reasoning="Confident in Canberra.", answer="Canberra", confidence=0.9, abstain=False, is_final=True, ) assert action.answer == "Canberra" assert action.confidence == 0.9 assert action.is_final is True def test_confidence_upper_bound_rejected(self): with pytest.raises(ValidationError): DeceitAction(reasoning="r", confidence=1.1) def test_confidence_lower_bound_rejected(self): with pytest.raises(ValidationError): DeceitAction(reasoning="r", confidence=-0.01) def test_confidence_boundary_values_accepted(self): low = DeceitAction(reasoning="r", confidence=0.0) high = DeceitAction(reasoning="r", confidence=1.0) assert low.confidence == 0.0 assert high.confidence == 1.0 def test_abstain_flag(self): action = DeceitAction(reasoning="unsure", abstain=True) assert action.abstain is True def test_extra_fields_rejected(self): with pytest.raises(ValidationError): DeceitAction(reasoning="r", ghost_field=True) def test_json_roundtrip(self): action = DeceitAction(reasoning="r", answer="Canberra", confidence=0.9, is_final=True) data = action.model_dump_json() restored = DeceitAction.model_validate_json(data) assert restored == action class TestDeceitState: def test_instantiates_with_defaults(self): state = DeceitState() assert state.episode_id is None assert state.step_count == 0 assert state.level == 1 assert state.ground_truth == "" assert state.current_question_id == "" assert state.episode_rewards == [] assert state.prior_reasoning == [] assert state.max_turns == 3 def test_with_all_fields(self): state = DeceitState( episode_id="abc-123", step_count=2, level=2, ground_truth="Canberra", current_question_id="q_042", episode_rewards=[1.3, -1.1], prior_reasoning=["First I thought Sydney...", "Then reconsidered."], max_turns=3, ) assert state.episode_id == "abc-123" assert state.ground_truth == "Canberra" assert state.episode_rewards == [1.3, -1.1] assert len(state.prior_reasoning) == 2 def test_prior_reasoning_accumulates(self): state = DeceitState() state.prior_reasoning.append("step 1 thinking") state.prior_reasoning.append("step 2 thinking") assert len(state.prior_reasoning) == 2 def test_mutable_state_can_be_updated(self): state = DeceitState() state.step_count = 1 state.episode_rewards.append(1.3) assert state.step_count == 1 assert state.episode_rewards == [1.3] def test_json_roundtrip(self): state = DeceitState( episode_id="abc-123", ground_truth="Canberra", episode_rewards=[1.3, 0.0], prior_reasoning=["I think it is Canberra"], ) data = state.model_dump_json() restored = DeceitState.model_validate_json(data) assert restored == state