# test_properties_persistence.py """ Property-based tests for verification data persistence. Tests that verification records and sessions persist correctly. """ import pytest from hypothesis import given, strategies as st, settings, HealthCheck from datetime import datetime from src.core.verification_models import ( VerificationRecord, VerificationSession, ) from src.core.verification_store import JSONVerificationStore # Strategies for generating test data def valid_id_strategy(): """Generate valid IDs for use as filenames.""" return st.text( alphabet="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-", min_size=1, max_size=20, ) def verification_record_strategy(): """Generate random verification records with consistent is_correct field.""" # Generate classifier_decision and ground_truth_label together to ensure is_correct is consistent @st.composite def build_record(draw): message_id = draw(valid_id_strategy()) original_message = draw(st.text(min_size=1, max_size=500)) classifier_decision = draw(st.sampled_from(["green", "yellow", "red"])) classifier_confidence = draw(st.floats(min_value=0.0, max_value=1.0)) classifier_indicators = draw(st.lists(st.text(min_size=1, max_size=50), max_size=5)) verifier_notes = draw(st.text(max_size=200)) # Decide if this should be correct or incorrect is_correct = draw(st.booleans()) # Set ground_truth_label based on is_correct if is_correct: ground_truth_label = classifier_decision else: # Pick a different label other_labels = [l for l in ["green", "yellow", "red"] if l != classifier_decision] ground_truth_label = draw(st.sampled_from(other_labels)) return VerificationRecord( message_id=message_id, original_message=original_message, classifier_decision=classifier_decision, classifier_confidence=classifier_confidence, classifier_indicators=classifier_indicators, ground_truth_label=ground_truth_label, verifier_notes=verifier_notes, is_correct=is_correct, timestamp=datetime.now(), ) return build_record() def verification_session_strategy(): """Generate random verification sessions.""" return st.builds( VerificationSession, session_id=valid_id_strategy(), verifier_name=st.text(min_size=1, max_size=50), dataset_id=valid_id_strategy(), dataset_name=st.text(min_size=1, max_size=100), created_at=st.just(datetime.now()), completed_at=st.none(), total_messages=st.integers(min_value=1, max_value=100), verified_count=st.integers(min_value=0, max_value=100), correct_count=st.integers(min_value=0, max_value=100), incorrect_count=st.integers(min_value=0, max_value=100), verifications=st.just([]), is_complete=st.booleans(), ) class TestVerificationRecordPersistence: """ **Feature: verification-mode, Property 1: Feedback Saves Correctly** Tests that verification records save and load correctly with all fields intact. """ @given(verification_record_strategy()) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) def test_record_saves_and_loads_correctly(self, verification_store, record): """ **Feature: verification-mode, Property 1: Feedback Saves Correctly** **Validates: Requirements 3.2, 3.5, 8.1** For any verification record, when saved to storage and then loaded, all fields should be preserved exactly. """ # Create a session to hold the record session = VerificationSession( session_id="test_session", verifier_name="Test Verifier", dataset_id="test_dataset", dataset_name="Test Dataset", total_messages=1, ) verification_store.save_session(session) # Save the verification record verification_store.save_verification("test_session", record) # Load the session and verify the record loaded_session = verification_store.load_session("test_session") assert loaded_session is not None assert len(loaded_session.verifications) == 1 loaded_record = loaded_session.verifications[0] # Verify all fields are preserved assert loaded_record.message_id == record.message_id assert loaded_record.original_message == record.original_message assert loaded_record.classifier_decision == record.classifier_decision assert loaded_record.classifier_confidence == record.classifier_confidence assert loaded_record.classifier_indicators == record.classifier_indicators assert loaded_record.ground_truth_label == record.ground_truth_label assert loaded_record.verifier_notes == record.verifier_notes assert loaded_record.is_correct == record.is_correct @given(verification_record_strategy()) def test_record_to_dict_and_back(self, record): """ **Feature: verification-mode, Property 1: Feedback Saves Correctly** **Validates: Requirements 3.2, 3.5, 8.1** For any verification record, converting to dict and back should preserve all fields. """ # Convert to dict and back record_dict = record.to_dict() restored_record = VerificationRecord.from_dict(record_dict) # Verify all fields match assert restored_record.message_id == record.message_id assert restored_record.original_message == record.original_message assert restored_record.classifier_decision == record.classifier_decision assert restored_record.classifier_confidence == record.classifier_confidence assert restored_record.classifier_indicators == record.classifier_indicators assert restored_record.ground_truth_label == record.ground_truth_label assert restored_record.verifier_notes == record.verifier_notes assert restored_record.is_correct == record.is_correct class TestSessionStatePersistence: """ **Feature: verification-mode, Property 3: Session State Persists** Tests that verification sessions persist and can be resumed with state intact. """ @given(verification_session_strategy()) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) def test_session_saves_and_loads_correctly(self, verification_store, session): """ **Feature: verification-mode, Property 3: Session State Persists** **Validates: Requirements 8.2, 8.3** For any verification session, when saved and then loaded, all session state should be preserved exactly. """ # Save the session verification_store.save_session(session) # Load the session loaded_session = verification_store.load_session(session.session_id) # Verify all fields are preserved assert loaded_session is not None assert loaded_session.session_id == session.session_id assert loaded_session.verifier_name == session.verifier_name assert loaded_session.dataset_id == session.dataset_id assert loaded_session.dataset_name == session.dataset_name assert loaded_session.total_messages == session.total_messages assert loaded_session.verified_count == session.verified_count assert loaded_session.correct_count == session.correct_count assert loaded_session.incorrect_count == session.incorrect_count assert loaded_session.is_complete == session.is_complete @given(verification_session_strategy()) def test_session_to_dict_and_back(self, session): """ **Feature: verification-mode, Property 3: Session State Persists** **Validates: Requirements 8.2, 8.3** For any verification session, converting to dict and back should preserve all session state. """ # Convert to dict and back session_dict = session.to_dict() restored_session = VerificationSession.from_dict(session_dict) # Verify all fields match assert restored_session.session_id == session.session_id assert restored_session.verifier_name == session.verifier_name assert restored_session.dataset_id == session.dataset_id assert restored_session.dataset_name == session.dataset_name assert restored_session.total_messages == session.total_messages assert restored_session.verified_count == session.verified_count assert restored_session.correct_count == session.correct_count assert restored_session.incorrect_count == session.incorrect_count assert restored_session.is_complete == session.is_complete @given(verification_session_strategy()) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None) def test_session_with_multiple_records_persists( self, verification_store, session ): """ **Feature: verification-mode, Property 3: Session State Persists** **Validates: Requirements 8.2, 8.3** For any session with multiple verification records, when saved and loaded, all records and session state should be preserved. """ # Ensure session is not already marked complete session.is_complete = False session.completed_at = None # Generate records with unique message IDs records = [] for i in range(5): record = VerificationRecord( message_id=f"msg_{i}", original_message=f"Test message {i}", classifier_decision="green", classifier_confidence=0.9, classifier_indicators=["test"], ground_truth_label="green", verifier_notes="", is_correct=True, timestamp=datetime.now(), ) records.append(record) # Save the session verification_store.save_session(session) # Add records to the session for record in records: verification_store.save_verification(session.session_id, record) # Load the session loaded_session = verification_store.load_session(session.session_id) # Verify session state assert loaded_session is not None assert loaded_session.session_id == session.session_id assert len(loaded_session.verifications) == len(records) # Verify all records are preserved for i, original_record in enumerate(records): loaded_record = loaded_session.verifications[i] assert loaded_record.message_id == original_record.message_id assert loaded_record.original_message == original_record.original_message assert ( loaded_record.classifier_decision == original_record.classifier_decision ) class TestCompletedSessionImmutability: """ **Feature: verification-mode, Property 13: Completed Sessions Cannot be Modified** Tests that completed sessions cannot be modified after completion. """ @given(verification_session_strategy(), verification_record_strategy()) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) def test_completed_session_cannot_be_modified( self, verification_store, session, record ): """ **Feature: verification-mode, Property 13: Completed Sessions Cannot be Modified** **Validates: Requirements 8.4** For any completed verification session, attempting to add new verifications should raise an error and the session should remain unchanged. """ # Save the session verification_store.save_session(session) # Mark session as complete verification_store.mark_session_complete(session.session_id) # Verify session is marked complete loaded_session = verification_store.load_session(session.session_id) assert loaded_session.is_complete is True assert loaded_session.completed_at is not None # Attempt to add a verification record to completed session with pytest.raises(ValueError, match="Cannot modify completed session"): verification_store.save_verification(session.session_id, record) @given(verification_session_strategy()) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) def test_can_modify_session_returns_false_for_completed( self, verification_store, session ): """ **Feature: verification-mode, Property 13: Completed Sessions Cannot be Modified** **Validates: Requirements 8.4** For any completed session, can_modify_session should return False. """ # Ensure session is not already marked complete session.is_complete = False session.completed_at = None # Save the session verification_store.save_session(session) # Initially should be modifiable assert verification_store.can_modify_session(session.session_id) is True # Mark session as complete verification_store.mark_session_complete(session.session_id) # Now should not be modifiable assert verification_store.can_modify_session(session.session_id) is False @given(verification_session_strategy()) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) def test_completed_session_persists_completion_state( self, verification_store, session ): """ **Feature: verification-mode, Property 13: Completed Sessions Cannot be Modified** **Validates: Requirements 8.4** For any completed session, when saved and reloaded, the completion state should be preserved. """ # Save the session verification_store.save_session(session) # Mark session as complete verification_store.mark_session_complete(session.session_id) # Load the session loaded_session = verification_store.load_session(session.session_id) # Verify completion state is preserved assert loaded_session.is_complete is True assert loaded_session.completed_at is not None # Verify it still cannot be modified assert verification_store.can_modify_session(session.session_id) is False