feat: implement comprehensive User Profile Management System (issue #107)
Complete user profile management system with CRUD operations and CLI integration: ## 🎯 Core Features Delivered - **ProfileManager**: Complete CRUD operations with database integration - **JSON Schema validation**: Comprehensive profile data validation - **Multiple profile support**: Named profiles (personal, work, etc.) - **Default profile system**: Set and manage default profiles - **Profile inheritance**: Merge profiles with override capabilities - **Template integration**: Extract flattened variables for template filling ## 📋 Profile Schema & Data Model - **Structured data classes**: ProfileData, ContactInfo, Address, Organization - **JSON Schema validation**: Full validation with field descriptions - **Flexible structure**: Support for nested data and custom fields - **Timestamp management**: Automatic created_at/updated_at tracking ## 🖥️ CLI Integration Complete - **9 CLI Commands**: create, show, list, update, delete, set-default, export, import, variables - **Multiple formats**: JSON, YAML, and table output formats - **Interactive mode**: Guided profile creation and updates - **Export/Import**: Full profile portability with validation - **Template variables**: Extract flattened variables for template systems ## 📊 Implementation Stats - **ProfileManager**: 500+ lines with comprehensive functionality - **ProfileSchema**: 350+ lines with validation and data structures - **CLI Commands**: 450+ lines of professional command interface - **Test Coverage**: 66 tests (36 core + 30 CLI) with 100% pass rate ## 🚀 **Ready for Template Integration** Foundation complete for Issue #99 (Auto Fill Templates) with: - Template variable extraction from profiles - Default profile system for seamless integration - Profile merging for complex template scenarios - Professional CLI for user profile management 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
621
tests/test_profile_cli_commands.py
Normal file
621
tests/test_profile_cli_commands.py
Normal file
@@ -0,0 +1,621 @@
|
||||
"""
|
||||
Tests for MarkiTect user profile CLI commands.
|
||||
|
||||
This module tests the command-line interface for user profile management
|
||||
including creation, listing, updating, and template variable extraction.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import tempfile
|
||||
import os
|
||||
import json
|
||||
from click.testing import CliRunner
|
||||
from pathlib import Path
|
||||
|
||||
from markitect.profile.commands import profile_commands
|
||||
from markitect.profile.manager import ProfileManager
|
||||
from markitect.profile.schema import ProfileData, ContactInfo, Organization
|
||||
|
||||
|
||||
class TestProfileCLICommands:
|
||||
"""Test suite for profile management CLI commands."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_db(self):
|
||||
"""Create temporary database for testing."""
|
||||
fd, path = tempfile.mkstemp(suffix='.db')
|
||||
os.close(fd)
|
||||
yield path
|
||||
os.unlink(path)
|
||||
|
||||
@pytest.fixture
|
||||
def setup_test_profile(self, temp_db):
|
||||
"""Setup test database with a sample profile."""
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
profile_data = ProfileData(
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
contact=ContactInfo(email="john@example.com", phone="555-0123"),
|
||||
organization=Organization(name="Tech Corp", position="Developer")
|
||||
)
|
||||
profile_id = profile_manager.create_profile("test_profile", profile_data, "Test profile")
|
||||
return temp_db, profile_id
|
||||
|
||||
@pytest.fixture
|
||||
def runner(self):
|
||||
"""Create Click test runner."""
|
||||
return CliRunner()
|
||||
|
||||
def test_profile_create_basic(self, runner, temp_db):
|
||||
"""Test basic profile creation."""
|
||||
result = runner.invoke(profile_commands, [
|
||||
'create', 'basic_profile',
|
||||
'--first-name', 'Alice',
|
||||
'--last-name', 'Smith',
|
||||
'--email', 'alice@example.com',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "✅ Created profile 'basic_profile'" in result.output
|
||||
|
||||
# Verify profile was created
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
profile = profile_manager.get_profile('basic_profile')
|
||||
assert profile.first_name == "Alice"
|
||||
assert profile.contact.email == "alice@example.com"
|
||||
|
||||
def test_profile_create_with_organization(self, runner, temp_db):
|
||||
"""Test profile creation with organization info."""
|
||||
result = runner.invoke(profile_commands, [
|
||||
'create', 'work_profile',
|
||||
'--first-name', 'Bob',
|
||||
'--organization', 'ACME Corp',
|
||||
'--position', 'Manager',
|
||||
'--city', 'New York',
|
||||
'--description', 'My work profile',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "✅ Created profile 'work_profile'" in result.output
|
||||
|
||||
# Verify organization details
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
profile = profile_manager.get_profile('work_profile')
|
||||
assert profile.organization.name == "ACME Corp"
|
||||
assert profile.organization.position == "Manager"
|
||||
assert profile.address.city == "New York"
|
||||
|
||||
def test_profile_create_set_default(self, runner, temp_db):
|
||||
"""Test creating profile and setting as default."""
|
||||
result = runner.invoke(profile_commands, [
|
||||
'create', 'default_profile',
|
||||
'--first-name', 'Default',
|
||||
'--set-default',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "✅ Created profile 'default_profile'" in result.output
|
||||
assert "🎯 Set 'default_profile' as default profile" in result.output
|
||||
|
||||
# Verify it's set as default
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
default_profile = profile_manager.get_default_profile()
|
||||
assert default_profile.first_name == "Default"
|
||||
|
||||
def test_profile_create_duplicate_name(self, runner, setup_test_profile):
|
||||
"""Test creating profile with duplicate name fails."""
|
||||
temp_db, _ = setup_test_profile
|
||||
|
||||
result = runner.invoke(profile_commands, [
|
||||
'create', 'test_profile', # Same name as existing
|
||||
'--first-name', 'Duplicate',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "already exists" in result.output
|
||||
|
||||
def test_profile_show_table_format(self, runner, setup_test_profile):
|
||||
"""Test showing profile in table format."""
|
||||
temp_db, _ = setup_test_profile
|
||||
|
||||
result = runner.invoke(profile_commands, [
|
||||
'show', 'test_profile',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "👤 Profile: test_profile" in result.output
|
||||
assert "First Name: John" in result.output
|
||||
assert "📞 Contact Information" in result.output
|
||||
assert "Email: john@example.com" in result.output
|
||||
assert "🏢 Organization" in result.output
|
||||
assert "Organization: Tech Corp" in result.output
|
||||
|
||||
def test_profile_show_json_format(self, runner, setup_test_profile):
|
||||
"""Test showing profile in JSON format."""
|
||||
temp_db, _ = setup_test_profile
|
||||
|
||||
result = runner.invoke(profile_commands, [
|
||||
'show', 'test_profile',
|
||||
'--format', 'json',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Parse JSON output
|
||||
data = json.loads(result.output)
|
||||
assert "profile_info" in data
|
||||
assert "profile_data" in data
|
||||
assert data["profile_info"]["name"] == "test_profile"
|
||||
assert data["profile_data"]["first_name"] == "John"
|
||||
|
||||
def test_profile_show_nonexistent(self, runner, temp_db):
|
||||
"""Test showing non-existent profile."""
|
||||
result = runner.invoke(profile_commands, [
|
||||
'show', 'nonexistent',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "not found" in result.output
|
||||
|
||||
def test_profile_list_empty(self, runner, temp_db):
|
||||
"""Test listing profiles when none exist."""
|
||||
result = runner.invoke(profile_commands, [
|
||||
'list',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "No profiles found" in result.output
|
||||
|
||||
def test_profile_list_with_profiles(self, runner, temp_db):
|
||||
"""Test listing profiles."""
|
||||
# Create multiple profiles
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
profile_manager.create_profile("profile1", ProfileData(first_name="User1"), "First profile")
|
||||
profile_manager.create_profile("profile2", ProfileData(first_name="User2"), set_as_default=True)
|
||||
|
||||
result = runner.invoke(profile_commands, [
|
||||
'list',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "👤 User Profiles" in result.output
|
||||
assert "profile1" in result.output
|
||||
assert "profile2" in result.output
|
||||
assert "Total: 2 profiles" in result.output
|
||||
|
||||
# Check default indicator
|
||||
lines = result.output.split('\n')
|
||||
profile2_line = [line for line in lines if 'profile2' in line][0]
|
||||
assert "Yes" in profile2_line # Default column should show "Yes"
|
||||
|
||||
def test_profile_list_include_inactive(self, runner, temp_db):
|
||||
"""Test listing profiles including inactive ones."""
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
profile_manager.create_profile("active", ProfileData(first_name="Active"))
|
||||
profile_manager.create_profile("inactive", ProfileData(first_name="Inactive"))
|
||||
profile_manager.delete_profile("inactive", hard_delete=False) # Soft delete
|
||||
|
||||
# List without inactive
|
||||
result = runner.invoke(profile_commands, [
|
||||
'list',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "active" in result.output
|
||||
assert "inactive" not in result.output
|
||||
|
||||
# List with inactive
|
||||
result = runner.invoke(profile_commands, [
|
||||
'list',
|
||||
'--include-inactive',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "active" in result.output
|
||||
assert "inactive" in result.output
|
||||
|
||||
def test_profile_list_json_format(self, runner, setup_test_profile):
|
||||
"""Test listing profiles in JSON format."""
|
||||
temp_db, _ = setup_test_profile
|
||||
|
||||
result = runner.invoke(profile_commands, [
|
||||
'list',
|
||||
'--format', 'json',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Parse JSON output
|
||||
data = json.loads(result.output)
|
||||
assert isinstance(data, list)
|
||||
assert len(data) == 1
|
||||
assert data[0]["name"] == "test_profile"
|
||||
|
||||
def test_profile_update_basic_fields(self, runner, setup_test_profile):
|
||||
"""Test updating basic profile fields."""
|
||||
temp_db, _ = setup_test_profile
|
||||
|
||||
result = runner.invoke(profile_commands, [
|
||||
'update', 'test_profile',
|
||||
'--first-name', 'Johnny',
|
||||
'--email', 'johnny@example.com',
|
||||
'--organization', 'New Corp',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "✅ Updated profile 'test_profile'" in result.output
|
||||
|
||||
# Verify updates
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
profile = profile_manager.get_profile('test_profile')
|
||||
assert profile.first_name == "Johnny"
|
||||
assert profile.contact.email == "johnny@example.com"
|
||||
assert profile.organization.name == "New Corp"
|
||||
|
||||
def test_profile_update_nonexistent(self, runner, temp_db):
|
||||
"""Test updating non-existent profile."""
|
||||
result = runner.invoke(profile_commands, [
|
||||
'update', 'nonexistent',
|
||||
'--first-name', 'New',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "not found" in result.output
|
||||
|
||||
def test_profile_delete_soft(self, runner, setup_test_profile):
|
||||
"""Test soft delete (deactivate) profile."""
|
||||
temp_db, _ = setup_test_profile
|
||||
|
||||
result = runner.invoke(profile_commands, [
|
||||
'delete', 'test_profile',
|
||||
'--yes', # Skip confirmation
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "✅ Profile 'test_profile' deactivated" in result.output
|
||||
|
||||
# Verify profile is deactivated
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
with pytest.raises(Exception): # ProfileNotFoundError
|
||||
profile_manager.get_profile('test_profile')
|
||||
|
||||
def test_profile_delete_hard(self, runner, setup_test_profile):
|
||||
"""Test hard delete (permanent) profile."""
|
||||
temp_db, _ = setup_test_profile
|
||||
|
||||
result = runner.invoke(profile_commands, [
|
||||
'delete', 'test_profile',
|
||||
'--hard',
|
||||
'--yes',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "✅ Profile 'test_profile' deleted permanently" in result.output
|
||||
|
||||
# Verify profile is completely gone
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
all_profiles = profile_manager.list_profiles(include_inactive=True)
|
||||
profile_names = [p["name"] for p in all_profiles]
|
||||
assert "test_profile" not in profile_names
|
||||
|
||||
def test_profile_delete_with_confirmation(self, runner, setup_test_profile):
|
||||
"""Test profile deletion with confirmation prompt."""
|
||||
temp_db, _ = setup_test_profile
|
||||
|
||||
# Test declining confirmation
|
||||
result = runner.invoke(profile_commands, [
|
||||
'delete', 'test_profile',
|
||||
'--database', temp_db
|
||||
], input='n\n')
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Deletion cancelled" in result.output
|
||||
|
||||
# Profile should still exist
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
profile = profile_manager.get_profile('test_profile')
|
||||
assert profile.first_name == "John"
|
||||
|
||||
def test_profile_set_default(self, runner, temp_db):
|
||||
"""Test setting profile as default."""
|
||||
# Create multiple profiles
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
profile_manager.create_profile("profile1", ProfileData(first_name="User1"))
|
||||
profile_manager.create_profile("profile2", ProfileData(first_name="User2"))
|
||||
|
||||
result = runner.invoke(profile_commands, [
|
||||
'set-default', 'profile2',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "✅ Set 'profile2' as default profile" in result.output
|
||||
|
||||
# Verify default was set
|
||||
default_profile = profile_manager.get_default_profile()
|
||||
assert default_profile.first_name == "User2"
|
||||
|
||||
def test_profile_set_default_nonexistent(self, runner, temp_db):
|
||||
"""Test setting non-existent profile as default."""
|
||||
result = runner.invoke(profile_commands, [
|
||||
'set-default', 'nonexistent',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "not found" in result.output
|
||||
|
||||
def test_profile_export_json(self, runner, setup_test_profile):
|
||||
"""Test exporting profile to JSON."""
|
||||
temp_db, _ = setup_test_profile
|
||||
|
||||
result = runner.invoke(profile_commands, [
|
||||
'export', 'test_profile',
|
||||
'--format', 'json',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Parse output as JSON
|
||||
data = json.loads(result.output)
|
||||
assert "profile_info" in data
|
||||
assert "profile_data" in data
|
||||
assert data["profile_data"]["first_name"] == "John"
|
||||
|
||||
def test_profile_export_to_file(self, runner, setup_test_profile):
|
||||
"""Test exporting profile to file."""
|
||||
temp_db, _ = setup_test_profile
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as f:
|
||||
output_file = f.name
|
||||
|
||||
try:
|
||||
result = runner.invoke(profile_commands, [
|
||||
'export', 'test_profile',
|
||||
'--output', output_file,
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert f"✅ Exported profile 'test_profile' to {output_file}" in result.output
|
||||
|
||||
# Verify file contents
|
||||
data = json.loads(Path(output_file).read_text())
|
||||
assert data["profile_data"]["first_name"] == "John"
|
||||
|
||||
finally:
|
||||
os.unlink(output_file)
|
||||
|
||||
def test_profile_import_json(self, runner, temp_db):
|
||||
"""Test importing profile from JSON file."""
|
||||
import_data = {
|
||||
"profile_info": {"description": "Imported profile"},
|
||||
"profile_data": {
|
||||
"first_name": "Imported",
|
||||
"last_name": "User",
|
||||
"contact": {"email": "imported@example.com"}
|
||||
}
|
||||
}
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as f:
|
||||
json.dump(import_data, f)
|
||||
import_file = f.name
|
||||
|
||||
try:
|
||||
result = runner.invoke(profile_commands, [
|
||||
'import', 'imported_profile', import_file,
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "✅ Created profile 'imported_profile'" in result.output
|
||||
|
||||
# Verify imported data
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
profile = profile_manager.get_profile('imported_profile')
|
||||
assert profile.first_name == "Imported"
|
||||
assert profile.contact.email == "imported@example.com"
|
||||
|
||||
finally:
|
||||
os.unlink(import_file)
|
||||
|
||||
def test_profile_import_nonexistent_file(self, runner, temp_db):
|
||||
"""Test importing from non-existent file."""
|
||||
result = runner.invoke(profile_commands, [
|
||||
'import', 'test', '/nonexistent/file.json',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "not found" in result.output
|
||||
|
||||
def test_profile_import_overwrite(self, runner, setup_test_profile):
|
||||
"""Test importing with overwrite flag."""
|
||||
temp_db, _ = setup_test_profile
|
||||
|
||||
import_data = {
|
||||
"profile_data": {
|
||||
"first_name": "Overwritten",
|
||||
"contact": {"email": "overwritten@example.com"}
|
||||
}
|
||||
}
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as f:
|
||||
json.dump(import_data, f)
|
||||
import_file = f.name
|
||||
|
||||
try:
|
||||
result = runner.invoke(profile_commands, [
|
||||
'import', 'test_profile', import_file,
|
||||
'--overwrite',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "✅ Updated profile 'test_profile'" in result.output
|
||||
|
||||
# Verify overwrite
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
profile = profile_manager.get_profile('test_profile')
|
||||
assert profile.first_name == "Overwritten"
|
||||
|
||||
finally:
|
||||
os.unlink(import_file)
|
||||
|
||||
def test_profile_variables_table_format(self, runner, setup_test_profile):
|
||||
"""Test showing template variables in table format."""
|
||||
temp_db, _ = setup_test_profile
|
||||
|
||||
result = runner.invoke(profile_commands, [
|
||||
'variables',
|
||||
'--profile', 'test_profile',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "📋 Template Variables - test_profile" in result.output
|
||||
assert "first_name" in result.output
|
||||
assert "John" in result.output
|
||||
assert "contact.email" in result.output
|
||||
assert "organization.name" in result.output
|
||||
|
||||
def test_profile_variables_json_format(self, runner, setup_test_profile):
|
||||
"""Test showing template variables in JSON format."""
|
||||
temp_db, _ = setup_test_profile
|
||||
|
||||
result = runner.invoke(profile_commands, [
|
||||
'variables',
|
||||
'--profile', 'test_profile',
|
||||
'--format', 'json',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Parse JSON output
|
||||
data = json.loads(result.output)
|
||||
assert data["first_name"] == "John"
|
||||
assert data["contact.email"] == "john@example.com"
|
||||
assert data["organization.name"] == "Tech Corp"
|
||||
|
||||
def test_profile_variables_default_profile(self, runner, temp_db):
|
||||
"""Test showing variables from default profile."""
|
||||
# Create and set default profile
|
||||
profile_manager = ProfileManager(temp_db)
|
||||
profile_data = ProfileData(first_name="Default", last_name="User")
|
||||
profile_manager.create_profile("default", profile_data, set_as_default=True)
|
||||
|
||||
result = runner.invoke(profile_commands, [
|
||||
'variables', # No --profile specified, should use default
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "📋 Template Variables - (default)" in result.output
|
||||
assert "first_name" in result.output
|
||||
assert "Default" in result.output
|
||||
|
||||
def test_profile_variables_no_default(self, runner, temp_db):
|
||||
"""Test showing variables when no default profile set."""
|
||||
result = runner.invoke(profile_commands, [
|
||||
'variables',
|
||||
'--database', temp_db
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "No default profile set" in result.output
|
||||
|
||||
def test_profile_help_commands(self, runner):
|
||||
"""Test help output for profile commands."""
|
||||
# Test main profile help
|
||||
result = runner.invoke(profile_commands, ['--help'])
|
||||
assert result.exit_code == 0
|
||||
assert "User profile management commands" in result.output
|
||||
|
||||
# Test create help
|
||||
result = runner.invoke(profile_commands, ['create', '--help'])
|
||||
assert result.exit_code == 0
|
||||
assert "Create a new user profile" in result.output
|
||||
|
||||
# Test show help
|
||||
result = runner.invoke(profile_commands, ['show', '--help'])
|
||||
assert result.exit_code == 0
|
||||
assert "Show profile details" in result.output
|
||||
|
||||
def test_profile_commands_missing_database(self, runner):
|
||||
"""Test profile commands without database specification."""
|
||||
# These should use default config path
|
||||
result = runner.invoke(profile_commands, [
|
||||
'list'
|
||||
])
|
||||
|
||||
# Should succeed with default database configuration
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_complex_profile_workflow(self, runner, temp_db):
|
||||
"""Test complex workflow with multiple operations."""
|
||||
# Create profile
|
||||
result = runner.invoke(profile_commands, [
|
||||
'create', 'workflow_test',
|
||||
'--first-name', 'Workflow',
|
||||
'--last-name', 'Test',
|
||||
'--email', 'workflow@example.com',
|
||||
'--organization', 'Test Corp',
|
||||
'--description', 'Workflow test profile',
|
||||
'--database', temp_db
|
||||
])
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Update profile
|
||||
result = runner.invoke(profile_commands, [
|
||||
'update', 'workflow_test',
|
||||
'--first-name', 'Updated',
|
||||
'--position', 'Manager',
|
||||
'--database', temp_db
|
||||
])
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Set as default
|
||||
result = runner.invoke(profile_commands, [
|
||||
'set-default', 'workflow_test',
|
||||
'--database', temp_db
|
||||
])
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Show variables
|
||||
result = runner.invoke(profile_commands, [
|
||||
'variables',
|
||||
'--database', temp_db
|
||||
])
|
||||
assert result.exit_code == 0
|
||||
assert "Updated" in result.output # Updated name
|
||||
assert "Manager" in result.output # New position
|
||||
|
||||
# Export profile
|
||||
result = runner.invoke(profile_commands, [
|
||||
'export', 'workflow_test',
|
||||
'--database', temp_db
|
||||
])
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Verify export contains updates
|
||||
data = json.loads(result.output)
|
||||
assert data["profile_data"]["first_name"] == "Updated"
|
||||
assert data["profile_data"]["organization"]["position"] == "Manager"
|
||||
568
tests/test_profile_manager.py
Normal file
568
tests/test_profile_manager.py
Normal file
@@ -0,0 +1,568 @@
|
||||
"""
|
||||
Tests for MarkiTect User Profile Management System.
|
||||
|
||||
This module tests the complete user profile management functionality including:
|
||||
- CRUD operations for user profiles
|
||||
- Profile validation and schema compliance
|
||||
- Database integration and data persistence
|
||||
- Profile inheritance and merging
|
||||
- Template variable extraction
|
||||
- Export/import functionality
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import tempfile
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from markitect.profile.manager import ProfileManager, ProfileNotFoundError, ProfileValidationError
|
||||
from markitect.profile.schema import ProfileSchema, ProfileData, ContactInfo, Address, Organization
|
||||
|
||||
|
||||
class TestProfileSchema:
|
||||
"""Test suite for profile schema and validation."""
|
||||
|
||||
def test_profile_data_creation(self):
|
||||
"""Test ProfileData dataclass creation."""
|
||||
profile = ProfileData(
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
contact=ContactInfo(email="john@example.com"),
|
||||
address=Address(city="Boston", country="USA")
|
||||
)
|
||||
|
||||
assert profile.first_name == "John"
|
||||
assert profile.last_name == "Doe"
|
||||
assert profile.contact.email == "john@example.com"
|
||||
assert profile.address.city == "Boston"
|
||||
|
||||
def test_profile_data_to_dict(self):
|
||||
"""Test converting ProfileData to dictionary."""
|
||||
profile = ProfileData(
|
||||
first_name="Jane",
|
||||
last_name="Smith",
|
||||
contact=ContactInfo(email="jane@example.com", phone="123-456-7890")
|
||||
)
|
||||
|
||||
profile_dict = profile.to_dict()
|
||||
|
||||
assert profile_dict["first_name"] == "Jane"
|
||||
assert profile_dict["last_name"] == "Smith"
|
||||
assert profile_dict["contact"]["email"] == "jane@example.com"
|
||||
assert profile_dict["contact"]["phone"] == "123-456-7890"
|
||||
|
||||
def test_profile_data_from_dict(self):
|
||||
"""Test creating ProfileData from dictionary."""
|
||||
data = {
|
||||
"first_name": "Bob",
|
||||
"last_name": "Johnson",
|
||||
"contact": {
|
||||
"email": "bob@example.com",
|
||||
"phone": "098-765-4321"
|
||||
},
|
||||
"organization": {
|
||||
"name": "ACME Corp",
|
||||
"position": "Developer"
|
||||
}
|
||||
}
|
||||
|
||||
profile = ProfileData.from_dict(data)
|
||||
|
||||
assert profile.first_name == "Bob"
|
||||
assert profile.contact.email == "bob@example.com"
|
||||
assert profile.organization.name == "ACME Corp"
|
||||
|
||||
def test_profile_schema_validation_success(self):
|
||||
"""Test successful profile schema validation."""
|
||||
valid_data = {
|
||||
"first_name": "Alice",
|
||||
"last_name": "Wilson",
|
||||
"contact": {
|
||||
"email": "alice@example.com"
|
||||
},
|
||||
"address": {
|
||||
"city": "New York",
|
||||
"country": "USA"
|
||||
}
|
||||
}
|
||||
|
||||
# Should not raise exception
|
||||
ProfileSchema.validate(valid_data)
|
||||
assert ProfileSchema.is_valid(valid_data) is True
|
||||
|
||||
def test_profile_schema_validation_failure(self):
|
||||
"""Test profile schema validation with invalid data."""
|
||||
invalid_data = {
|
||||
"first_name": "A" * 150, # Too long
|
||||
"contact": {
|
||||
"email": "invalid-email" # Invalid email format
|
||||
}
|
||||
}
|
||||
|
||||
with pytest.raises(Exception): # ValidationError
|
||||
ProfileSchema.validate(invalid_data)
|
||||
|
||||
assert ProfileSchema.is_valid(invalid_data) is False
|
||||
|
||||
def test_profile_schema_get_field_description(self):
|
||||
"""Test getting field descriptions from schema."""
|
||||
email_desc = ProfileSchema.get_field_description("contact.email")
|
||||
assert "Email address" in email_desc
|
||||
|
||||
name_desc = ProfileSchema.get_field_description("first_name")
|
||||
assert "first name" in name_desc.lower()
|
||||
|
||||
invalid_desc = ProfileSchema.get_field_description("nonexistent.field")
|
||||
assert invalid_desc is None
|
||||
|
||||
def test_profile_schema_get_all_fields(self):
|
||||
"""Test getting all available field paths."""
|
||||
fields = ProfileSchema.get_all_fields()
|
||||
|
||||
assert "first_name" in fields
|
||||
assert "contact.email" in fields
|
||||
assert "organization.name" in fields
|
||||
assert "address.city" in fields
|
||||
assert len(fields) > 10 # Should have many fields
|
||||
|
||||
def test_create_empty_profile(self):
|
||||
"""Test creating empty profile with timestamps."""
|
||||
profile = ProfileSchema.create_empty_profile()
|
||||
|
||||
assert profile.first_name is None
|
||||
assert profile.contact is not None
|
||||
assert profile.created_at is not None
|
||||
assert profile.updated_at is not None
|
||||
|
||||
|
||||
class TestProfileManager:
|
||||
"""Test suite for profile manager functionality."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_db(self):
|
||||
"""Create temporary database for testing."""
|
||||
fd, path = tempfile.mkstemp(suffix='.db')
|
||||
os.close(fd)
|
||||
yield path
|
||||
os.unlink(path)
|
||||
|
||||
@pytest.fixture
|
||||
def profile_manager(self, temp_db):
|
||||
"""Create profile manager with temporary database."""
|
||||
return ProfileManager(temp_db)
|
||||
|
||||
@pytest.fixture
|
||||
def sample_profile_data(self):
|
||||
"""Sample profile data for testing."""
|
||||
return ProfileData(
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
full_name="John Doe",
|
||||
contact=ContactInfo(
|
||||
email="john.doe@example.com",
|
||||
phone="555-0123"
|
||||
),
|
||||
organization=Organization(
|
||||
name="Tech Corp",
|
||||
position="Senior Developer"
|
||||
),
|
||||
address=Address(
|
||||
city="San Francisco",
|
||||
state="CA",
|
||||
country="USA"
|
||||
)
|
||||
)
|
||||
|
||||
def test_create_profile_success(self, profile_manager, sample_profile_data):
|
||||
"""Test successful profile creation."""
|
||||
profile_id = profile_manager.create_profile(
|
||||
name="personal",
|
||||
data=sample_profile_data,
|
||||
description="My personal profile"
|
||||
)
|
||||
|
||||
assert profile_id is not None
|
||||
assert isinstance(profile_id, int)
|
||||
|
||||
# Verify profile was created
|
||||
retrieved_profile = profile_manager.get_profile("personal")
|
||||
assert retrieved_profile.first_name == "John"
|
||||
assert retrieved_profile.last_name == "Doe"
|
||||
assert retrieved_profile.contact.email == "john.doe@example.com"
|
||||
|
||||
def test_create_profile_with_dict(self, profile_manager):
|
||||
"""Test profile creation with dictionary data."""
|
||||
profile_data = {
|
||||
"first_name": "Jane",
|
||||
"last_name": "Smith",
|
||||
"contact": {
|
||||
"email": "jane@example.com"
|
||||
}
|
||||
}
|
||||
|
||||
profile_id = profile_manager.create_profile("work", profile_data)
|
||||
assert profile_id is not None
|
||||
|
||||
retrieved_profile = profile_manager.get_profile("work")
|
||||
assert retrieved_profile.first_name == "Jane"
|
||||
|
||||
def test_create_profile_duplicate_name(self, profile_manager, sample_profile_data):
|
||||
"""Test creating profile with duplicate name fails."""
|
||||
profile_manager.create_profile("test", sample_profile_data)
|
||||
|
||||
with pytest.raises(ValueError, match="already exists"):
|
||||
profile_manager.create_profile("test", sample_profile_data)
|
||||
|
||||
def test_create_profile_invalid_data(self, profile_manager):
|
||||
"""Test creating profile with invalid data fails."""
|
||||
invalid_data = {
|
||||
"first_name": "A" * 150, # Too long
|
||||
"contact": {
|
||||
"email": "invalid-email"
|
||||
}
|
||||
}
|
||||
|
||||
with pytest.raises(ProfileValidationError):
|
||||
profile_manager.create_profile("invalid", invalid_data)
|
||||
|
||||
def test_create_profile_set_default(self, profile_manager, sample_profile_data):
|
||||
"""Test creating profile and setting as default."""
|
||||
profile_id = profile_manager.create_profile(
|
||||
"default_test",
|
||||
sample_profile_data,
|
||||
set_as_default=True
|
||||
)
|
||||
|
||||
# Verify it's set as default
|
||||
default_profile = profile_manager.get_default_profile()
|
||||
assert default_profile is not None
|
||||
assert default_profile.first_name == "John"
|
||||
|
||||
def test_get_profile_not_found(self, profile_manager):
|
||||
"""Test getting non-existent profile raises error."""
|
||||
with pytest.raises(ProfileNotFoundError):
|
||||
profile_manager.get_profile("nonexistent")
|
||||
|
||||
def test_get_profile_info(self, profile_manager, sample_profile_data):
|
||||
"""Test getting profile metadata."""
|
||||
profile_manager.create_profile("info_test", sample_profile_data, description="Test profile")
|
||||
|
||||
profile_info = profile_manager.get_profile_info("info_test")
|
||||
|
||||
assert profile_info["name"] == "info_test"
|
||||
assert profile_info["description"] == "Test profile"
|
||||
assert profile_info["is_active"] is True
|
||||
assert profile_info["is_default"] is False
|
||||
assert "created_at" in profile_info
|
||||
assert "updated_at" in profile_info
|
||||
|
||||
def test_update_profile_success(self, profile_manager, sample_profile_data):
|
||||
"""Test successful profile update."""
|
||||
profile_manager.create_profile("update_test", sample_profile_data)
|
||||
|
||||
# Update some fields
|
||||
updated_data = ProfileData(
|
||||
first_name="Johnny",
|
||||
last_name="Doe",
|
||||
contact=ContactInfo(email="johnny@example.com")
|
||||
)
|
||||
|
||||
success = profile_manager.update_profile("update_test", updated_data, "Updated description")
|
||||
assert success is True
|
||||
|
||||
# Verify updates
|
||||
updated_profile = profile_manager.get_profile("update_test")
|
||||
assert updated_profile.first_name == "Johnny"
|
||||
assert updated_profile.contact.email == "johnny@example.com"
|
||||
|
||||
profile_info = profile_manager.get_profile_info("update_test")
|
||||
assert profile_info["description"] == "Updated description"
|
||||
|
||||
def test_update_profile_not_found(self, profile_manager):
|
||||
"""Test updating non-existent profile fails."""
|
||||
with pytest.raises(ProfileNotFoundError):
|
||||
profile_manager.update_profile("nonexistent", ProfileData())
|
||||
|
||||
def test_delete_profile_soft_delete(self, profile_manager, sample_profile_data):
|
||||
"""Test soft delete (deactivate) profile."""
|
||||
profile_manager.create_profile("delete_test", sample_profile_data)
|
||||
|
||||
success = profile_manager.delete_profile("delete_test", hard_delete=False)
|
||||
assert success is True
|
||||
|
||||
# Profile should not be found in active profiles
|
||||
with pytest.raises(ProfileNotFoundError):
|
||||
profile_manager.get_profile("delete_test")
|
||||
|
||||
# But should appear in list with inactive profiles
|
||||
all_profiles = profile_manager.list_profiles(include_inactive=True)
|
||||
inactive_names = [p["name"] for p in all_profiles if not p["is_active"]]
|
||||
assert "delete_test" in inactive_names
|
||||
|
||||
def test_delete_profile_hard_delete(self, profile_manager, sample_profile_data):
|
||||
"""Test hard delete (permanent) profile."""
|
||||
profile_manager.create_profile("hard_delete_test", sample_profile_data)
|
||||
|
||||
success = profile_manager.delete_profile("hard_delete_test", hard_delete=True)
|
||||
assert success is True
|
||||
|
||||
# Profile should not appear anywhere
|
||||
all_profiles = profile_manager.list_profiles(include_inactive=True)
|
||||
all_names = [p["name"] for p in all_profiles]
|
||||
assert "hard_delete_test" not in all_names
|
||||
|
||||
def test_list_profiles_active_only(self, profile_manager, sample_profile_data):
|
||||
"""Test listing active profiles only."""
|
||||
# Create multiple profiles
|
||||
profile_manager.create_profile("active1", sample_profile_data)
|
||||
profile_manager.create_profile("active2", sample_profile_data)
|
||||
profile_manager.create_profile("to_deactivate", sample_profile_data)
|
||||
|
||||
# Deactivate one
|
||||
profile_manager.delete_profile("to_deactivate", hard_delete=False)
|
||||
|
||||
profiles = profile_manager.list_profiles(include_inactive=False)
|
||||
active_names = [p["name"] for p in profiles]
|
||||
|
||||
assert "active1" in active_names
|
||||
assert "active2" in active_names
|
||||
assert "to_deactivate" not in active_names
|
||||
|
||||
def test_list_profiles_include_inactive(self, profile_manager, sample_profile_data):
|
||||
"""Test listing all profiles including inactive."""
|
||||
profile_manager.create_profile("active", sample_profile_data)
|
||||
profile_manager.create_profile("inactive", sample_profile_data)
|
||||
profile_manager.delete_profile("inactive", hard_delete=False)
|
||||
|
||||
profiles = profile_manager.list_profiles(include_inactive=True)
|
||||
all_names = [p["name"] for p in profiles]
|
||||
|
||||
assert "active" in all_names
|
||||
assert "inactive" in all_names
|
||||
assert len(profiles) == 2
|
||||
|
||||
def test_set_default_profile(self, profile_manager, sample_profile_data):
|
||||
"""Test setting default profile."""
|
||||
# Create multiple profiles
|
||||
profile_manager.create_profile("profile1", sample_profile_data)
|
||||
profile_manager.create_profile("profile2", sample_profile_data)
|
||||
|
||||
# Set profile2 as default
|
||||
success = profile_manager.set_default_profile("profile2")
|
||||
assert success is True
|
||||
|
||||
# Verify default
|
||||
default_profile = profile_manager.get_default_profile()
|
||||
assert default_profile is not None
|
||||
assert default_profile.first_name == "John" # From sample data
|
||||
|
||||
# Check that only profile2 is marked as default
|
||||
profiles = profile_manager.list_profiles()
|
||||
default_profiles = [p for p in profiles if p["is_default"]]
|
||||
assert len(default_profiles) == 1
|
||||
assert default_profiles[0]["name"] == "profile2"
|
||||
|
||||
def test_get_default_profile_none_set(self, profile_manager):
|
||||
"""Test getting default profile when none is set."""
|
||||
default_profile = profile_manager.get_default_profile()
|
||||
assert default_profile is None
|
||||
|
||||
def test_export_profile_json(self, profile_manager, sample_profile_data):
|
||||
"""Test exporting profile to JSON format."""
|
||||
profile_manager.create_profile("export_test", sample_profile_data, "Test for export")
|
||||
|
||||
exported = profile_manager.export_profile("export_test", format="json")
|
||||
|
||||
# Parse and verify
|
||||
data = json.loads(exported)
|
||||
assert "profile_info" in data
|
||||
assert "profile_data" in data
|
||||
assert data["profile_info"]["name"] == "export_test"
|
||||
assert data["profile_data"]["first_name"] == "John"
|
||||
|
||||
def test_export_profile_yaml(self, profile_manager, sample_profile_data):
|
||||
"""Test exporting profile to YAML format."""
|
||||
profile_manager.create_profile("yaml_test", sample_profile_data)
|
||||
|
||||
try:
|
||||
exported = profile_manager.export_profile("yaml_test", format="yaml")
|
||||
assert "first_name: John" in exported
|
||||
assert "profile_info:" in exported
|
||||
except ValueError as e:
|
||||
if "PyYAML" in str(e):
|
||||
pytest.skip("PyYAML not available")
|
||||
raise
|
||||
|
||||
def test_export_profile_unsupported_format(self, profile_manager, sample_profile_data):
|
||||
"""Test exporting profile with unsupported format."""
|
||||
profile_manager.create_profile("format_test", sample_profile_data)
|
||||
|
||||
with pytest.raises(ValueError, match="Unsupported export format"):
|
||||
profile_manager.export_profile("format_test", format="xml")
|
||||
|
||||
def test_import_profile_json(self, profile_manager):
|
||||
"""Test importing profile from JSON."""
|
||||
import_data = {
|
||||
"profile_info": {
|
||||
"description": "Imported profile"
|
||||
},
|
||||
"profile_data": {
|
||||
"first_name": "Imported",
|
||||
"last_name": "User",
|
||||
"contact": {
|
||||
"email": "imported@example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json_data = json.dumps(import_data)
|
||||
profile_id = profile_manager.import_profile("imported", json_data, format="json")
|
||||
|
||||
assert profile_id is not None
|
||||
|
||||
# Verify imported data
|
||||
imported_profile = profile_manager.get_profile("imported")
|
||||
assert imported_profile.first_name == "Imported"
|
||||
assert imported_profile.contact.email == "imported@example.com"
|
||||
|
||||
profile_info = profile_manager.get_profile_info("imported")
|
||||
assert profile_info["description"] == "Imported profile"
|
||||
|
||||
def test_import_profile_overwrite(self, profile_manager, sample_profile_data):
|
||||
"""Test importing profile with overwrite."""
|
||||
# Create existing profile
|
||||
profile_manager.create_profile("overwrite_test", sample_profile_data)
|
||||
|
||||
# Import new data
|
||||
import_data = {
|
||||
"profile_data": {
|
||||
"first_name": "Overwritten",
|
||||
"contact": {"email": "new@example.com"}
|
||||
}
|
||||
}
|
||||
|
||||
json_data = json.dumps(import_data)
|
||||
profile_id = profile_manager.import_profile("overwrite_test", json_data, overwrite=True)
|
||||
|
||||
# Verify overwrite
|
||||
profile = profile_manager.get_profile("overwrite_test")
|
||||
assert profile.first_name == "Overwritten"
|
||||
|
||||
def test_import_profile_no_overwrite_fails(self, profile_manager, sample_profile_data):
|
||||
"""Test importing existing profile without overwrite fails."""
|
||||
profile_manager.create_profile("existing", sample_profile_data)
|
||||
|
||||
import_data = {"profile_data": {"first_name": "New"}}
|
||||
json_data = json.dumps(import_data)
|
||||
|
||||
with pytest.raises(ValueError, match="already exists"):
|
||||
profile_manager.import_profile("existing", json_data, overwrite=False)
|
||||
|
||||
def test_merge_profiles(self, profile_manager):
|
||||
"""Test merging two profiles."""
|
||||
# Create base profile
|
||||
base_data = ProfileData(
|
||||
first_name="Base",
|
||||
last_name="User",
|
||||
contact=ContactInfo(email="base@example.com", phone="123-456-7890"),
|
||||
address=Address(city="BaseCity")
|
||||
)
|
||||
profile_manager.create_profile("base", base_data)
|
||||
|
||||
# Create override profile
|
||||
override_data = ProfileData(
|
||||
first_name="Override",
|
||||
contact=ContactInfo(email="override@example.com"),
|
||||
organization=Organization(name="Override Corp")
|
||||
)
|
||||
profile_manager.create_profile("override", override_data)
|
||||
|
||||
# Merge profiles
|
||||
merged_profile = profile_manager.merge_profiles("base", "override")
|
||||
|
||||
# Verify merge results
|
||||
assert merged_profile.first_name == "Override" # Overridden
|
||||
assert merged_profile.last_name == "User" # From base
|
||||
assert merged_profile.contact.email == "override@example.com" # Overridden
|
||||
assert merged_profile.contact.phone == "123-456-7890" # From base
|
||||
assert merged_profile.address.city == "BaseCity" # From base
|
||||
assert merged_profile.organization.name == "Override Corp" # From override
|
||||
|
||||
def test_get_template_variables(self, profile_manager, sample_profile_data):
|
||||
"""Test extracting template variables from profile."""
|
||||
profile_manager.create_profile("template_test", sample_profile_data, set_as_default=True)
|
||||
|
||||
variables = profile_manager.get_template_variables("template_test")
|
||||
|
||||
# Check flattened variables
|
||||
assert variables["first_name"] == "John"
|
||||
assert variables["last_name"] == "Doe"
|
||||
assert variables["contact.email"] == "john.doe@example.com"
|
||||
assert variables["organization.name"] == "Tech Corp"
|
||||
assert variables["address.city"] == "San Francisco"
|
||||
|
||||
# Check computed variable
|
||||
assert variables["full_name"] == "John Doe"
|
||||
|
||||
def test_get_template_variables_default_profile(self, profile_manager, sample_profile_data):
|
||||
"""Test getting template variables from default profile."""
|
||||
profile_manager.create_profile("default_vars", sample_profile_data, set_as_default=True)
|
||||
|
||||
# Get variables without specifying profile name
|
||||
variables = profile_manager.get_template_variables()
|
||||
|
||||
assert variables["first_name"] == "John"
|
||||
assert "contact.email" in variables
|
||||
|
||||
def test_get_template_variables_no_default(self, profile_manager):
|
||||
"""Test getting template variables when no default profile."""
|
||||
variables = profile_manager.get_template_variables()
|
||||
assert variables == {}
|
||||
|
||||
def test_database_integration(self, profile_manager, sample_profile_data):
|
||||
"""Test database persistence and retrieval."""
|
||||
# Create profile
|
||||
profile_id = profile_manager.create_profile("db_test", sample_profile_data)
|
||||
|
||||
# Create new manager instance with same database
|
||||
new_manager = ProfileManager(profile_manager.db_path)
|
||||
|
||||
# Verify data persists
|
||||
retrieved_profile = new_manager.get_profile("db_test")
|
||||
assert retrieved_profile.first_name == "John"
|
||||
assert retrieved_profile.contact.email == "john.doe@example.com"
|
||||
|
||||
def test_profile_timestamps(self, profile_manager, sample_profile_data):
|
||||
"""Test profile creation and update timestamps."""
|
||||
before_create = datetime.now().isoformat()
|
||||
profile_manager.create_profile("timestamp_test", sample_profile_data)
|
||||
after_create = datetime.now().isoformat()
|
||||
|
||||
profile_info = profile_manager.get_profile_info("timestamp_test")
|
||||
assert before_create <= profile_info["created_at"] <= after_create
|
||||
assert before_create <= profile_info["updated_at"] <= after_create
|
||||
|
||||
# Update profile
|
||||
before_update = datetime.now().isoformat()
|
||||
profile_manager.update_profile("timestamp_test", ProfileData(first_name="Updated"))
|
||||
after_update = datetime.now().isoformat()
|
||||
|
||||
updated_info = profile_manager.get_profile_info("timestamp_test")
|
||||
assert updated_info["created_at"] == profile_info["created_at"] # Unchanged
|
||||
assert before_update <= updated_info["updated_at"] <= after_update
|
||||
|
||||
def test_edge_cases(self, profile_manager):
|
||||
"""Test edge cases and boundary conditions."""
|
||||
# Empty profile
|
||||
empty_profile = ProfileData()
|
||||
profile_manager.create_profile("empty", empty_profile)
|
||||
retrieved = profile_manager.get_profile("empty")
|
||||
assert retrieved.first_name is None
|
||||
|
||||
# Profile with only custom fields
|
||||
custom_profile = ProfileData(custom_fields={"hobby": "coding", "level": "expert"})
|
||||
profile_manager.create_profile("custom", custom_profile)
|
||||
retrieved_custom = profile_manager.get_profile("custom")
|
||||
assert retrieved_custom.custom_fields["hobby"] == "coding"
|
||||
Reference in New Issue
Block a user