|
10 | 10 | from alembic.config import Config |
11 | 11 | from dotenv import load_dotenv |
12 | 12 | from fastapi.testclient import TestClient |
| 13 | +from numpy.random import Generator as NpGenerator |
| 14 | +from numpy.random import default_rng |
13 | 15 | from sqlalchemy.ext.asyncio import ( |
14 | 16 | AsyncEngine, |
15 | 17 | AsyncSession, |
|
18 | 20 | ) |
19 | 21 | from sqlalchemy_utils import create_database, database_exists, drop_database |
20 | 22 |
|
| 23 | +from app.config.constants import ( |
| 24 | + EventMetricName, |
| 25 | + SampledMetricName, |
| 26 | + TimeDefaults, |
| 27 | +) |
21 | 28 | from app.config.settings import settings |
22 | 29 | from app.db.session import get_db |
23 | 30 | from app.main import app |
| 31 | +from app.schemas.full_simulation_input import SimulationPayload |
| 32 | +from app.schemas.random_variables_config import RVConfig |
| 33 | +from app.schemas.requests_generator_input import RqsGeneratorInput |
| 34 | +from app.schemas.simulation_settings_input import SimulationSettings |
| 35 | +from app.schemas.system_topology_schema.full_system_topology_schema import ( |
| 36 | + Client, |
| 37 | + TopologyGraph, |
| 38 | + TopologyNodes, |
| 39 | +) |
24 | 40 |
|
25 | 41 | # Load test environment variables from .env.test |
26 | 42 | ENV_PATH = Path(__file__).resolve().parents[1] / "docker_fs" / ".env.test" |
@@ -121,3 +137,102 @@ async def db_session(async_engine: AsyncEngine) -> AsyncGenerator[AsyncSession, |
121 | 137 | await transaction.rollback() |
122 | 138 | # Close the connection |
123 | 139 | await connection.close() |
| 140 | + |
| 141 | +# ============================================================================ |
| 142 | +# STANDARD CONFIGURATION FOR INPUT VARIABLES |
| 143 | +# ============================================================================ |
| 144 | + |
| 145 | +# --------------------------------------------------------------------------- |
| 146 | +# RNG |
| 147 | +# --------------------------------------------------------------------------- |
| 148 | + |
| 149 | + |
| 150 | +@pytest.fixture(scope="session") |
| 151 | +def rng() -> NpGenerator: |
| 152 | + """Deterministic NumPy RNG shared across tests (seed=0).""" |
| 153 | + return default_rng(0) |
| 154 | + |
| 155 | + |
| 156 | +# --------------------------------------------------------------------------- |
| 157 | +# Metrics sets |
| 158 | +# --------------------------------------------------------------------------- |
| 159 | + |
| 160 | + |
| 161 | +@pytest.fixture(scope="session") |
| 162 | +def enabled_sample_metrics() -> set[SampledMetricName]: |
| 163 | + """Default sample-level KPIs tracked in most tests.""" |
| 164 | + return { |
| 165 | + SampledMetricName.READY_QUEUE_LEN, |
| 166 | + SampledMetricName.RAM_IN_USE, |
| 167 | + } |
| 168 | + |
| 169 | + |
| 170 | +@pytest.fixture(scope="session") |
| 171 | +def enabled_event_metrics() -> set[EventMetricName]: |
| 172 | + """Default event-level KPIs tracked in most tests.""" |
| 173 | + return {EventMetricName.RQS_LATENCY} |
| 174 | + |
| 175 | + |
| 176 | +# --------------------------------------------------------------------------- |
| 177 | +# Global simulation settings |
| 178 | +# --------------------------------------------------------------------------- |
| 179 | + |
| 180 | + |
| 181 | +@pytest.fixture |
| 182 | +def sim_settings( |
| 183 | + enabled_sample_metrics: set[SampledMetricName], |
| 184 | + enabled_event_metrics: set[EventMetricName], |
| 185 | +) -> SimulationSettings: |
| 186 | + """A minimal `SimulationSettings` instance for unit tests.""" |
| 187 | + return SimulationSettings( |
| 188 | + total_simulation_time=TimeDefaults.MIN_SIMULATION_TIME, |
| 189 | + enabled_sample_metrics=enabled_sample_metrics, |
| 190 | + enabled_event_metrics=enabled_event_metrics, |
| 191 | + ) |
| 192 | + |
| 193 | + |
| 194 | +# --------------------------------------------------------------------------- |
| 195 | +# Traffic profile |
| 196 | +# --------------------------------------------------------------------------- |
| 197 | + |
| 198 | + |
| 199 | +@pytest.fixture |
| 200 | +def rqs_input() -> RqsGeneratorInput: |
| 201 | + """`RqsGeneratorInput` with 1 user and 2 req/min for quick tests.""" |
| 202 | + return RqsGeneratorInput( |
| 203 | + avg_active_users=RVConfig(mean=1.0), |
| 204 | + avg_request_per_minute_per_user=RVConfig(mean=2.0), |
| 205 | + user_sampling_window=TimeDefaults.USER_SAMPLING_WINDOW, |
| 206 | + ) |
| 207 | + |
| 208 | + |
| 209 | +# --------------------------------------------------------------------------- |
| 210 | +# Minimal topology (one client, no servers, no edges) |
| 211 | +# --------------------------------------------------------------------------- |
| 212 | + |
| 213 | + |
| 214 | +@pytest.fixture |
| 215 | +def topology_minimal() -> TopologyGraph: |
| 216 | + """Valid topology with a single client and zero servers/edges.""" |
| 217 | + client = Client(id="client-1") |
| 218 | + nodes = TopologyNodes(servers=[], client=client) |
| 219 | + return TopologyGraph(nodes=nodes, edges=[]) |
| 220 | + |
| 221 | + |
| 222 | +# --------------------------------------------------------------------------- |
| 223 | +# Full simulation payload |
| 224 | +# --------------------------------------------------------------------------- |
| 225 | + |
| 226 | + |
| 227 | +@pytest.fixture |
| 228 | +def payload_base( |
| 229 | + rqs_input: RqsGeneratorInput, |
| 230 | + sim_settings: SimulationSettings, |
| 231 | + topology_minimal: TopologyGraph, |
| 232 | +) -> SimulationPayload: |
| 233 | + """End-to-end payload used by high-level simulation tests.""" |
| 234 | + return SimulationPayload( |
| 235 | + rqs_input=rqs_input, |
| 236 | + topology_graph=topology_minimal, |
| 237 | + sim_settings=sim_settings, |
| 238 | + ) |
0 commit comments