source: flexoentity/tests/conftest.py@ 9a50e0b

main unify_backends
Last change on this file since 9a50e0b was e458b5a, checked in by Enrico Schwass <ennoausberlin@…>, 3 months ago

fix Domain and FlexoSignature and tests to reflect changes to serialization

  • Property mode set to 100644
File size: 5.8 KB
RevLine 
[8aa20c7]1# tests/stubs/single_choice_question.py
[d7499ca]2from dataclasses import dataclass, field
[8a238e2]3import pytest
[d7499ca]4import platform
5from pathlib import Path
[8aa20c7]6from datetime import datetime
7from typing import List
[d7499ca]8from flexoentity import FlexOID, FlexoEntity, EntityType, EntityState, Domain, get_signing_backend, CertificateReference
9
[8a238e2]10
[8aa20c7]11@pytest.fixture
12def fixed_datetime(monkeypatch):
13 class FixedDate(datetime):
14 @classmethod
15 def now(cls, tz=None):
16 return datetime(2025, 11, 1, tzinfo=tz)
17 monkeypatch.setattr("flexoentity.id_factory.datetime", FixedDate)
18 return FixedDate
[02d288d]19
[8aa20c7]20@dataclass
21class AnswerOption:
22 id: str
23 text: str
24 points: float = 0.0
[02d288d]25
[8aa20c7]26 def to_dict(self):
27 return {"id": self.id, "text": self.text, "points": self.points}
[8a238e2]28
[8aa20c7]29 @classmethod
30 def from_dict(cls, data):
31 return cls(
32 id=data.get("id", ""),
33 text=data.get("text", ""),
34 points=data.get("points", 0.0)
35 )
[02d288d]36
37
[8aa20c7]38@dataclass
39class SingleChoiceQuestion(FlexoEntity):
40 """A minimal stub to test FlexoEntity integration."""
[5c72356]41 ENTITY_TYPE = EntityType.ITEM
42
[8aa20c7]43 text: str = ""
44 options: List[AnswerOption] = field(default_factory=list)
[02d288d]45
[5c72356]46 def __post_init__(self):
47 # If no FlexOID yet, generate a draft ID now.
48 if not getattr(self, "flexo_id", None):
49 self.flexo_id = FlexOID.safe_generate(
[9592936]50 domain_id=self.domain_id,
[269fdc2]51 entity_type=SingleChoiceQuestion.ENTITY_TYPE.value, # 'I'
[9592936]52 state=EntityState.DRAFT.value, # 'D'
[5c72356]53 text=self.text_seed or self.text,
54 version=1,
55 )
[02d288d]56
[8aa20c7]57 @classmethod
58 def default(cls):
[269fdc2]59 return cls()
[02d288d]60
[e458b5a]61 def _serialize_content(self):
62 return {
[8aa20c7]63 "text": self.text,
64 "options": [opt.to_dict() for opt in self.options],
[e458b5a]65 }
[8aa20c7]66
67 @property
68 def text_seed(self) -> str:
69 """Include answer options (and points) for deterministic ID generation."""
70
71 joined = "|".join(
72 f"{opt.text.strip()}:{opt.points}"
73 for opt in sorted(self.options, key=lambda o: o.text.strip().lower())
74 )
75 return f"{self.text}{joined}"
76
77 @classmethod
78 def from_dict(cls, data):
[269fdc2]79 obj = cls(text=data.get("text", ""),
[8aa20c7]80 options=[AnswerOption.from_dict(o) for o in data.get("options", [])],
81 )
82 # restore FlexoEntity core fields
83 if "flexo_id" in data:
[fd1913f]84 obj.flexo_id = FlexOID.to_dict(data["flexo_id"])
[8aa20c7]85 return obj
86
87@pytest.fixture
[6ad031b]88def sample_domain():
89 domain_id = "PY_ARITHM"
[9592936]90 return Domain.with_domain_id(domain_id=domain_id,
91 fullname="PYTHON_ARITHMETIC",
92 description="ALL ABOUT ARITHMETIC IN PYTHON")
[3d16c35]93
[524040a]94@pytest.fixture
[6ad031b]95def sample_question(sample_domain):
[9592936]96 q = SingleChoiceQuestion.with_domain_id(domain_id=sample_domain.domain_id,
97 text="What is 2 + 2?",
98 options=[])
[5c72356]99 q._update_fingerprint()
100 return q
[d7499ca]101
102SYSTEM = platform.system()
103
104
105# ─────────────────────────────────────────────────────────────
106# Basic test data directory + PEM test files
107# ─────────────────────────────────────────────────────────────
108
109@pytest.fixture(scope="session")
110def test_data_dir():
111 return Path(__file__).parent / "data"
112
113
114@pytest.fixture(scope="session")
115def test_cert(test_data_dir):
116 return test_data_dir / "testcert.pem"
117
118
119@pytest.fixture(scope="session")
120def test_key(test_data_dir):
121 return test_data_dir / "testkey.pem"
122
123
124# ─────────────────────────────────────────────────────────────
125# CertificateReference fixtures for each platform
126# ─────────────────────────────────────────────────────────────
127
128@pytest.fixture(scope="session")
129def cert_ref_linux(test_cert, test_key):
130 """Linux: Uses OpenSSL CMS with PEM cert + PEM private key."""
131 return CertificateReference(
132 platform="LINUX",
133 identifier=str(test_cert),
134 private_key_path=str(test_key),
135 public_cert_path=str(test_cert),
136 )
137
138
139@pytest.fixture(scope="session")
140def cert_ref_macos(test_cert):
141 """
142 macOS: Uses Keychain identity with Common Name (CN).
143 The test cert must be imported into the login keychain with CN=FlexOSignerTest.
144 """
145 return CertificateReference(
146 platform="MACOS",
147 identifier="FlexOSignerTest",
148 public_cert_path=str(test_cert),
149 )
150
151@pytest.fixture(scope="session")
152def backend(test_cert, test_key):
153 """Return the correct backend for the current platform."""
154
155 if SYSTEM == "Linux":
156 cert_ref = CertificateReference(
157 platform="LINUX",
158 identifier=str(test_cert),
159 private_key_path=str(test_key),
160 public_cert_path=str(test_cert),
161 )
162
163 elif SYSTEM == "Darwin":
164 cert_ref = CertificateReference(
165 platform="MACOS",
166 identifier="FlexOSignerTest",
167 public_cert_path=str(test_cert),
168 )
169
170 elif SYSTEM == "Windows":
171 pytest.skip("Windows signing tests not implemented yet")
172
173 else:
174 pytest.skip(f"Unsupported platform: {SYSTEM}")
175
176 try:
177 backend = get_signing_backend(cert_ref)
178 # sanity check: ensures cert exists and command is available
179 _ = backend.certificate_thumbprint
180 return backend
181 except Exception as e:
182 pytest.skip(f"Backend unavailable or misconfigured: {e}")
Note: See TracBrowser for help on using the repository browser.