"""
Property-Based Tests for Identity Types - Agent Orchestration Platform
Comprehensive property-based testing using Hypothesis to validate type safety,
security boundaries, and correctness of all identity type operations.
Author: Adder_3 | Created: 2025-06-26 | Last Modified: 2025-06-26
"""
import pytest
from hypothesis import given, strategies as st, assume, example
from hypothesis.stateful import RuleBasedStateMachine, rule, invariant
import uuid
import re
from src.models.ids import (
AgentId, SessionId, ProcessId, ITermTabId,
create_agent_id, create_session_id, create_iterm_tab_id, create_process_id,
is_valid_agent_id, is_valid_session_id, validate_agent_name,
InvalidIdentifierError, AgentIDValidationError,
AGENT_ID_PATTERN, SESSION_ID_PATTERN, AGENT_NAME_PATTERN, ITERM_TAB_ID_PATTERN
)
class TestAgentIdProperties:
"""Property-based tests for Agent ID operations."""
def test_create_agent_id_always_valid(self):
"""Property: Generated agent IDs are always valid."""
agent_id = create_agent_id()
# Property 1: ID matches expected pattern
assert AGENT_ID_PATTERN.match(agent_id)
# Property 2: ID is valid according to validation function
assert is_valid_agent_id(agent_id)
# Property 3: ID starts with correct prefix
assert agent_id.startswith("Agent_")
# Property 4: ID contains valid number
number_part = agent_id[6:] # Remove "Agent_" prefix
assert number_part.isdigit(), f"Expected numeric part, got: {number_part}"
agent_num = int(number_part)
assert 1 <= agent_num <= 99, f"Agent number {agent_num} not in valid range [1, 99]"
@given(st.text())
def test_agent_id_validation_properties(self, test_string):
"""Property: Agent ID validation is consistent and secure."""
result = is_valid_agent_id(test_string)
# Property 1: Valid IDs match pattern
if result:
assert AGENT_ID_PATTERN.match(test_string)
assert test_string.startswith("Agent_")
# Property 2: Invalid IDs don't match pattern
if not AGENT_ID_PATTERN.match(test_string):
assert not result
# Property 3: Validation is deterministic
assert is_valid_agent_id(test_string) == result
@given(st.text(min_size=1, max_size=1000))
def test_agent_id_validation_security(self, malicious_input):
"""Property: Agent ID validation prevents security issues."""
# Property 1: No exceptions raised for any input
try:
result = is_valid_agent_id(malicious_input)
assert isinstance(result, bool)
except Exception as e:
pytest.fail(f"Validation raised exception for input: {e}")
# Property 2: Only properly formatted IDs are accepted
if is_valid_agent_id(malicious_input):
assert "Agent_" in malicious_input
# Valid agent IDs have format Agent_[1-99], so length varies from 7-9
assert 7 <= len(malicious_input) <= 9
def test_agent_id_uniqueness_property(self):
"""Property: Generated agent IDs use the full range of possible values."""
# Generate 10 IDs - should show good distribution with 99 possible values
ids = [create_agent_id() for _ in range(10)]
# Property: All values should be valid (uniqueness not guaranteed due to limited range)
for agent_id in ids:
assert agent_id.startswith("Agent_")
assert is_valid_agent_id(agent_id)
# Property: Should generate some variety within the valid range
unique_count = len(set(ids))
assert unique_count >= 5 # Very lenient - just checking we get some variation
class TestSessionIdProperties:
"""Property-based tests for Session ID operations."""
def test_create_session_id_always_valid(self):
"""Property: Generated session IDs are always valid."""
session_id = create_session_id()
# Property 1: ID matches expected pattern
assert SESSION_ID_PATTERN.match(session_id)
# Property 2: ID is valid according to validation function
assert is_valid_session_id(session_id)
# Property 3: ID starts with correct prefix
assert session_id.startswith("session_")
# Property 4: ID contains valid UUID
uuid_part = session_id[8:] # Remove "session_" prefix
uuid.UUID(uuid_part) # Should not raise exception
@given(st.text())
def test_session_id_validation_properties(self, test_string):
"""Property: Session ID validation is consistent and secure."""
result = is_valid_session_id(test_string)
# Property 1: Valid IDs match pattern
if result:
assert SESSION_ID_PATTERN.match(test_string)
assert test_string.startswith("session_")
# Property 2: Invalid IDs don't match pattern
if not SESSION_ID_PATTERN.match(test_string):
assert not result
class TestProcessIdProperties:
"""Property-based tests for Process ID operations."""
@given(st.integers(min_value=1, max_value=2**30))
def test_create_process_id_valid_range(self, process_id):
"""Property: Valid process IDs are accepted."""
result = create_process_id(process_id)
# Property 1: Result is branded ProcessId type
assert isinstance(result, int)
assert result == process_id
# Property 2: Value is preserved
assert int(result) == process_id
@given(st.integers(max_value=0))
def test_create_process_id_invalid_negative(self, invalid_pid):
"""Property: Negative process IDs are rejected."""
with pytest.raises(ValueError):
create_process_id(invalid_pid)
@given(st.integers(min_value=2**31))
def test_create_process_id_invalid_large(self, invalid_pid):
"""Property: Oversized process IDs are rejected."""
with pytest.raises(ValueError):
create_process_id(invalid_pid)
class TestAgentNameProperties:
"""Property-based tests for Agent Name validation."""
@given(st.integers(min_value=1, max_value=99))
def test_valid_agent_name_format(self, agent_number):
"""Property: Properly formatted agent names are accepted."""
agent_name = f"Agent_{agent_number}"
result = validate_agent_name(agent_name)
# Property 1: Valid names are returned unchanged
assert result == agent_name
# Property 2: Valid names match pattern
assert AGENT_NAME_PATTERN.match(result)
@given(st.text().filter(lambda x: not AGENT_NAME_PATTERN.match(x.strip())))
def test_invalid_agent_name_rejection(self, invalid_name):
"""Property: Invalid agent names are rejected."""
assume(invalid_name.strip() != "") # Avoid empty string edge case
with pytest.raises((InvalidIdentifierError, AgentIDValidationError)):
validate_agent_name(invalid_name)
@given(st.text(min_size=1).map(lambda x: f" Agent_42 {x} "))
def test_agent_name_whitespace_handling(self, padded_name):
"""Property: Agent names with whitespace are rejected."""
# The implementation doesn't strip whitespace, so any padded name should fail
with pytest.raises((InvalidIdentifierError, AgentIDValidationError)):
validate_agent_name(padded_name)
class TestITermTabIdProperties:
"""Property-based tests for iTerm Tab ID operations."""
def test_create_iterm_tab_id_always_valid(self):
"""Property: Generated iTerm tab IDs are always valid."""
tab_id = create_iterm_tab_id()
# Property 1: ID matches expected pattern
assert ITERM_TAB_ID_PATTERN.match(tab_id)
# Property 2: ID starts with correct prefix
assert tab_id.startswith("tab_")
# Property 3: ID contains valid UUID (with dashes removed)
uuid_part = tab_id[4:] # Remove "tab_" prefix
# Re-insert dashes to make valid UUID format
if len(uuid_part) == 32: # UUID without dashes
formatted_uuid = f"{uuid_part[:8]}-{uuid_part[8:12]}-{uuid_part[12:16]}-{uuid_part[16:20]}-{uuid_part[20:]}"
uuid.UUID(formatted_uuid) # Should not raise exception
class IdentifierStateMachine(RuleBasedStateMachine):
"""
Stateful property-based testing for identifier operations.
Tests complex interactions between different identifier types
and ensures system invariants are maintained.
"""
def __init__(self):
super().__init__()
self.created_agent_ids = set()
self.created_session_ids = set()
self.created_tab_ids = set()
self.agent_name_counter = 1
@rule()
def create_new_agent_id(self):
"""Rule: Create new agent ID and track it."""
agent_id = create_agent_id()
# Agent IDs use limited range (1-99) so duplicates are expected
# Just ensure the ID is valid and add to tracking set
assert is_valid_agent_id(agent_id)
self.created_agent_ids.add(agent_id)
@rule()
def create_new_session_id(self):
"""Rule: Create new session ID and track it."""
session_id = create_session_id()
# Ensure uniqueness
assert session_id not in self.created_session_ids
self.created_session_ids.add(session_id)
@rule()
def create_new_tab_id(self):
"""Rule: Create new tab ID and track it."""
tab_id = create_iterm_tab_id()
# Ensure uniqueness
assert tab_id not in self.created_tab_ids
self.created_tab_ids.add(tab_id)
@rule()
def create_agent_name(self):
"""Rule: Create valid agent name."""
agent_name = f"Agent_{self.agent_name_counter}"
self.agent_name_counter += 1
# Validate agent name
result = validate_agent_name(agent_name)
assert result == agent_name
@invariant()
def all_created_ids_are_valid(self):
"""Invariant: All created IDs remain valid."""
# Check agent IDs
for agent_id in self.created_agent_ids:
assert is_valid_agent_id(agent_id)
# Check session IDs
for session_id in self.created_session_ids:
assert is_valid_session_id(session_id)
# Check tab IDs
for tab_id in self.created_tab_ids:
assert ITERM_TAB_ID_PATTERN.match(tab_id)
@invariant()
def no_id_collision_across_types(self):
"""Invariant: No collisions between different ID types."""
all_ids = (
self.created_agent_ids |
self.created_session_ids |
self.created_tab_ids
)
# Ensure no overlaps
assert len(all_ids) == (
len(self.created_agent_ids) +
len(self.created_session_ids) +
len(self.created_tab_ids)
)
# Test the state machine
TestIdentifierStateMachine = IdentifierStateMachine.TestCase
class TestSecurityProperties:
"""Security-focused property-based tests."""
@given(st.text(min_size=1, max_size=10000))
def test_validation_functions_safe_with_malicious_input(self, malicious_input):
"""Property: Validation functions handle malicious input safely."""
# Test all validation functions with potentially malicious input
# Agent ID validation - should never raise exception
try:
result = is_valid_agent_id(malicious_input)
assert isinstance(result, bool)
except Exception as e:
pytest.fail(f"Agent ID validation failed: {e}")
# Session ID validation - should never raise exception
try:
result = is_valid_session_id(malicious_input)
assert isinstance(result, bool)
except Exception as e:
pytest.fail(f"Session ID validation failed: {e}")
@given(st.binary(min_size=0, max_size=1000))
def test_validation_with_binary_data(self, binary_data):
"""Property: Validation handles binary data safely."""
try:
# Convert to string and test
string_data = binary_data.decode('utf-8', errors='ignore')
# Should not crash
is_valid_agent_id(string_data)
is_valid_session_id(string_data)
except UnicodeDecodeError:
# Binary data that can't be decoded should be handled gracefully
pass
except Exception as e:
pytest.fail(f"Binary data handling failed: {e}")
if __name__ == "__main__":
# Run property-based tests
pytest.main([__file__, "-v"])