""" Unit tests for artifact models. Tests Artifact, ArtifactMetadata, ArtifactReference, and content digest calculation functions. """ import pytest from datetime import datetime from markitect.prompts.models import ( Artifact, ArtifactMetadata, ArtifactReference, ArtifactType, calculate_content_digest, calculate_bundle_digest, ) class TestArtifactMetadata: """Tests for ArtifactMetadata.""" def test_create_empty_metadata(self): """Test creating metadata with defaults.""" metadata = ArtifactMetadata() assert metadata.description is None assert metadata.tags == [] assert metadata.author is None assert metadata.version is None assert metadata.custom == {} def test_create_metadata_with_values(self): """Test creating metadata with values.""" metadata = ArtifactMetadata( description="Test artifact", tags=["test", "example"], author="test-author", version="1.0.0", custom={"key": "value"} ) assert metadata.description == "Test artifact" assert metadata.tags == ["test", "example"] assert metadata.author == "test-author" assert metadata.version == "1.0.0" assert metadata.custom == {"key": "value"} def test_metadata_to_dict(self): """Test metadata serialization to dict.""" metadata = ArtifactMetadata( description="Test", tags=["tag1"], author="author", ) data = metadata.to_dict() assert data["description"] == "Test" assert data["tags"] == ["tag1"] assert data["author"] == "author" assert data["version"] is None assert data["custom"] == {} def test_metadata_from_dict(self): """Test metadata deserialization from dict.""" data = { "description": "Test", "tags": ["tag1", "tag2"], "author": "author", "version": "2.0", "custom": {"extra": "data"} } metadata = ArtifactMetadata.from_dict(data) assert metadata.description == "Test" assert metadata.tags == ["tag1", "tag2"] assert metadata.author == "author" assert metadata.version == "2.0" assert metadata.custom == {"extra": "data"} class TestArtifact: """Tests for Artifact model.""" def test_create_artifact(self): """Test artifact creation with automatic digest calculation.""" content = "# Test Content\n\nThis is a test." artifact = Artifact.create( space_id="space-1", name="test-artifact", content=content, ) assert artifact.id # UUID generated assert artifact.space_id == "space-1" assert artifact.name == "test-artifact" assert artifact.artifact_type == ArtifactType.CONTENT assert artifact.content_digest == calculate_content_digest(content) assert artifact.content_size == len(content.encode('utf-8')) assert isinstance(artifact.metadata, ArtifactMetadata) assert isinstance(artifact.created_at, datetime) assert isinstance(artifact.updated_at, datetime) def test_create_artifact_with_type(self): """Test creating artifact with specific type.""" artifact = Artifact.create( space_id="space-1", name="my-template", content="Template content", artifact_type=ArtifactType.TEMPLATE, ) assert artifact.artifact_type == ArtifactType.TEMPLATE def test_create_artifact_with_metadata(self): """Test creating artifact with metadata.""" metadata = ArtifactMetadata(description="Test", tags=["tag1"]) artifact = Artifact.create( space_id="space-1", name="test", content="content", metadata=metadata, ) assert artifact.metadata.description == "Test" assert artifact.metadata.tags == ["tag1"] def test_update_content(self): """Test updating artifact content.""" artifact = Artifact.create( space_id="space-1", name="test", content="original content", ) original_digest = artifact.content_digest original_updated_at = artifact.updated_at # Small delay to ensure timestamp difference import time time.sleep(0.01) new_content = "updated content" artifact.update_content(new_content) assert artifact.content_digest != original_digest assert artifact.content_digest == calculate_content_digest(new_content) assert artifact.content_size == len(new_content.encode('utf-8')) assert artifact.updated_at > original_updated_at def test_has_changed(self): """Test change detection.""" content = "original" artifact = Artifact.create( space_id="space-1", name="test", content=content, ) # Same content - no change assert not artifact.has_changed(calculate_content_digest(content)) # Different content - changed assert artifact.has_changed(calculate_content_digest("different")) def test_artifact_to_dict(self): """Test artifact serialization.""" artifact = Artifact.create( space_id="space-1", name="test", content="content", artifact_type=ArtifactType.TEMPLATE, ) data = artifact.to_dict() assert data["id"] == artifact.id assert data["space_id"] == "space-1" assert data["name"] == "test" assert data["artifact_type"] == "template" assert data["content_digest"] == artifact.content_digest assert data["content_size"] == artifact.content_size assert "metadata" in data assert "created_at" in data assert "updated_at" in data def test_artifact_from_dict(self): """Test artifact deserialization.""" original = Artifact.create( space_id="space-1", name="test", content="content", ) data = original.to_dict() restored = Artifact.from_dict(data) assert restored.id == original.id assert restored.space_id == original.space_id assert restored.name == original.name assert restored.artifact_type == original.artifact_type assert restored.content_digest == original.content_digest assert restored.content_size == original.content_size class TestArtifactReference: """Tests for ArtifactReference.""" def test_simple_reference(self): """Test simple artifact name reference.""" ref = ArtifactReference(name="my-artifact") assert ref.name == "my-artifact" assert ref.space_id is None assert ref.version is None assert str(ref) == "my-artifact" def test_reference_with_space(self): """Test reference with space ID.""" ref = ArtifactReference(name="artifact", space_id="space-1") assert ref.name == "artifact" assert ref.space_id == "space-1" assert str(ref) == "space-1:artifact" def test_reference_with_version(self): """Test reference with version.""" ref = ArtifactReference(name="artifact", version="1.0.0") assert ref.name == "artifact" assert ref.version == "1.0.0" assert str(ref) == "artifact@1.0.0" def test_reference_with_space_and_version(self): """Test fully qualified reference.""" ref = ArtifactReference( name="artifact", space_id="space-1", version="2.0.0" ) assert str(ref) == "space-1:artifact@2.0.0" def test_parse_simple_reference(self): """Test parsing simple reference.""" ref = ArtifactReference.parse("my-artifact") assert ref.name == "my-artifact" assert ref.space_id is None assert ref.version is None def test_parse_space_reference(self): """Test parsing space:artifact reference.""" ref = ArtifactReference.parse("space-1:my-artifact") assert ref.name == "my-artifact" assert ref.space_id == "space-1" assert ref.version is None def test_parse_version_reference(self): """Test parsing artifact@version reference.""" ref = ArtifactReference.parse("my-artifact@1.0.0") assert ref.name == "my-artifact" assert ref.space_id is None assert ref.version == "1.0.0" def test_parse_full_reference(self): """Test parsing space:artifact@version reference.""" ref = ArtifactReference.parse("space-1:my-artifact@2.0.0") assert ref.name == "my-artifact" assert ref.space_id == "space-1" assert ref.version == "2.0.0" def test_parse_invalid_reference(self): """Test parsing invalid reference raises error.""" with pytest.raises(ValueError, match="Invalid artifact reference"): ArtifactReference.parse("space-1:") with pytest.raises(ValueError, match="Invalid artifact reference"): ArtifactReference.parse("@1.0.0") class TestContentDigest: """Tests for content digest functions.""" def test_calculate_content_digest(self): """Test SHA-256 digest calculation.""" content = "Hello, World!" digest = calculate_content_digest(content) # SHA-256 produces 64 hex characters assert len(digest) == 64 assert digest.isalnum() def test_digest_consistency(self): """Test digest is consistent for same content.""" content = "Test content" digest1 = calculate_content_digest(content) digest2 = calculate_content_digest(content) assert digest1 == digest2 def test_digest_changes_with_content(self): """Test digest changes when content changes.""" digest1 = calculate_content_digest("content 1") digest2 = calculate_content_digest("content 2") assert digest1 != digest2 def test_calculate_bundle_digest(self): """Test bundle digest calculation.""" components = { "template": "abc123", "dep1": "def456", "dep2": "ghi789", } digest = calculate_bundle_digest(components) assert len(digest) == 64 def test_bundle_digest_order_independent(self): """Test bundle digest is deterministic regardless of dict order.""" components1 = {"a": "1", "b": "2", "c": "3"} components2 = {"c": "3", "a": "1", "b": "2"} assert calculate_bundle_digest(components1) == calculate_bundle_digest(components2) def test_bundle_digest_sensitive_to_values(self): """Test bundle digest changes when values change.""" digest1 = calculate_bundle_digest({"a": "1", "b": "2"}) digest2 = calculate_bundle_digest({"a": "1", "b": "3"}) assert digest1 != digest2