first
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"""Tests for core domain invariants around artifacts & relationships."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from asf_domain import (
|
||||
Artifact,
|
||||
ArtifactCreate,
|
||||
ArtifactRelationshipCreate,
|
||||
ArtifactRelationship,
|
||||
ArtifactType,
|
||||
RelationshipType,
|
||||
generate_id,
|
||||
)
|
||||
|
||||
|
||||
NOW = datetime(2026, 9, 7, 12, 0, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def test_generate_id_returns_unique_uuid4() -> None:
|
||||
a = generate_id()
|
||||
b = generate_id()
|
||||
assert a != b
|
||||
assert a.version == 4
|
||||
|
||||
|
||||
def test_artifact_create_minimal() -> None:
|
||||
body = ArtifactCreate(
|
||||
project_id=uuid4(),
|
||||
type=ArtifactType.REQUIREMENT,
|
||||
title="Login page must support MFA",
|
||||
)
|
||||
assert body.type == ArtifactType.REQUIREMENT
|
||||
assert body.metadata == {}
|
||||
|
||||
|
||||
def test_artifact_stored_round_trip() -> None:
|
||||
tenant_id = uuid4()
|
||||
project_id = uuid4()
|
||||
|
||||
a = Artifact(
|
||||
tenant_id=tenant_id,
|
||||
project_id=project_id,
|
||||
type=ArtifactType.TASK,
|
||||
title="Wire MFA provider",
|
||||
content="- [ ] choose TOTP lib\n- [ ] enroll flow",
|
||||
metadata={"estimated_points": 5, "labels": ["auth", "security"]},
|
||||
created_at=NOW,
|
||||
updated_at=NOW,
|
||||
)
|
||||
d = a.model_dump(mode="json")
|
||||
# ensure UTC serialization works
|
||||
assert d["created_at"].endswith("Z")
|
||||
assert d["metadata"]["labels"] == ["auth", "security"]
|
||||
|
||||
|
||||
def test_artifact_rejects_empty_title() -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
ArtifactCreate(project_id=uuid4(), type=ArtifactType.STORY, title="")
|
||||
|
||||
|
||||
def test_relationship_create() -> None:
|
||||
a = uuid4()
|
||||
b = uuid4()
|
||||
rel = ArtifactRelationshipCreate(
|
||||
relationship_type=RelationshipType.IMPLEMENTED_BY,
|
||||
source_artifact_id=a,
|
||||
target_artifact_id=b,
|
||||
)
|
||||
assert rel.source_artifact_id == a
|
||||
|
||||
|
||||
def test_relationship_stored_enforces_tenancy() -> None:
|
||||
tenant_id = uuid4()
|
||||
a, b = uuid4(), uuid4()
|
||||
pa, pb = uuid4(), uuid4()
|
||||
rel = ArtifactRelationship(
|
||||
tenant_id=tenant_id,
|
||||
source_project_id=pa,
|
||||
target_project_id=pb,
|
||||
relationship_type=RelationshipType.VERIFIED_BY,
|
||||
source_artifact_id=a,
|
||||
target_artifact_id=b,
|
||||
created_at=NOW,
|
||||
updated_at=NOW,
|
||||
)
|
||||
# a relationship carries both project ids for tenant isolation queries
|
||||
assert rel.tenant_id == tenant_id
|
||||
assert rel.source_project_id == pa
|
||||
assert rel.target_project_id == pb
|
||||
@@ -0,0 +1,41 @@
|
||||
"""Tests for Organization / Project invariants."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from asf_domain import (
|
||||
Organization,
|
||||
OrganizationCreate,
|
||||
Project,
|
||||
ProjectCreate,
|
||||
)
|
||||
|
||||
|
||||
NOW = datetime(2026, 9, 7, 12, 0, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def test_organization_create_validates_slug() -> None:
|
||||
OrganizationCreate(name="Contoso", slug="contoso-retail")
|
||||
with pytest.raises(ValidationError):
|
||||
OrganizationCreate(name="Bad", slug="UPPERCASE no!")
|
||||
|
||||
|
||||
def test_project_create_validates_key_uppercase() -> None:
|
||||
ProjectCreate(name="ASF Core", key="ASF")
|
||||
with pytest.raises(ValidationError):
|
||||
ProjectCreate(name="Bad", key="lowercase")
|
||||
|
||||
|
||||
def test_project_scoped_under_tenant() -> None:
|
||||
tenant_id = uuid4()
|
||||
p = Project(
|
||||
tenant_id=tenant_id,
|
||||
name="ASF Web",
|
||||
key="WEB",
|
||||
created_at=NOW,
|
||||
updated_at=NOW,
|
||||
)
|
||||
assert p.tenant_id == tenant_id
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Tests for job state transitions & usage tracking."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from asf_domain import (
|
||||
Job,
|
||||
JobCreate,
|
||||
JobUpdate,
|
||||
JobStatus,
|
||||
JobPriority,
|
||||
JobType,
|
||||
JobUsage,
|
||||
)
|
||||
|
||||
|
||||
NOW = datetime(2026, 9, 7, 12, 0, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def test_job_create_defaults() -> None:
|
||||
jc = JobCreate(
|
||||
type=JobType.AI_INTERACTIVE,
|
||||
payload={"prompt": "summarize REQ-1", "artifact_id": str(uuid4())},
|
||||
)
|
||||
assert jc.priority == JobPriority.NORMAL
|
||||
assert jc.model is None
|
||||
|
||||
|
||||
def test_job_lifecycle_updates() -> None:
|
||||
tenant_id = uuid4()
|
||||
j = Job(
|
||||
tenant_id=tenant_id,
|
||||
type=JobType.AI_BACKGROUND,
|
||||
payload={"artifact_id": str(uuid4())},
|
||||
created_at=NOW,
|
||||
)
|
||||
assert j.status == JobStatus.QUEUED
|
||||
|
||||
# worker claims it
|
||||
update1 = JobUpdate(status=JobStatus.RUNNING, worker="worker-asf-ai-7", started_at=NOW)
|
||||
data = j.model_copy(update=update1.model_dump(exclude_unset=True))
|
||||
assert data.status == JobStatus.RUNNING
|
||||
assert data.worker == "worker-asf-ai-7"
|
||||
|
||||
# worker completes with usage
|
||||
usage = JobUsage(
|
||||
input_tokens=1500, output_tokens=400, gpu_seconds=2.3, estimated_cost=0.012
|
||||
)
|
||||
update2 = JobUpdate(status=JobStatus.COMPLETED, completed_at=NOW, usage=usage)
|
||||
final = data.model_copy(update=update2.model_dump(exclude_unset=True))
|
||||
assert final.status == JobStatus.COMPLETED
|
||||
final_usage = JobUsage.model_validate(final.usage)
|
||||
assert final_usage.output_tokens == 400
|
||||
assert final_usage.estimated_cost == pytest.approx(0.012)
|
||||
|
||||
|
||||
def test_job_update_validates_progress_bounds() -> None:
|
||||
from pydantic import ValidationError
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
JobUpdate(progress_percent=150)
|
||||
Reference in New Issue
Block a user