All checks were successful
Enterprise AI Code Review / ai-review (pull_request) Successful in 38s
301 lines
10 KiB
Python
301 lines
10 KiB
Python
"""Intimacy boundary integration tests.
|
|
|
|
Tests that intimacy levels (LOW/MEDIUM/HIGH) correctly control:
|
|
- Memory surfacing depth
|
|
- Proactive behavior frequency
|
|
- Response length and thoughtfulness
|
|
- Emotional intensity
|
|
"""
|
|
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from loyal_companion.models.platform import (
|
|
ConversationContext,
|
|
ConversationRequest,
|
|
IntimacyLevel,
|
|
Platform,
|
|
)
|
|
from loyal_companion.services.conversation_gateway import ConversationGateway
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestIntimacyLevelBehavior:
|
|
"""Test that intimacy levels control behavior appropriately."""
|
|
|
|
async def test_low_intimacy_behavior(self):
|
|
"""Test LOW intimacy (Discord guild) behavior constraints."""
|
|
# Setup
|
|
request = ConversationRequest(
|
|
user_id="test_user_123",
|
|
platform=Platform.DISCORD,
|
|
session_id="guild_channel_456",
|
|
message="How are you today?",
|
|
context=ConversationContext(
|
|
is_public=True,
|
|
intimacy_level=IntimacyLevel.LOW,
|
|
guild_id="guild_123",
|
|
channel_id="channel_456",
|
|
),
|
|
)
|
|
|
|
# Expected behaviors for LOW intimacy:
|
|
# - Brief responses
|
|
# - No personal memory surfacing
|
|
# - No proactive follow-ups
|
|
# - Light, casual tone
|
|
# - Public-safe topics only
|
|
|
|
assert request.context.intimacy_level == IntimacyLevel.LOW
|
|
assert request.context.is_public == True
|
|
|
|
async def test_medium_intimacy_behavior(self):
|
|
"""Test MEDIUM intimacy (Discord DM) behavior constraints."""
|
|
request = ConversationRequest(
|
|
user_id="test_user_123",
|
|
platform=Platform.DISCORD,
|
|
session_id="dm_channel_789",
|
|
message="I've been feeling stressed lately",
|
|
context=ConversationContext(
|
|
is_public=False,
|
|
intimacy_level=IntimacyLevel.MEDIUM,
|
|
channel_id="dm_789",
|
|
),
|
|
)
|
|
|
|
# Expected behaviors for MEDIUM intimacy:
|
|
# - Balanced warmth
|
|
# - Personal memory allowed
|
|
# - Moderate proactive behavior
|
|
# - Normal response length
|
|
|
|
assert request.context.intimacy_level == IntimacyLevel.MEDIUM
|
|
assert request.context.is_public == False
|
|
|
|
async def test_high_intimacy_behavior(self):
|
|
"""Test HIGH intimacy (Web/CLI) behavior allowances."""
|
|
request = ConversationRequest(
|
|
user_id="alice@example.com",
|
|
platform=Platform.WEB,
|
|
session_id="web_session_abc",
|
|
message="I've been thinking about what we talked about yesterday",
|
|
context=ConversationContext(
|
|
is_public=False,
|
|
intimacy_level=IntimacyLevel.HIGH,
|
|
),
|
|
)
|
|
|
|
# Expected behaviors for HIGH intimacy:
|
|
# - Deep reflection permitted
|
|
# - Silence tolerance
|
|
# - Proactive follow-ups allowed
|
|
# - Deep memory surfacing
|
|
# - Longer, thoughtful responses
|
|
# - Emotional naming encouraged
|
|
|
|
assert request.context.intimacy_level == IntimacyLevel.HIGH
|
|
assert request.context.is_public == False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestMemorySurfacing:
|
|
"""Test that memory surfacing respects intimacy levels."""
|
|
|
|
async def test_low_intimacy_no_personal_memory(self):
|
|
"""Test that LOW intimacy doesn't surface personal memories."""
|
|
# Scenario: User in Discord guild has personal facts stored
|
|
# These should NOT be mentioned in public guild chat
|
|
|
|
user_facts = [
|
|
"User mentioned feeling anxious in crowded places",
|
|
"User's mother is visiting next week",
|
|
"User is recovering from a breakup",
|
|
]
|
|
|
|
# In LOW intimacy context, these facts should be filtered out
|
|
# System prompt should not include personal facts for public contexts
|
|
|
|
# This test would verify that get_relevant_facts() or similar
|
|
# filters based on is_public=True
|
|
pass # Integration test placeholder
|
|
|
|
async def test_medium_intimacy_allows_personal_memory(self):
|
|
"""Test that MEDIUM intimacy allows personal memory surfacing."""
|
|
# In Discord DM, personal facts can be surfaced
|
|
user_facts = [
|
|
"User mentioned feeling anxious in crowded places",
|
|
"User enjoys hiking on weekends",
|
|
]
|
|
|
|
# These CAN be referenced in MEDIUM intimacy
|
|
pass # Integration test placeholder
|
|
|
|
async def test_high_intimacy_deep_memory_surfacing(self):
|
|
"""Test that HIGH intimacy allows deep memory surfacing."""
|
|
# On Web/CLI, can surface deeper, more personal memories
|
|
user_facts = [
|
|
"User mentioned feeling lonely at night",
|
|
"User is processing grief from losing a friend",
|
|
"User finds comfort in quiet, early mornings",
|
|
]
|
|
|
|
# These deeper facts are appropriate for HIGH intimacy
|
|
pass # Integration test placeholder
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestProactiveBehavior:
|
|
"""Test that proactive behavior is filtered by intimacy level."""
|
|
|
|
async def test_low_intimacy_no_proactive_followup(self):
|
|
"""Test that LOW intimacy prevents proactive follow-ups."""
|
|
# In Discord guild, bot should NOT do proactive check-ins
|
|
# No scheduled follow-up events should be created
|
|
|
|
context = ConversationContext(
|
|
is_public=True,
|
|
intimacy_level=IntimacyLevel.LOW,
|
|
)
|
|
|
|
# Verify proactive service doesn't schedule events for LOW intimacy
|
|
pass # Integration test placeholder
|
|
|
|
async def test_medium_intimacy_moderate_proactive(self):
|
|
"""Test that MEDIUM intimacy allows moderate proactive behavior."""
|
|
context = ConversationContext(
|
|
is_public=False,
|
|
intimacy_level=IntimacyLevel.MEDIUM,
|
|
)
|
|
|
|
# Some proactive behavior OK but limited
|
|
pass # Integration test placeholder
|
|
|
|
async def test_high_intimacy_full_proactive(self):
|
|
"""Test that HIGH intimacy allows full proactive behavior."""
|
|
context = ConversationContext(
|
|
is_public=False,
|
|
intimacy_level=IntimacyLevel.HIGH,
|
|
)
|
|
|
|
# Full proactive follow-ups allowed
|
|
# "You mentioned feeling stuck yesterday—how's that today?"
|
|
pass # Integration test placeholder
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestResponseCharacteristics:
|
|
"""Test that response characteristics match intimacy level."""
|
|
|
|
async def test_low_intimacy_short_responses(self):
|
|
"""Test that LOW intimacy produces shorter responses."""
|
|
# Guild chat should be brief, light
|
|
# Max ~50-100 words typically
|
|
pass # Integration test placeholder
|
|
|
|
async def test_medium_intimacy_balanced_length(self):
|
|
"""Test that MEDIUM intimacy produces balanced responses."""
|
|
# DM can be more thoughtful but not overly long
|
|
# ~100-200 words reasonable
|
|
pass # Integration test placeholder
|
|
|
|
async def test_high_intimacy_allows_depth(self):
|
|
"""Test that HIGH intimacy allows longer, deeper responses."""
|
|
# Web/CLI can have thoughtful, reflective responses
|
|
# Length driven by content, not arbitrary limit
|
|
pass # Integration test placeholder
|
|
|
|
async def test_emotional_intensity_scaled(self):
|
|
"""Test that emotional intensity is scaled by intimacy."""
|
|
# LOW: Minimal emotional language, grounded
|
|
# MEDIUM: Moderate emotional validation
|
|
# HIGH: Can name emotions, deeper reflection
|
|
pass # Integration test placeholder
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestCrossPlatformConsistency:
|
|
"""Test that platform differences are appropriate and consistent."""
|
|
|
|
async def test_same_user_different_platforms_same_memories(self):
|
|
"""Test that user memories are shared across platforms."""
|
|
# User alice@example.com on Web is linked to Discord ID 123456
|
|
# Fact learned on Web should be available on Discord (if appropriate intimacy)
|
|
pass # Integration test placeholder
|
|
|
|
async def test_intimacy_level_determines_memory_surfacing(self):
|
|
"""Test that intimacy (not platform) determines what memories surface."""
|
|
# Same fact, different intimacy levels:
|
|
# LOW: Don't mention
|
|
# MEDIUM: Can mention
|
|
# HIGH: Can mention with depth
|
|
pass # Integration test placeholder
|
|
|
|
async def test_platform_metadata_preserved(self):
|
|
"""Test that platform-specific context is preserved."""
|
|
# Discord: guild_id, channel_id, mentioned users
|
|
# Web: session info
|
|
# CLI: session name
|
|
pass # Integration test placeholder
|
|
|
|
|
|
class TestIntimacyLevelAssignment:
|
|
"""Test that platforms correctly assign intimacy levels."""
|
|
|
|
def test_discord_guild_assigns_low(self):
|
|
"""Test that Discord guild channels assign LOW intimacy."""
|
|
# Discord adapter should detect guild context and set LOW
|
|
is_guild = True
|
|
is_dm = False
|
|
|
|
expected_intimacy = IntimacyLevel.LOW if is_guild else IntimacyLevel.MEDIUM
|
|
assert expected_intimacy == IntimacyLevel.LOW
|
|
|
|
def test_discord_dm_assigns_medium(self):
|
|
"""Test that Discord DMs assign MEDIUM intimacy."""
|
|
is_dm = True
|
|
is_guild = False
|
|
|
|
expected_intimacy = IntimacyLevel.MEDIUM if is_dm else IntimacyLevel.LOW
|
|
assert expected_intimacy == IntimacyLevel.MEDIUM
|
|
|
|
def test_web_assigns_high(self):
|
|
"""Test that Web platform assigns HIGH intimacy."""
|
|
platform = Platform.WEB
|
|
expected_intimacy = IntimacyLevel.HIGH
|
|
|
|
assert expected_intimacy == IntimacyLevel.HIGH
|
|
|
|
def test_cli_assigns_high(self):
|
|
"""Test that CLI platform assigns HIGH intimacy."""
|
|
platform = Platform.CLI
|
|
expected_intimacy = IntimacyLevel.HIGH
|
|
|
|
assert expected_intimacy == IntimacyLevel.HIGH
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestBoundaryEnforcement:
|
|
"""Test that boundaries are enforced even at HIGH intimacy."""
|
|
|
|
async def test_high_intimacy_still_enforces_safety(self):
|
|
"""Test that HIGH intimacy still enforces safety boundaries."""
|
|
# Even at HIGH intimacy:
|
|
# - No exclusivity claims
|
|
# - No dependency reinforcement
|
|
# - Crisis deferral
|
|
# - No romantic framing
|
|
|
|
context = ConversationContext(
|
|
is_public=False,
|
|
intimacy_level=IntimacyLevel.HIGH,
|
|
)
|
|
|
|
# Safety boundaries are ALWAYS enforced
|
|
# Intimacy only affects warmth/depth, not safety
|
|
pass # Integration test placeholder
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|