✨ feat(admin): consolidar governanca segura de tools na fase 5
parent
b3662906bc
commit
3dcf80eaaa
@ -0,0 +1,113 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from sqlalchemy import ForeignKey, Integer, JSON, String, Text, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.types import TypeDecorator
|
||||
|
||||
from admin_app.db.models.base import AdminTimestampedModel
|
||||
|
||||
|
||||
class ToolArtifactStage(str, Enum):
|
||||
GENERATION = "generation"
|
||||
VALIDATION = "validation"
|
||||
|
||||
|
||||
class ToolArtifactKind(str, Enum):
|
||||
GENERATION_REQUEST = "generation_request"
|
||||
VALIDATION_REPORT = "validation_report"
|
||||
|
||||
|
||||
class ToolArtifactStorageKind(str, Enum):
|
||||
INLINE_JSON = "inline_json"
|
||||
|
||||
|
||||
class ToolArtifactStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
SUCCEEDED = "succeeded"
|
||||
FAILED = "failed"
|
||||
|
||||
|
||||
class ToolArtifactEnumType(TypeDecorator):
|
||||
impl = String(40)
|
||||
cache_ok = True
|
||||
|
||||
def __init__(self, enum_cls: type[Enum], *, length: int = 40):
|
||||
super().__init__(length=length)
|
||||
self.enum_cls = enum_cls
|
||||
|
||||
@property
|
||||
def python_type(self):
|
||||
return self.enum_cls
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, self.enum_cls):
|
||||
return value.value
|
||||
return self.enum_cls(str(value).strip().lower()).value
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
if value is None:
|
||||
return None
|
||||
return self.enum_cls(str(value).strip().lower())
|
||||
|
||||
|
||||
class ToolArtifact(AdminTimestampedModel):
|
||||
__tablename__ = "tool_artifacts"
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"tool_version_id",
|
||||
"artifact_kind",
|
||||
name="uq_tool_artifacts_tool_version_kind",
|
||||
),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
artifact_id: Mapped[str] = mapped_column(String(140), unique=True, index=True, nullable=False)
|
||||
draft_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("tool_drafts.id"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
tool_version_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("tool_versions.id"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
tool_name: Mapped[str] = mapped_column(String(64), index=True, nullable=False)
|
||||
version_number: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
artifact_stage: Mapped[ToolArtifactStage] = mapped_column(
|
||||
ToolArtifactEnumType(ToolArtifactStage),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
artifact_kind: Mapped[ToolArtifactKind] = mapped_column(
|
||||
ToolArtifactEnumType(ToolArtifactKind),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
artifact_status: Mapped[ToolArtifactStatus] = mapped_column(
|
||||
ToolArtifactEnumType(ToolArtifactStatus),
|
||||
nullable=False,
|
||||
default=ToolArtifactStatus.PENDING,
|
||||
index=True,
|
||||
)
|
||||
storage_kind: Mapped[ToolArtifactStorageKind] = mapped_column(
|
||||
ToolArtifactEnumType(ToolArtifactStorageKind),
|
||||
nullable=False,
|
||||
default=ToolArtifactStorageKind.INLINE_JSON,
|
||||
)
|
||||
summary: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
payload_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict)
|
||||
checksum: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
author_staff_account_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("staff_accounts.id"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
author_display_name: Mapped[str] = mapped_column(String(150), nullable=False)
|
||||
@ -0,0 +1,54 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy import ForeignKey, Integer, JSON, String, Text, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from admin_app.db.models.base import AdminTimestampedModel
|
||||
from admin_app.db.models.tool_draft import ToolLifecycleStatusType
|
||||
from shared.contracts import ToolLifecycleStatus
|
||||
|
||||
|
||||
class ToolMetadata(AdminTimestampedModel):
|
||||
__tablename__ = "tool_metadata"
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"tool_name",
|
||||
"version_number",
|
||||
name="uq_tool_metadata_tool_name_version_number",
|
||||
),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
metadata_id: Mapped[str] = mapped_column(String(120), unique=True, index=True, nullable=False)
|
||||
draft_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("tool_drafts.id"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
tool_version_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("tool_versions.id"),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
index=True,
|
||||
)
|
||||
tool_name: Mapped[str] = mapped_column(String(64), index=True, nullable=False)
|
||||
display_name: Mapped[str] = mapped_column(String(120), nullable=False)
|
||||
domain: Mapped[str] = mapped_column(String(40), index=True, nullable=False)
|
||||
description: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
parameters_json: Mapped[list[dict]] = mapped_column(JSON, nullable=False, default=list)
|
||||
version_number: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
status: Mapped[ToolLifecycleStatus] = mapped_column(
|
||||
ToolLifecycleStatusType(),
|
||||
nullable=False,
|
||||
default=ToolLifecycleStatus.DRAFT,
|
||||
index=True,
|
||||
)
|
||||
author_staff_account_id: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
ForeignKey("staff_accounts.id"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
author_display_name: Mapped[str] = mapped_column(String(150), nullable=False)
|
||||
@ -0,0 +1,213 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from admin_app.db.models import ToolArtifact
|
||||
from admin_app.db.models.tool_artifact import (
|
||||
ToolArtifactKind,
|
||||
ToolArtifactStage,
|
||||
ToolArtifactStatus,
|
||||
ToolArtifactStorageKind,
|
||||
)
|
||||
from admin_app.repositories.base_repository import BaseRepository
|
||||
|
||||
|
||||
class ToolArtifactRepository(BaseRepository):
|
||||
def list_artifacts(
|
||||
self,
|
||||
*,
|
||||
tool_name: str | None = None,
|
||||
tool_version_id: int | None = None,
|
||||
artifact_stage: ToolArtifactStage | str | None = None,
|
||||
artifact_kind: ToolArtifactKind | str | None = None,
|
||||
) -> list[ToolArtifact]:
|
||||
statement = select(ToolArtifact).order_by(
|
||||
ToolArtifact.version_number.desc(),
|
||||
ToolArtifact.updated_at.desc(),
|
||||
ToolArtifact.created_at.desc(),
|
||||
)
|
||||
if tool_name:
|
||||
statement = statement.where(ToolArtifact.tool_name == str(tool_name).strip().lower())
|
||||
if tool_version_id is not None:
|
||||
statement = statement.where(ToolArtifact.tool_version_id == tool_version_id)
|
||||
if artifact_stage:
|
||||
statement = statement.where(
|
||||
ToolArtifact.artifact_stage == self._normalize_stage(artifact_stage)
|
||||
)
|
||||
if artifact_kind:
|
||||
statement = statement.where(
|
||||
ToolArtifact.artifact_kind == self._normalize_kind(artifact_kind)
|
||||
)
|
||||
return list(self.db.execute(statement).scalars().all())
|
||||
|
||||
def get_by_tool_version_and_kind(
|
||||
self,
|
||||
tool_version_id: int,
|
||||
artifact_kind: ToolArtifactKind | str,
|
||||
) -> ToolArtifact | None:
|
||||
statement = select(ToolArtifact).where(
|
||||
ToolArtifact.tool_version_id == tool_version_id,
|
||||
ToolArtifact.artifact_kind == self._normalize_kind(artifact_kind),
|
||||
)
|
||||
return self.db.execute(statement).scalar_one_or_none()
|
||||
|
||||
def create(
|
||||
self,
|
||||
*,
|
||||
draft_id: int,
|
||||
tool_version_id: int,
|
||||
tool_name: str,
|
||||
version_number: int,
|
||||
artifact_stage: ToolArtifactStage | str,
|
||||
artifact_kind: ToolArtifactKind | str,
|
||||
artifact_status: ToolArtifactStatus | str,
|
||||
summary: str,
|
||||
payload_json: dict,
|
||||
author_staff_account_id: int,
|
||||
author_display_name: str,
|
||||
storage_kind: ToolArtifactStorageKind | str = ToolArtifactStorageKind.INLINE_JSON,
|
||||
checksum: str | None = None,
|
||||
commit: bool = True,
|
||||
) -> ToolArtifact:
|
||||
normalized_kind = self._normalize_kind(artifact_kind)
|
||||
artifact = ToolArtifact(
|
||||
artifact_id=self.build_artifact_id(tool_name, version_number, normalized_kind),
|
||||
draft_id=draft_id,
|
||||
tool_version_id=tool_version_id,
|
||||
tool_name=str(tool_name or "").strip().lower(),
|
||||
version_number=int(version_number),
|
||||
artifact_stage=self._normalize_stage(artifact_stage),
|
||||
artifact_kind=normalized_kind,
|
||||
artifact_status=self._normalize_status(artifact_status),
|
||||
storage_kind=self._normalize_storage_kind(storage_kind),
|
||||
summary=str(summary or "").strip(),
|
||||
payload_json=dict(payload_json or {}),
|
||||
checksum=checksum or self._build_payload_checksum(payload_json),
|
||||
author_staff_account_id=author_staff_account_id,
|
||||
author_display_name=author_display_name,
|
||||
)
|
||||
self.db.add(artifact)
|
||||
if commit:
|
||||
self.db.commit()
|
||||
self.db.refresh(artifact)
|
||||
else:
|
||||
self.db.flush()
|
||||
return artifact
|
||||
|
||||
def update_artifact(
|
||||
self,
|
||||
artifact: ToolArtifact,
|
||||
*,
|
||||
artifact_status: ToolArtifactStatus | str,
|
||||
summary: str,
|
||||
payload_json: dict,
|
||||
author_staff_account_id: int,
|
||||
author_display_name: str,
|
||||
storage_kind: ToolArtifactStorageKind | str = ToolArtifactStorageKind.INLINE_JSON,
|
||||
checksum: str | None = None,
|
||||
commit: bool = True,
|
||||
) -> ToolArtifact:
|
||||
artifact.artifact_status = self._normalize_status(artifact_status)
|
||||
artifact.storage_kind = self._normalize_storage_kind(storage_kind)
|
||||
artifact.summary = str(summary or "").strip()
|
||||
artifact.payload_json = dict(payload_json or {})
|
||||
artifact.checksum = checksum or self._build_payload_checksum(payload_json)
|
||||
artifact.author_staff_account_id = author_staff_account_id
|
||||
artifact.author_display_name = author_display_name
|
||||
if commit:
|
||||
self.db.commit()
|
||||
self.db.refresh(artifact)
|
||||
else:
|
||||
self.db.flush()
|
||||
return artifact
|
||||
|
||||
def upsert_version_artifact(
|
||||
self,
|
||||
*,
|
||||
draft_id: int,
|
||||
tool_version_id: int,
|
||||
tool_name: str,
|
||||
version_number: int,
|
||||
artifact_stage: ToolArtifactStage | str,
|
||||
artifact_kind: ToolArtifactKind | str,
|
||||
artifact_status: ToolArtifactStatus | str,
|
||||
summary: str,
|
||||
payload_json: dict,
|
||||
author_staff_account_id: int,
|
||||
author_display_name: str,
|
||||
storage_kind: ToolArtifactStorageKind | str = ToolArtifactStorageKind.INLINE_JSON,
|
||||
checksum: str | None = None,
|
||||
commit: bool = True,
|
||||
) -> ToolArtifact:
|
||||
normalized_kind = self._normalize_kind(artifact_kind)
|
||||
existing = self.get_by_tool_version_and_kind(tool_version_id, normalized_kind)
|
||||
if existing is None:
|
||||
return self.create(
|
||||
draft_id=draft_id,
|
||||
tool_version_id=tool_version_id,
|
||||
tool_name=tool_name,
|
||||
version_number=version_number,
|
||||
artifact_stage=artifact_stage,
|
||||
artifact_kind=normalized_kind,
|
||||
artifact_status=artifact_status,
|
||||
summary=summary,
|
||||
payload_json=payload_json,
|
||||
author_staff_account_id=author_staff_account_id,
|
||||
author_display_name=author_display_name,
|
||||
storage_kind=storage_kind,
|
||||
checksum=checksum,
|
||||
commit=commit,
|
||||
)
|
||||
return self.update_artifact(
|
||||
existing,
|
||||
artifact_status=artifact_status,
|
||||
summary=summary,
|
||||
payload_json=payload_json,
|
||||
author_staff_account_id=author_staff_account_id,
|
||||
author_display_name=author_display_name,
|
||||
storage_kind=storage_kind,
|
||||
checksum=checksum,
|
||||
commit=commit,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def build_artifact_id(
|
||||
tool_name: str,
|
||||
version_number: int,
|
||||
artifact_kind: ToolArtifactKind | str,
|
||||
) -> str:
|
||||
normalized_tool_name = str(tool_name or "").strip().lower()
|
||||
normalized_kind = ToolArtifactRepository._normalize_kind(artifact_kind)
|
||||
return f"tool_artifact::{normalized_tool_name}::v{int(version_number)}::{normalized_kind.value}"
|
||||
|
||||
@staticmethod
|
||||
def _build_payload_checksum(payload_json: dict | None) -> str:
|
||||
canonical_payload = json.dumps(payload_json or {}, ensure_ascii=True, sort_keys=True, separators=(",", ":"))
|
||||
return hashlib.sha256(canonical_payload.encode("utf-8")).hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def _normalize_stage(value: ToolArtifactStage | str) -> ToolArtifactStage:
|
||||
if isinstance(value, ToolArtifactStage):
|
||||
return value
|
||||
return ToolArtifactStage(str(value or "").strip().lower())
|
||||
|
||||
@staticmethod
|
||||
def _normalize_kind(value: ToolArtifactKind | str) -> ToolArtifactKind:
|
||||
if isinstance(value, ToolArtifactKind):
|
||||
return value
|
||||
return ToolArtifactKind(str(value or "").strip().lower())
|
||||
|
||||
@staticmethod
|
||||
def _normalize_status(value: ToolArtifactStatus | str) -> ToolArtifactStatus:
|
||||
if isinstance(value, ToolArtifactStatus):
|
||||
return value
|
||||
return ToolArtifactStatus(str(value or "").strip().lower())
|
||||
|
||||
@staticmethod
|
||||
def _normalize_storage_kind(value: ToolArtifactStorageKind | str) -> ToolArtifactStorageKind:
|
||||
if isinstance(value, ToolArtifactStorageKind):
|
||||
return value
|
||||
return ToolArtifactStorageKind(str(value or "").strip().lower())
|
||||
@ -0,0 +1,144 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from admin_app.db.models import ToolMetadata
|
||||
from admin_app.repositories.base_repository import BaseRepository
|
||||
from shared.contracts import ToolLifecycleStatus
|
||||
|
||||
|
||||
class ToolMetadataRepository(BaseRepository):
|
||||
def list_metadata(
|
||||
self,
|
||||
*,
|
||||
tool_name: str | None = None,
|
||||
statuses: tuple[ToolLifecycleStatus, ...] | None = None,
|
||||
) -> list[ToolMetadata]:
|
||||
statement = select(ToolMetadata).order_by(
|
||||
ToolMetadata.version_number.desc(),
|
||||
ToolMetadata.updated_at.desc(),
|
||||
ToolMetadata.created_at.desc(),
|
||||
)
|
||||
if tool_name:
|
||||
statement = statement.where(ToolMetadata.tool_name == str(tool_name).strip().lower())
|
||||
if statuses:
|
||||
statement = statement.where(ToolMetadata.status.in_(statuses))
|
||||
return list(self.db.execute(statement).scalars().all())
|
||||
|
||||
def get_by_tool_version_id(self, tool_version_id: int) -> ToolMetadata | None:
|
||||
statement = select(ToolMetadata).where(ToolMetadata.tool_version_id == tool_version_id)
|
||||
return self.db.execute(statement).scalar_one_or_none()
|
||||
|
||||
def create(
|
||||
self,
|
||||
*,
|
||||
draft_id: int,
|
||||
tool_version_id: int,
|
||||
tool_name: str,
|
||||
display_name: str,
|
||||
domain: str,
|
||||
description: str,
|
||||
parameters_json: list[dict],
|
||||
version_number: int,
|
||||
status: ToolLifecycleStatus,
|
||||
author_staff_account_id: int,
|
||||
author_display_name: str,
|
||||
commit: bool = True,
|
||||
) -> ToolMetadata:
|
||||
metadata = ToolMetadata(
|
||||
metadata_id=self.build_metadata_id(tool_name, version_number),
|
||||
draft_id=draft_id,
|
||||
tool_version_id=tool_version_id,
|
||||
tool_name=tool_name,
|
||||
display_name=display_name,
|
||||
domain=domain,
|
||||
description=description,
|
||||
parameters_json=parameters_json,
|
||||
version_number=version_number,
|
||||
status=status,
|
||||
author_staff_account_id=author_staff_account_id,
|
||||
author_display_name=author_display_name,
|
||||
)
|
||||
self.db.add(metadata)
|
||||
if commit:
|
||||
self.db.commit()
|
||||
self.db.refresh(metadata)
|
||||
else:
|
||||
self.db.flush()
|
||||
return metadata
|
||||
|
||||
def update_metadata(
|
||||
self,
|
||||
metadata: ToolMetadata,
|
||||
*,
|
||||
display_name: str,
|
||||
domain: str,
|
||||
description: str,
|
||||
parameters_json: list[dict],
|
||||
status: ToolLifecycleStatus,
|
||||
author_staff_account_id: int,
|
||||
author_display_name: str,
|
||||
commit: bool = True,
|
||||
) -> ToolMetadata:
|
||||
metadata.display_name = display_name
|
||||
metadata.domain = domain
|
||||
metadata.description = description
|
||||
metadata.parameters_json = parameters_json
|
||||
metadata.status = status
|
||||
metadata.author_staff_account_id = author_staff_account_id
|
||||
metadata.author_display_name = author_display_name
|
||||
if commit:
|
||||
self.db.commit()
|
||||
self.db.refresh(metadata)
|
||||
else:
|
||||
self.db.flush()
|
||||
return metadata
|
||||
|
||||
def upsert_version_metadata(
|
||||
self,
|
||||
*,
|
||||
draft_id: int,
|
||||
tool_version_id: int,
|
||||
tool_name: str,
|
||||
display_name: str,
|
||||
domain: str,
|
||||
description: str,
|
||||
parameters_json: list[dict],
|
||||
version_number: int,
|
||||
status: ToolLifecycleStatus,
|
||||
author_staff_account_id: int,
|
||||
author_display_name: str,
|
||||
commit: bool = True,
|
||||
) -> ToolMetadata:
|
||||
existing = self.get_by_tool_version_id(tool_version_id)
|
||||
if existing is None:
|
||||
return self.create(
|
||||
draft_id=draft_id,
|
||||
tool_version_id=tool_version_id,
|
||||
tool_name=tool_name,
|
||||
display_name=display_name,
|
||||
domain=domain,
|
||||
description=description,
|
||||
parameters_json=parameters_json,
|
||||
version_number=version_number,
|
||||
status=status,
|
||||
author_staff_account_id=author_staff_account_id,
|
||||
author_display_name=author_display_name,
|
||||
commit=commit,
|
||||
)
|
||||
return self.update_metadata(
|
||||
existing,
|
||||
display_name=display_name,
|
||||
domain=domain,
|
||||
description=description,
|
||||
parameters_json=parameters_json,
|
||||
status=status,
|
||||
author_staff_account_id=author_staff_account_id,
|
||||
author_display_name=author_display_name,
|
||||
commit=commit,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def build_metadata_id(tool_name: str, version_number: int) -> str:
|
||||
normalized_tool_name = str(tool_name or "").strip().lower()
|
||||
return f"tool_metadata::{normalized_tool_name}::v{int(version_number)}"
|
||||
@ -0,0 +1,3 @@
|
||||
# Generated Tools
|
||||
|
||||
Diretorio isolado para modulos publicados pelo fluxo administrativo de tools.
|
||||
@ -0,0 +1 @@
|
||||
"""Isolated runtime package for admin-governed generated tools."""
|
||||
@ -0,0 +1,61 @@
|
||||
import unittest
|
||||
|
||||
from admin_app.db.models import ToolArtifact
|
||||
from admin_app.db.models.tool_artifact import (
|
||||
ToolArtifactKind,
|
||||
ToolArtifactStage,
|
||||
ToolArtifactStatus,
|
||||
ToolArtifactStorageKind,
|
||||
)
|
||||
|
||||
|
||||
class ToolArtifactModelTests(unittest.TestCase):
|
||||
def test_tool_artifact_declares_expected_table_and_columns(self):
|
||||
self.assertEqual(ToolArtifact.__tablename__, "tool_artifacts")
|
||||
self.assertIn("artifact_id", ToolArtifact.__table__.columns)
|
||||
self.assertIn("draft_id", ToolArtifact.__table__.columns)
|
||||
self.assertIn("tool_version_id", ToolArtifact.__table__.columns)
|
||||
self.assertIn("tool_name", ToolArtifact.__table__.columns)
|
||||
self.assertIn("version_number", ToolArtifact.__table__.columns)
|
||||
self.assertIn("artifact_stage", ToolArtifact.__table__.columns)
|
||||
self.assertIn("artifact_kind", ToolArtifact.__table__.columns)
|
||||
self.assertIn("artifact_status", ToolArtifact.__table__.columns)
|
||||
self.assertIn("storage_kind", ToolArtifact.__table__.columns)
|
||||
self.assertIn("summary", ToolArtifact.__table__.columns)
|
||||
self.assertIn("payload_json", ToolArtifact.__table__.columns)
|
||||
self.assertIn("checksum", ToolArtifact.__table__.columns)
|
||||
self.assertIn("author_staff_account_id", ToolArtifact.__table__.columns)
|
||||
self.assertIn("author_display_name", ToolArtifact.__table__.columns)
|
||||
|
||||
def test_tool_artifact_uses_expected_constraints_and_defaults(self):
|
||||
draft_foreign_keys = list(ToolArtifact.__table__.columns["draft_id"].foreign_keys)
|
||||
self.assertEqual(len(draft_foreign_keys), 1)
|
||||
self.assertEqual(str(draft_foreign_keys[0].target_fullname), "tool_drafts.id")
|
||||
|
||||
version_foreign_keys = list(ToolArtifact.__table__.columns["tool_version_id"].foreign_keys)
|
||||
self.assertEqual(len(version_foreign_keys), 1)
|
||||
self.assertEqual(str(version_foreign_keys[0].target_fullname), "tool_versions.id")
|
||||
|
||||
author_foreign_keys = list(ToolArtifact.__table__.columns["author_staff_account_id"].foreign_keys)
|
||||
self.assertEqual(len(author_foreign_keys), 1)
|
||||
self.assertEqual(str(author_foreign_keys[0].target_fullname), "staff_accounts.id")
|
||||
|
||||
status_column = ToolArtifact.__table__.columns["artifact_status"]
|
||||
self.assertEqual(status_column.default.arg, ToolArtifactStatus.PENDING)
|
||||
self.assertEqual(status_column.type.process_bind_param("succeeded", None), "succeeded")
|
||||
self.assertEqual(status_column.type.process_result_value("pending", None), ToolArtifactStatus.PENDING)
|
||||
|
||||
stage_column = ToolArtifact.__table__.columns["artifact_stage"]
|
||||
kind_column = ToolArtifact.__table__.columns["artifact_kind"]
|
||||
storage_column = ToolArtifact.__table__.columns["storage_kind"]
|
||||
self.assertEqual(stage_column.type.process_bind_param("validation", None), "validation")
|
||||
self.assertEqual(kind_column.type.process_result_value("generation_request", None), ToolArtifactKind.GENERATION_REQUEST)
|
||||
self.assertEqual(storage_column.default.arg, ToolArtifactStorageKind.INLINE_JSON)
|
||||
self.assertEqual(storage_column.type.process_result_value("inline_json", None), ToolArtifactStorageKind.INLINE_JSON)
|
||||
|
||||
unique_constraints = {constraint.name for constraint in ToolArtifact.__table__.constraints}
|
||||
self.assertIn("uq_tool_artifacts_tool_version_kind", unique_constraints)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@ -0,0 +1,49 @@
|
||||
import unittest
|
||||
|
||||
from admin_app.db.models import ToolMetadata
|
||||
from shared.contracts import ToolLifecycleStatus
|
||||
|
||||
|
||||
class ToolMetadataModelTests(unittest.TestCase):
|
||||
def test_tool_metadata_declares_expected_table_and_columns(self):
|
||||
self.assertEqual(ToolMetadata.__tablename__, "tool_metadata")
|
||||
self.assertIn("metadata_id", ToolMetadata.__table__.columns)
|
||||
self.assertIn("draft_id", ToolMetadata.__table__.columns)
|
||||
self.assertIn("tool_version_id", ToolMetadata.__table__.columns)
|
||||
self.assertIn("tool_name", ToolMetadata.__table__.columns)
|
||||
self.assertIn("display_name", ToolMetadata.__table__.columns)
|
||||
self.assertIn("domain", ToolMetadata.__table__.columns)
|
||||
self.assertIn("description", ToolMetadata.__table__.columns)
|
||||
self.assertIn("parameters_json", ToolMetadata.__table__.columns)
|
||||
self.assertIn("version_number", ToolMetadata.__table__.columns)
|
||||
self.assertIn("status", ToolMetadata.__table__.columns)
|
||||
self.assertIn("author_staff_account_id", ToolMetadata.__table__.columns)
|
||||
self.assertIn("author_display_name", ToolMetadata.__table__.columns)
|
||||
self.assertIn("created_at", ToolMetadata.__table__.columns)
|
||||
self.assertIn("updated_at", ToolMetadata.__table__.columns)
|
||||
|
||||
def test_tool_metadata_uses_expected_constraints_and_defaults(self):
|
||||
draft_foreign_keys = list(ToolMetadata.__table__.columns["draft_id"].foreign_keys)
|
||||
self.assertEqual(len(draft_foreign_keys), 1)
|
||||
self.assertEqual(str(draft_foreign_keys[0].target_fullname), "tool_drafts.id")
|
||||
|
||||
version_foreign_keys = list(ToolMetadata.__table__.columns["tool_version_id"].foreign_keys)
|
||||
self.assertEqual(len(version_foreign_keys), 1)
|
||||
self.assertEqual(str(version_foreign_keys[0].target_fullname), "tool_versions.id")
|
||||
|
||||
author_foreign_keys = list(ToolMetadata.__table__.columns["author_staff_account_id"].foreign_keys)
|
||||
self.assertEqual(len(author_foreign_keys), 1)
|
||||
self.assertEqual(str(author_foreign_keys[0].target_fullname), "staff_accounts.id")
|
||||
|
||||
status_column = ToolMetadata.__table__.columns["status"]
|
||||
self.assertEqual(status_column.default.arg, ToolLifecycleStatus.DRAFT)
|
||||
self.assertEqual(status_column.type.process_bind_param("archived", None), "archived")
|
||||
self.assertEqual(status_column.type.process_result_value("draft", None), ToolLifecycleStatus.DRAFT)
|
||||
|
||||
unique_constraints = {constraint.name for constraint in ToolMetadata.__table__.constraints}
|
||||
self.assertIn("uq_tool_metadata_tool_name_version_number", unique_constraints)
|
||||
self.assertTrue(ToolMetadata.__table__.columns["tool_version_id"].unique)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in New Issue