# tests/stubs/single_choice_question.py from dataclasses import dataclass, field import pytest import platform from pathlib import Path from datetime import datetime from typing import List from flexoentity import FlexOID, FlexoEntity, EntityType, EntityState, Domain, get_signing_backend, CertificateReference @pytest.fixture def fixed_datetime(monkeypatch): class FixedDate(datetime): @classmethod def now(cls, tz=None): return datetime(2025, 11, 1, tzinfo=tz) monkeypatch.setattr("flexoentity.id_factory.datetime", FixedDate) return FixedDate @dataclass class AnswerOption: id: str text: str points: float = 0.0 def to_dict(self): return {"id": self.id, "text": self.text, "points": self.points} @classmethod def from_dict(cls, data): return cls( id=data.get("id", ""), text=data.get("text", ""), points=data.get("points", 0.0) ) @dataclass class SingleChoiceQuestion(FlexoEntity): """A minimal stub to test FlexoEntity integration.""" ENTITY_TYPE = EntityType.ITEM text: str = "" options: List[AnswerOption] = field(default_factory=list) def __post_init__(self): # If no FlexOID yet, generate a draft ID now. if not getattr(self, "flexo_id", None): self.flexo_id = FlexOID.safe_generate( domain_id=self.domain_id, entity_type=SingleChoiceQuestion.ENTITY_TYPE.value, # 'I' state=EntityState.DRAFT.value, # 'D' text=self.text_seed or self.text, version=1, ) @classmethod def default(cls): return cls() def to_dict(self): base = super().to_dict() base.update({ "text": self.text, "options": [opt.to_dict() for opt in self.options], }) return base @property def text_seed(self) -> str: """Include answer options (and points) for deterministic ID generation.""" joined = "|".join( f"{opt.text.strip()}:{opt.points}" for opt in sorted(self.options, key=lambda o: o.text.strip().lower()) ) return f"{self.text}{joined}" @classmethod def from_dict(cls, data): obj = cls(text=data.get("text", ""), options=[AnswerOption.from_dict(o) for o in data.get("options", [])], ) # restore FlexoEntity core fields if "flexo_id" in data: obj.flexo_id = FlexOID.to_dict(data["flexo_id"]) return obj @pytest.fixture def sample_domain(): domain_id = "PY_ARITHM" return Domain.with_domain_id(domain_id=domain_id, fullname="PYTHON_ARITHMETIC", description="ALL ABOUT ARITHMETIC IN PYTHON") @pytest.fixture def sample_question(sample_domain): q = SingleChoiceQuestion.with_domain_id(domain_id=sample_domain.domain_id, text="What is 2 + 2?", options=[]) q._update_fingerprint() return q SYSTEM = platform.system() # ───────────────────────────────────────────────────────────── # Basic test data directory + PEM test files # ───────────────────────────────────────────────────────────── @pytest.fixture(scope="session") def test_data_dir(): return Path(__file__).parent / "data" @pytest.fixture(scope="session") def test_cert(test_data_dir): return test_data_dir / "testcert.pem" @pytest.fixture(scope="session") def test_key(test_data_dir): return test_data_dir / "testkey.pem" # ───────────────────────────────────────────────────────────── # CertificateReference fixtures for each platform # ───────────────────────────────────────────────────────────── @pytest.fixture(scope="session") def cert_ref_linux(test_cert, test_key): """Linux: Uses OpenSSL CMS with PEM cert + PEM private key.""" return CertificateReference( platform="LINUX", identifier=str(test_cert), private_key_path=str(test_key), public_cert_path=str(test_cert), ) @pytest.fixture(scope="session") def cert_ref_macos(test_cert): """ macOS: Uses Keychain identity with Common Name (CN). The test cert must be imported into the login keychain with CN=FlexOSignerTest. """ return CertificateReference( platform="MACOS", identifier="FlexOSignerTest", public_cert_path=str(test_cert), ) @pytest.fixture(scope="session") def backend(test_cert, test_key): """Return the correct backend for the current platform.""" if SYSTEM == "Linux": cert_ref = CertificateReference( platform="LINUX", identifier=str(test_cert), private_key_path=str(test_key), public_cert_path=str(test_cert), ) elif SYSTEM == "Darwin": cert_ref = CertificateReference( platform="MACOS", identifier="FlexOSignerTest", public_cert_path=str(test_cert), ) elif SYSTEM == "Windows": pytest.skip("Windows signing tests not implemented yet") else: pytest.skip(f"Unsupported platform: {SYSTEM}") try: backend = get_signing_backend(cert_ref) # sanity check: ensures cert exists and command is available _ = backend.certificate_thumbprint return backend except Exception as e: pytest.skip(f"Backend unavailable or misconfigured: {e}")