Files
crewAI/tests/security/test_fingerprint.py
João Moura d42e58e199 adding fingerprints (#2332)
* adding fingerprints

* fixed

* fix

* Fix Pydantic v2 compatibility in SecurityConfig and Fingerprint classes (#2335)

* Fix Pydantic v2 compatibility in SecurityConfig and Fingerprint classes

Co-Authored-By: Joe Moura <joao@crewai.com>

* Fix type-checker errors in fingerprint properties

Co-Authored-By: Joe Moura <joao@crewai.com>

* Enhance security validation in Fingerprint and SecurityConfig classes

Co-Authored-By: Joe Moura <joao@crewai.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>

* incorporate small improvements / changes

* Expect different

* Remove redundant null check in Crew.fingerprint property (#2342)

* Remove redundant null check in Crew.fingerprint property and add security module

Co-Authored-By: Joe Moura <joao@crewai.com>

* Enhance security module with type hints, improved UUID namespace, metadata validation, and versioning

Co-Authored-By: Joe Moura <joao@crewai.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>
Co-authored-by: João Moura <joaomdmoura@gmail.com>

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>
Co-authored-by: Brandon Hancock <brandon@brandonhancock.io>
2025-03-14 03:00:30 -03:00

263 lines
8.2 KiB
Python

"""Test for the Fingerprint class."""
import json
import uuid
from datetime import datetime, timedelta
import pytest
from pydantic import ValidationError
from crewai.security import Fingerprint
def test_fingerprint_creation_with_defaults():
"""Test creating a Fingerprint with default values."""
fingerprint = Fingerprint()
# Check that a UUID was generated
assert fingerprint.uuid_str is not None
# Check that it's a valid UUID
uuid_obj = uuid.UUID(fingerprint.uuid_str)
assert isinstance(uuid_obj, uuid.UUID)
# Check that creation time was set
assert isinstance(fingerprint.created_at, datetime)
# Check that metadata is an empty dict
assert fingerprint.metadata == {}
def test_fingerprint_creation_with_metadata():
"""Test creating a Fingerprint with custom metadata only."""
metadata = {"version": "1.0", "author": "Test Author"}
fingerprint = Fingerprint(metadata=metadata)
# UUID and created_at should be auto-generated
assert fingerprint.uuid_str is not None
assert isinstance(fingerprint.created_at, datetime)
# Only metadata should be settable
assert fingerprint.metadata == metadata
def test_fingerprint_uuid_cannot_be_set():
"""Test that uuid_str cannot be manually set."""
original_uuid = "b723c6ff-95de-5e87-860b-467b72282bd8"
# Attempt to set uuid_str
fingerprint = Fingerprint(uuid_str=original_uuid)
# UUID should be generated, not set to our value
assert fingerprint.uuid_str != original_uuid
assert uuid.UUID(fingerprint.uuid_str) # Should be a valid UUID
def test_fingerprint_created_at_cannot_be_set():
"""Test that created_at cannot be manually set."""
original_time = datetime.now() - timedelta(days=1)
# Attempt to set created_at
fingerprint = Fingerprint(created_at=original_time)
# created_at should be auto-generated, not set to our value
assert fingerprint.created_at != original_time
assert fingerprint.created_at > original_time # Should be more recent
def test_fingerprint_uuid_property():
"""Test the uuid property returns a UUID object."""
fingerprint = Fingerprint()
assert isinstance(fingerprint.uuid, uuid.UUID)
assert str(fingerprint.uuid) == fingerprint.uuid_str
def test_fingerprint_deterministic_generation():
"""Test that the same seed string always generates the same fingerprint using generate method."""
seed = "test-seed"
# Use the generate method which supports deterministic generation
fingerprint1 = Fingerprint.generate(seed)
fingerprint2 = Fingerprint.generate(seed)
assert fingerprint1.uuid_str == fingerprint2.uuid_str
# Also test with _generate_uuid method directly
uuid_str1 = Fingerprint._generate_uuid(seed)
uuid_str2 = Fingerprint._generate_uuid(seed)
assert uuid_str1 == uuid_str2
def test_fingerprint_generate_classmethod():
"""Test the generate class method."""
# Without seed
fingerprint1 = Fingerprint.generate()
assert isinstance(fingerprint1, Fingerprint)
# With seed
seed = "test-seed"
metadata = {"version": "1.0"}
fingerprint2 = Fingerprint.generate(seed, metadata)
assert isinstance(fingerprint2, Fingerprint)
assert fingerprint2.metadata == metadata
# Same seed should generate same UUID
fingerprint3 = Fingerprint.generate(seed)
assert fingerprint2.uuid_str == fingerprint3.uuid_str
def test_fingerprint_string_representation():
"""Test the string representation of Fingerprint."""
fingerprint = Fingerprint()
uuid_str = fingerprint.uuid_str
string_repr = str(fingerprint)
assert uuid_str in string_repr
def test_fingerprint_equality():
"""Test fingerprint equality comparison."""
# Using generate with the same seed to get consistent UUIDs
seed = "test-equality"
fingerprint1 = Fingerprint.generate(seed)
fingerprint2 = Fingerprint.generate(seed)
fingerprint3 = Fingerprint()
assert fingerprint1 == fingerprint2
assert fingerprint1 != fingerprint3
def test_fingerprint_hash():
"""Test that fingerprints can be used as dictionary keys."""
# Using generate with the same seed to get consistent UUIDs
seed = "test-hash"
fingerprint1 = Fingerprint.generate(seed)
fingerprint2 = Fingerprint.generate(seed)
# Hash should be consistent for same UUID
assert hash(fingerprint1) == hash(fingerprint2)
# Can be used as dict keys
fingerprint_dict = {fingerprint1: "value"}
assert fingerprint_dict[fingerprint2] == "value"
def test_fingerprint_to_dict():
"""Test converting fingerprint to dictionary."""
metadata = {"version": "1.0"}
fingerprint = Fingerprint(metadata=metadata)
uuid_str = fingerprint.uuid_str
created_at = fingerprint.created_at
fingerprint_dict = fingerprint.to_dict()
assert fingerprint_dict["uuid_str"] == uuid_str
assert fingerprint_dict["created_at"] == created_at.isoformat()
assert fingerprint_dict["metadata"] == metadata
def test_fingerprint_from_dict():
"""Test creating fingerprint from dictionary."""
uuid_str = "b723c6ff-95de-5e87-860b-467b72282bd8"
created_at = datetime.now()
created_at_iso = created_at.isoformat()
metadata = {"version": "1.0"}
fingerprint_dict = {
"uuid_str": uuid_str,
"created_at": created_at_iso,
"metadata": metadata
}
fingerprint = Fingerprint.from_dict(fingerprint_dict)
assert fingerprint.uuid_str == uuid_str
assert fingerprint.created_at.isoformat() == created_at_iso
assert fingerprint.metadata == metadata
def test_fingerprint_json_serialization():
"""Test that Fingerprint can be JSON serialized and deserialized."""
# Create a fingerprint, get its values
metadata = {"version": "1.0"}
fingerprint = Fingerprint(metadata=metadata)
uuid_str = fingerprint.uuid_str
created_at = fingerprint.created_at
# Convert to dict and then JSON
fingerprint_dict = fingerprint.to_dict()
json_str = json.dumps(fingerprint_dict)
# Parse JSON and create new fingerprint
parsed_dict = json.loads(json_str)
new_fingerprint = Fingerprint.from_dict(parsed_dict)
assert new_fingerprint.uuid_str == uuid_str
assert new_fingerprint.created_at.isoformat() == created_at.isoformat()
assert new_fingerprint.metadata == metadata
def test_invalid_uuid_str():
"""Test handling of invalid UUID strings."""
uuid_str = "not-a-valid-uuid"
created_at = datetime.now().isoformat()
fingerprint_dict = {
"uuid_str": uuid_str,
"created_at": created_at,
"metadata": {}
}
# The Fingerprint.from_dict method accepts even invalid UUIDs
# This seems to be the current behavior
fingerprint = Fingerprint.from_dict(fingerprint_dict)
# Verify it uses the provided UUID string, even if invalid
# This might not be ideal behavior, but it's the current implementation
assert fingerprint.uuid_str == uuid_str
# But this will raise an exception when we try to access the uuid property
with pytest.raises(ValueError):
uuid_obj = fingerprint.uuid
def test_fingerprint_metadata_mutation():
"""Test that metadata can be modified after fingerprint creation."""
# Create a fingerprint with initial metadata
initial_metadata = {"version": "1.0", "status": "draft"}
fingerprint = Fingerprint(metadata=initial_metadata)
# Verify initial metadata
assert fingerprint.metadata == initial_metadata
# Modify the metadata
fingerprint.metadata["status"] = "published"
fingerprint.metadata["author"] = "Test Author"
# Verify the modifications
expected_metadata = {
"version": "1.0",
"status": "published",
"author": "Test Author"
}
assert fingerprint.metadata == expected_metadata
# Make sure the UUID and creation time remain unchanged
uuid_str = fingerprint.uuid_str
created_at = fingerprint.created_at
# Completely replace the metadata
new_metadata = {"version": "2.0", "environment": "production"}
fingerprint.metadata = new_metadata
# Verify the replacement
assert fingerprint.metadata == new_metadata
# Ensure immutable fields remain unchanged
assert fingerprint.uuid_str == uuid_str
assert fingerprint.created_at == created_at