import uuid from sqlalchemy import Column, String, Text, Boolean, Integer, DateTime, ForeignKey, Enum, Index, func from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship from app.database import Base from app.models.enums import TestState, TestResult class Test(Base): """ Test model representing a security test for a MITRE ATT&CK technique. Each test documents an attempt to validate coverage of a specific technique, including the procedure, tools used, and outcome. V2 introduces dual validation: Red Lead and Blue Lead must each approve independently. """ __tablename__ = "tests" # ── Core fields ───────────────────────────────────────────────── id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) technique_id = Column(UUID(as_uuid=True), ForeignKey("techniques.id"), nullable=False) name = Column(String, nullable=False) description = Column(Text, nullable=True) platform = Column(String, nullable=True) procedure_text = Column(Text, nullable=True) tool_used = Column(String, nullable=True) execution_date = Column(DateTime, nullable=True) created_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True) result = Column(Enum(TestResult, name="testresult"), nullable=True) state = Column(Enum(TestState, name="teststate"), default=TestState.draft) created_at = Column(DateTime(timezone=True), server_default=func.now()) # ── Red Team fields ───────────────────────────────────────────── red_summary = Column(Text, nullable=True) attack_success = Column(Boolean, nullable=True) red_validated_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True) red_validated_at = Column(DateTime, nullable=True) red_validation_status = Column(String, nullable=True) # pending / approved / rejected red_validation_notes = Column(Text, nullable=True) # ── Blue Team fields ──────────────────────────────────────────── blue_summary = Column(Text, nullable=True) detection_result = Column(Enum(TestResult, name="testresult"), nullable=True) blue_validated_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True) blue_validated_at = Column(DateTime, nullable=True) blue_validation_status = Column(String, nullable=True) # pending / approved / rejected blue_validation_notes = Column(Text, nullable=True) # ── Phase timing fields (for automatic Tempo worklogs) ────────── red_started_at = Column(DateTime, nullable=True) blue_started_at = Column(DateTime, nullable=True) blue_work_started_at = Column(DateTime, nullable=True) # when blue tech picks up (Tempo start) paused_at = Column(DateTime, nullable=True) red_paused_seconds = Column(Integer, default=0) blue_paused_seconds = Column(Integer, default=0) # ── Remediation fields ─────────────────────────────────────────── remediation_steps = Column(Text, nullable=True) remediation_status = Column(String, nullable=True) # pending / in_progress / completed / not_applicable remediation_assignee = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True) # ── Re-test fields ──────────────────────────────────────────── retest_of = Column(UUID(as_uuid=True), ForeignKey("tests.id"), nullable=True) retest_count = Column(Integer, default=0) data_classification = Column(String(20), nullable=False, server_default="internal") # ── Relationships ─────────────────────────────────────────────── technique = relationship("Technique", back_populates="tests") evidences = relationship("Evidence", back_populates="test") creator = relationship("User", foreign_keys=[created_by]) red_validator = relationship("User", foreign_keys=[red_validated_by]) blue_validator = relationship("User", foreign_keys=[blue_validated_by]) remediation_user = relationship("User", foreign_keys=[remediation_assignee]) original_test = relationship("Test", remote_side="Test.id", foreign_keys=[retest_of]) retests = relationship("Test", foreign_keys=[retest_of], back_populates="original_test") __table_args__ = ( Index("ix_tests_technique_id", "technique_id"), Index("ix_tests_state", "state"), Index("ix_tests_created_at", "created_at"), Index("ix_tests_technique_state", "technique_id", "state"), Index("ix_tests_state_created_at", "state", "created_at"), )