Files
ss-tools/backend/tests/plugins/test_load_testing_scope.py
busya 488a8f349b test(backend): raise coverage to 95%+ statements and branches (97.8%/95.0%)
- ~60 new/extended test files across api, core, plugins, services, schemas:
  routes, superset clients, task_manager, lineage, git, translate,
  dashboard-testing, load-testing, migration, llm_analysis, scheduler, ssl
- .coveragerc: enable branch coverage; exclude src/__tests__ (test files)
  and src/scripts (CLI/ops tools) from the denominator
- bug fixes found while testing:
  * settings: PUT /settings/reports registered under duplicated prefix
  * schemas/lineage: FleetReportDTO missing run_status (route always 500)
  * dashboard_testing/baseline_inheritance: visual entry read wrong field
  * superset_client/_databases: logger extra name shadowed LogRecord attr
  * routes/datasets: _yaml_string_paths recursion without yield from
  * translate/sql_generator: restore explicit-type timestamp contract
  * baseline_catalog: remove unreachable dashboard_id fallback
- conftest fixes: pytest_plugins to rootdir conftest (pytest 9), test
  filename collision, TMPDIR-safe integration fixtures
2026-08-19 17:14:32 +03:00

598 lines
28 KiB
Python

# #region Test.LoadTesting.Scope [C:3] [TYPE Module] [SEMANTICS test,load-testing,scope,pool,start]
# @BRIEF Verify _resolve_execution_scope (env missing / specs / query-model / failure), the
# bounded-pool drain with batched persistence, variation loading, breaker factory, phase
# persistence, status percent, reload guard, _now, and start_load_run task dispatch.
# @RELATION BINDS_TO -> [Plugin.LoadTesting.LoadTestingPlugin]
# @TEST_EDGE: env_missing -> scope None (lifecycle-only)
# @TEST_EDGE: query_model_failure -> scope None
# @TEST_EDGE: no_charts -> scope None
# @TEST_EDGE: no_items -> scope None
# @TEST_EDGE: run_disappeared -> _reload_run raises RuntimeError
# @TEST_EDGE: task_manager_failure -> start_load_run propagates
# @TEST_EDGE: zero_capacity -> pool capacity floors at 1
# @TEST_INVARIANT: execution_specs_acknowledged_bulk -> VERIFIED_BY: test_scope_specs_branch, test_pool_drains_records
import asyncio
from datetime import datetime
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from sqlalchemy import create_engine, event
from sqlalchemy.orm import Session
from src.models.load_testing import LoadRun, LoadVariation, RunPhase, RunStatus
from src.models.mapping import Base
from src.plugins.load_testing import (
_build_breaker,
_load_variations,
_now,
_persist_phase,
_reload_run,
_resolve_execution_scope,
_run_bounded_pool,
_status_percent,
start_load_run,
)
from src.services.load_testing.breaker import CircuitBreaker
# #region Test.LoadTesting.Scope.Fakes [C:1] [TYPE Class]
# @BRIEF Hardcoded fakes: query-model charts, RunnerPool stand-in, task manager.
class _FakeChart:
def __init__(self, chart_id, dataset_id, metrics=None):
self.chart_id = chart_id
self.dataset_id = dataset_id
self.metrics = metrics or []
class _FakeQueryModel:
def __init__(self, charts):
self.charts = charts
class _FakeRunnerPool:
"""RunnerPool stand-in: records may be configured per test; on_result is awaited per item."""
def __init__(self, *, capacity, executor, env_capacity_semaphore, breaker, on_result):
self.capacity = capacity
self.executor = executor
self.env_capacity_semaphore = env_capacity_semaphore
self.breaker = breaker
self.on_result = on_result
self.records = []
self.results = []
async def run(self, items):
self.results = []
for i, it in enumerate(items):
record = dict(self.records[i]) if i < len(self.records) else {}
record.setdefault("execution_id", it.get("execution_id"))
if self.on_result is not None:
await self.on_result(record)
self.results.append(record)
return self.results
class _FakeTaskManager:
def __init__(self, task_id="task-1"):
self.task_id = task_id
self.calls = []
async def create_task(self, plugin_id, params):
self.calls.append((plugin_id, params))
return SimpleNamespace(id=self.task_id)
class _FailingTaskManager:
async def create_task(self, plugin_id, params):
raise RuntimeError("task manager down")
# #endregion Test.LoadTesting.Scope.Fakes
# #region Test.LoadTesting.Scope.Db [C:1] [TYPE Function]
# @BRIEF Build an isolated FK-enforced in-memory SQLite session with all tables.
@pytest.fixture()
def db():
engine = create_engine("sqlite:///:memory:", connect_args={"check_same_thread": False})
event.listen(engine, "connect", lambda c, _: c.execute("PRAGMA foreign_keys=ON"))
Base.metadata.create_all(bind=engine)
session = Session(bind=engine)
yield session
session.close()
engine.dispose()
# #endregion Test.LoadTesting.Scope.Db
def _make_run(db: Session, run_id: str = "run-1", **overrides) -> LoadRun:
fields = dict(
id=run_id,
environment_id="env-dev",
dashboard_id=400,
status=RunStatus.QUEUED,
phase=RunPhase.RAMP,
)
fields.update(overrides)
run = LoadRun(**fields)
db.add(run)
db.commit()
return run
def _env_cm(env=None):
cm = MagicMock()
cm.get_environment.return_value = env
return cm
# #region Test.LoadTesting.Scope.ResolveScope [C:2] [TYPE Class]
# @BRIEF _resolve_execution_scope: env lookup, specs priority, query-model builds, degradation.
class TestResolveScope:
"""Verify execution-scope resolution contract."""
# #region Test.LoadTesting.Scope.TestEnvMissing [C:2] [TYPE Function]
# @BRIEF Unknown environment id degrades to None (lifecycle-only).
@pytest.mark.asyncio
async def test_scope_env_missing(self, db: Session):
run = _make_run(db)
with patch("src.dependencies.get_config_manager", return_value=_env_cm(None)):
assert await _resolve_execution_scope(db, run, []) is None
# #endregion Test.LoadTesting.Scope.TestEnvMissing
# #region Test.LoadTesting.Scope.TestSpecsBranch [C:2] [TYPE Function]
# @BRIEF Explicit execution_specs win: defaulted run/env/dashboard/chart fields, real breaker.
@pytest.mark.asyncio
async def test_scope_specs_branch(self, db: Session):
run = _make_run(db)
env = SimpleNamespace(id="env-dev")
specs = [
{"execution_id": "e1", "chart_id": 7, "variation_id": "v1", "filters_hash": "h1"},
{"execution_id": "e2", "dataset_id": 9},
{"execution_id": "e3"},
]
with patch("src.dependencies.get_config_manager", return_value=_env_cm(env)):
items, resolved_env, breaker = await _resolve_execution_scope(db, run, specs)
assert resolved_env is env
assert isinstance(breaker, CircuitBreaker)
assert len(items) == 3
assert items[0]["run_id"] == run.id
assert items[0]["environment_id"] == run.environment_id
assert items[0]["dashboard_id"] == run.dashboard_id
assert items[0]["chart_id"] == 7
assert items[1]["chart_id"] == 9 # dataset_id fallback
assert items[2]["chart_id"] == 0 # neither chart_id nor dataset_id
# run.total_executions untouched in the specs branch
assert db.get(LoadRun, run.id).total_executions == 0
# #endregion Test.LoadTesting.Scope.TestSpecsBranch
# #region Test.LoadTesting.Scope.TestQueryModelFailure [C:2] [TYPE Function]
# @BRIEF Inspection exception degrades to None (lifecycle-only).
@pytest.mark.asyncio
async def test_scope_query_model_failure(self, db: Session):
run = _make_run(db)
env = SimpleNamespace(id="env-dev")
with (
patch("src.dependencies.get_config_manager", return_value=_env_cm(env)),
patch("src.core.utils.client_registry.get_superset_client", new=AsyncMock()),
patch(
"src.services.dashboard_testing.query_model.inspect_dashboard_query_model",
new=AsyncMock(side_effect=RuntimeError("superset down")),
),
):
assert await _resolve_execution_scope(db, run, []) is None
# #endregion Test.LoadTesting.Scope.TestQueryModelFailure
# #region Test.LoadTesting.Scope.TestNoCharts [C:2] [TYPE Function]
# @BRIEF Query model with zero charts degrades to None.
@pytest.mark.asyncio
async def test_scope_no_charts(self, db: Session):
run = _make_run(db)
env = SimpleNamespace(id="env-dev")
with (
patch("src.dependencies.get_config_manager", return_value=_env_cm(env)),
patch("src.core.utils.client_registry.get_superset_client", new=AsyncMock()),
patch(
"src.services.dashboard_testing.query_model.inspect_dashboard_query_model",
new=AsyncMock(return_value=_FakeQueryModel(charts=[])),
),
):
assert await _resolve_execution_scope(db, run, []) is None
# #endregion Test.LoadTesting.Scope.TestNoCharts
# #region Test.LoadTesting.Scope.TestNoItems [C:2] [TYPE Function]
# @BRIEF build_execution_items returning nothing degrades to None.
@pytest.mark.asyncio
async def test_scope_no_items(self, db: Session):
run = _make_run(db)
env = SimpleNamespace(id="env-dev")
with (
patch("src.dependencies.get_config_manager", return_value=_env_cm(env)),
patch("src.core.utils.client_registry.get_superset_client", new=AsyncMock()),
patch(
"src.services.dashboard_testing.query_model.inspect_dashboard_query_model",
new=AsyncMock(return_value=_FakeQueryModel(charts=[_FakeChart(1, 10)])),
),
patch("src.services.load_testing.executor.build_execution_items", return_value=[]),
):
assert await _resolve_execution_scope(db, run, []) is None
# #endregion Test.LoadTesting.Scope.TestNoItems
# #region Test.LoadTesting.Scope.TestQueryModelBuildsItems [C:2] [TYPE Function]
# @BRIEF Query model + base variation build items; total_executions updated and committed.
@pytest.mark.asyncio
async def test_scope_query_model_builds_items(self, db: Session):
run = _make_run(db)
env = SimpleNamespace(id="env-dev")
built = [{"execution_id": "x1", "chart_id": 1}]
charts = [_FakeChart(1, 10, [SimpleNamespace(metric_name="count")]), _FakeChart(2, 20)]
with (
patch("src.dependencies.get_config_manager", return_value=_env_cm(env)),
patch("src.core.utils.client_registry.get_superset_client", new=AsyncMock()) as mock_client,
patch(
"src.services.dashboard_testing.query_model.inspect_dashboard_query_model",
new=AsyncMock(return_value=_FakeQueryModel(charts=charts)),
) as mock_inspect,
patch("src.services.load_testing.executor.build_execution_items", return_value=built) as mock_build,
):
items, resolved_env, breaker = await _resolve_execution_scope(db, run, [])
assert items == built
assert resolved_env is env
assert isinstance(breaker, CircuitBreaker)
assert db.get(LoadRun, run.id).total_executions == 1
mock_inspect.assert_awaited_once_with(mock_client.return_value, run.environment_id, run.dashboard_id)
expected_charts = [
{"chart_id": 1, "dataset_id": 10, "result_key": "count"},
{"chart_id": 2, "dataset_id": 20, "result_key": "count"},
]
mock_build.assert_called_once_with(
run=run, charts=expected_charts, variations=[{"variation_id": "base", "coordinates": {"filters": [], "viewport": {}, "role": "analyst"}}]
)
# #endregion Test.LoadTesting.Scope.TestQueryModelBuildsItems
# #region Test.LoadTesting.Scope.TestQueryModelWithVariations [C:2] [TYPE Function]
# @BRIEF Persisted variations feed build_execution_items instead of the base variation.
@pytest.mark.asyncio
async def test_scope_query_model_with_variations(self, db: Session):
run = _make_run(db)
db.add_all(
[
LoadVariation(run_id=run.id, variation_id="var-1", filters=[{"region": "north"}], viewport={"w": 1366}, role="analyst", time_range={"since": "7 days ago"}),
LoadVariation(run_id=run.id, variation_id="var-2", filters=None, viewport=None, role="exec", time_range=None),
]
)
db.commit()
env = SimpleNamespace(id="env-dev")
with (
patch("src.dependencies.get_config_manager", return_value=_env_cm(env)),
patch("src.core.utils.client_registry.get_superset_client", new=AsyncMock()),
patch(
"src.services.dashboard_testing.query_model.inspect_dashboard_query_model",
new=AsyncMock(return_value=_FakeQueryModel(charts=[_FakeChart(1, 10)])),
),
patch("src.services.load_testing.executor.build_execution_items", return_value=[{"execution_id": "x1"}]) as mock_build,
):
await _resolve_execution_scope(db, run, [])
variations = mock_build.call_args.kwargs["variations"]
assert variations[0]["variation_id"] == "var-1"
assert variations[0]["coordinates"]["filters"] == [{"region": "north"}]
assert variations[1]["variation_id"] == "var-2"
assert variations[1]["coordinates"]["filters"] == [] # None -> []
assert variations[1]["coordinates"]["viewport"] == {} # None -> {}
# #endregion Test.LoadTesting.Scope.TestQueryModelWithVariations
# #endregion Test.LoadTesting.Scope.ResolveScope
# #region Test.LoadTesting.Scope.BoundedPool [C:2] [TYPE Class]
# @BRIEF _run_bounded_pool drains items through the pool and persists enriched records.
class TestBoundedPool:
"""Verify pool drain + batched persistence contract."""
def _patch_pool(self, records):
holder: dict = {}
def _make_pool(**kwargs):
pool = _FakeRunnerPool(**kwargs)
pool.records = records
holder["pool"] = pool
return pool
patchers = [
patch("src.services.load_testing.runner_pool.RunnerPool", side_effect=_make_pool),
patch("src.services.load_testing.persistence.write_load_executions"),
]
return patchers, holder
# #region Test.LoadTesting.Scope.TestPoolDrainsRecords [C:2] [TYPE Function]
# @BRIEF Records are enriched from the by_id map; breaker records each outcome.
@pytest.mark.asyncio
async def test_pool_drains_records(self, db: Session):
run = _make_run(db, effective_concurrency=3)
items = [
{"execution_id": "exec-1", "chart_id": 7, "variation_id": "v1", "filters_hash": "h1"},
{"execution_id": "exec-2", "chart_id": 8, "variation_id": "v2", "filters_hash": "h2"},
]
breaker = CircuitBreaker()
records = [
{"execution_id": "exec-1", "outcome": "error", "upstream_latency_ms": 5},
{"execution_id": "unknown", "outcome": "success"}, # unknown id -> item defaults; missing latency -> 0
]
patchers, holder = self._patch_pool(records)
with patchers[0], patchers[1] as mock_write:
await _run_bounded_pool(db, run, items, breaker)
fake_pool = holder["pool"]
assert fake_pool.capacity == 3
assert fake_pool.env_capacity_semaphore is None
first, second = mock_write.call_args_list
assert first.args[0] is db
record1 = first.args[1][0]
assert record1["run_id"] == run.id
assert record1["chart_id"] == 7
assert record1["variation_id"] == "v1"
assert record1["filters_hash"] == "h1"
assert record1["worker_id"] == "0"
record2 = second.args[1][0]
assert record2["chart_id"] == 0
assert record2["variation_id"] == "base"
assert record2["filters_hash"] == ""
# breaker saw both outcomes
assert list(breaker._outcomes) == [True, False]
assert breaker.tripped is False # 2 samples < min_samples
# #endregion Test.LoadTesting.Scope.TestPoolDrainsRecords
# #region Test.LoadTesting.Scope.TestPoolWithoutBreaker [C:2] [TYPE Function]
# @BRIEF breaker=None is tolerated: record() skipped, persistence still runs.
@pytest.mark.asyncio
async def test_pool_without_breaker(self, db: Session):
run = _make_run(db)
items = [{"execution_id": "exec-1", "chart_id": 7, "variation_id": "v1", "filters_hash": "h1"}]
records = [{"outcome": "success", "upstream_latency_ms": 3}]
patchers, holder = self._patch_pool(records)
with patchers[0], patchers[1] as mock_write:
await _run_bounded_pool(db, run, items, None)
assert holder["pool"].breaker is None
mock_write.assert_called_once()
# #endregion Test.LoadTesting.Scope.TestPoolWithoutBreaker
# #region Test.LoadTesting.Scope.TestPoolCapacityFloor [C:2] [TYPE Function]
# @BRIEF Zero/None effective_concurrency floors capacity at 1.
@pytest.mark.asyncio
async def test_pool_capacity_floor(self, db: Session):
run = _make_run(db, effective_concurrency=0)
items = [{"execution_id": "exec-1"}]
patchers, holder = self._patch_pool([])
with patchers[0], patchers[1]:
await _run_bounded_pool(db, run, items, None)
assert holder["pool"].capacity == 1
# #endregion Test.LoadTesting.Scope.TestPoolCapacityFloor
# #region Test.LoadTesting.Scope.TestPoolTripsBreaker [C:2] [TYPE Function]
# @BRIEF Breaker reaches tripped after enough error records (persist_result drives it).
@pytest.mark.asyncio
async def test_pool_trips_breaker(self, db: Session):
run = _make_run(db)
items = [{"execution_id": f"e{i}", "chart_id": 1, "variation_id": "v", "filters_hash": "h"} for i in range(21)]
breaker = CircuitBreaker()
records = [{"outcome": "error", "upstream_latency_ms": 99}] * 21
patchers, _ = self._patch_pool(records)
with patchers[0], patchers[1]:
await _run_bounded_pool(db, run, items, breaker)
assert breaker.tripped is True
assert breaker.trigger_metric == "error_rate"
# #endregion Test.LoadTesting.Scope.TestPoolTripsBreaker
# #endregion Test.LoadTesting.Scope.BoundedPool
# #region Test.LoadTesting.Scope.Variations [C:2] [TYPE Class]
# @BRIEF _load_variations: persisted rows mapped to coordinate dicts, else base variation.
class TestLoadVariations:
"""Verify variation loading contract."""
# #region Test.LoadTesting.Scope.TestRowsMapped [C:2] [TYPE Function]
# @BRIEF Persisted rows map filters/viewport with None fallbacks; ordered by variation_id.
def test_rows_mapped(self, db: Session):
_make_run(db, run_id="run-v")
db.add_all(
[
LoadVariation(run_id="run-v", variation_id="var-2", filters=None, viewport=None, role="exec", time_range=None),
LoadVariation(run_id="run-v", variation_id="var-1", filters=[{"region": "north"}], viewport={"w": 1366}, role="analyst", time_range={"since": "7 days ago"}),
]
)
db.commit()
rows = _load_variations(db, db.get(LoadRun, "run-v"))
assert [r["variation_id"] for r in rows] == ["var-1", "var-2"]
assert rows[0]["coordinates"] == {"filters": [{"region": "north"}], "viewport": {"w": 1366}, "role": "analyst", "time_range": {"since": "7 days ago"}}
assert rows[1]["coordinates"] == {"filters": [], "viewport": {}, "role": "exec", "time_range": None}
# #endregion Test.LoadTesting.Scope.TestRowsMapped
# #region Test.LoadTesting.Scope.TestNoRowsBaseVariation [C:2] [TYPE Function]
# @BRIEF No persisted rows -> single base variation for an analyst.
def test_no_rows_base_variation(self, db: Session):
run = _make_run(db, run_id="run-nov")
rows = _load_variations(db, run)
assert rows == [{"variation_id": "base", "coordinates": {"filters": [], "viewport": {}, "role": "analyst"}}]
# #endregion Test.LoadTesting.Scope.TestNoRowsBaseVariation
# #endregion Test.LoadTesting.Scope.Variations
# #region Test.LoadTesting.Scope.BreakerFactory [C:2] [TYPE Class]
# @BRIEF _build_breaker returns a fresh, untripped run-level breaker.
class TestBreakerFactory:
# #region Test.LoadTesting.Scope.TestBuildBreaker [C:2] [TYPE Function]
# @BRIEF Fresh CircuitBreaker with default thresholds and closed state.
def test_build_breaker(self):
breaker = _build_breaker()
assert isinstance(breaker, CircuitBreaker)
assert breaker.tripped is False
assert breaker.trigger_metric is None
# #endregion Test.LoadTesting.Scope.TestBuildBreaker
# #endregion Test.LoadTesting.Scope.BreakerFactory
# #region Test.LoadTesting.Scope.PersistPhase [C:2] [TYPE Class]
# @BRIEF _persist_phase transitions, timestamps started_at once, emits aggregate progress.
class TestPersistPhase:
# #region Test.LoadTesting.Scope.TestStartsRunAndCommits [C:2] [TYPE Function]
# @BRIEF started_at is set on first transition and committed.
def test_starts_run_and_commits(self, db: Session):
run = _make_run(db, run_id="run-p1", status=RunStatus.QUEUED)
_persist_phase(db, run, RunStatus.RAMPING, RunPhase.RAMP, None, "Ramping load run")
fresh = db.get(LoadRun, "run-p1")
assert fresh.status == RunStatus.RAMPING
assert fresh.phase == RunPhase.RAMP
assert fresh.started_at is not None
# #endregion Test.LoadTesting.Scope.TestStartsRunAndCommits
# #region Test.LoadTesting.Scope.TestKeepsStartedAt [C:2] [TYPE Function]
# @BRIEF A second transition keeps the original started_at.
def test_keeps_started_at(self, db: Session):
run = _make_run(db, run_id="run-p2", status=RunStatus.RAMPING)
run.started_at = datetime(2024, 1, 1)
db.commit()
_persist_phase(db, run, RunStatus.STEADY, RunPhase.STEADY, None, "Steady")
assert db.get(LoadRun, "run-p2").started_at == datetime(2024, 1, 1)
# #endregion Test.LoadTesting.Scope.TestKeepsStartedAt
# #region Test.LoadTesting.Scope.TestProgressPercent [C:2] [TYPE Function]
# @BRIEF context present -> progress emitted with the phase percent.
def test_progress_percent(self, db: Session):
run = _make_run(db, run_id="run-p3", status=RunStatus.QUEUED)
ctx = SimpleNamespace(logger=MagicMock())
_persist_phase(db, run, RunStatus.STEADY, RunPhase.STEADY, ctx, "Steady load phase")
ctx.logger.progress.assert_called_once_with("Steady load phase", percent=60)
# #endregion Test.LoadTesting.Scope.TestProgressPercent
# #region Test.LoadTesting.Scope.TestProgressFailureSuppressed [C:2] [TYPE Function]
# @BRIEF A throwing progress sink is suppressed — persistence still succeeds.
def test_progress_failure_suppressed(self, db: Session):
run = _make_run(db, run_id="run-p4", status=RunStatus.QUEUED)
ctx = SimpleNamespace(logger=MagicMock())
ctx.logger.progress.side_effect = RuntimeError("sink down")
_persist_phase(db, run, RunStatus.DRAINING, RunPhase.DRAIN, ctx, "Draining")
assert db.get(LoadRun, "run-p4").phase == RunPhase.DRAIN
# #endregion Test.LoadTesting.Scope.TestProgressFailureSuppressed
# #endregion Test.LoadTesting.Scope.PersistPhase
# #region Test.LoadTesting.Scope.StatusPercent [C:2] [TYPE Class]
# @BRIEF _status_percent maps the three non-terminal phases; unknown -> 0.
class TestStatusPercent:
# #region Test.LoadTesting.Scope.TestKnownAndUnknown [C:2] [TYPE Function]
# @BRIEF ramping=25, steady=60, draining=90, unknown=0.
def test_known_and_unknown(self):
assert _status_percent("ramping") == 25
assert _status_percent("steady") == 60
assert _status_percent("draining") == 90
assert _status_percent("queued") == 0
# #endregion Test.LoadTesting.Scope.TestKnownAndUnknown
# #endregion Test.LoadTesting.Scope.StatusPercent
# #region Test.LoadTesting.Scope.ReloadRun [C:2] [TYPE Class]
# @BRIEF _reload_run re-fetches after rollback; raises when the row vanished.
class TestReloadRun:
# #region Test.LoadTesting.Scope.TestReloadFound [C:2] [TYPE Function]
# @BRIEF Existing row is re-fetched with committed state.
def test_reload_found(self, db: Session):
_make_run(db, run_id="run-r1", status=RunStatus.RAMPING)
db.commit()
reloaded = _reload_run(db, "run-r1")
assert reloaded.id == "run-r1"
assert reloaded.status == RunStatus.RAMPING
# #endregion Test.LoadTesting.Scope.TestReloadFound
# #region Test.LoadTesting.Scope.TestReloadMissing [C:2] [TYPE Function]
# @BRIEF Missing row raises RuntimeError describing the vanished run.
def test_reload_missing(self, db: Session):
with pytest.raises(RuntimeError, match="disappeared during execution"):
_reload_run(db, "run-ghost")
# #endregion Test.LoadTesting.Scope.TestReloadMissing
# #endregion Test.LoadTesting.Scope.ReloadRun
# #region Test.LoadTesting.Scope.Now [C:2] [TYPE Class]
# @BRIEF _now returns a tz-aware UTC datetime.
class TestNow:
# #region Test.LoadTesting.Scope.TestNowUtc [C:2] [TYPE Function]
# @BRIEF Returned datetime carries a tzinfo.
def test_now_utc(self):
now = _now()
assert isinstance(now, datetime)
assert now.tzinfo is not None
# #endregion Test.LoadTesting.Scope.TestNowUtc
# #endregion Test.LoadTesting.Scope.Now
# #region Test.LoadTesting.Scope.StartLoadRun [C:2] [TYPE Class]
# @BRIEF start_load_run persists the run and dispatches exactly one TaskManager task.
class TestStartLoadRun:
"""Verify the R1 one-task-per-run dispatch contract."""
# #region Test.LoadTesting.Scope.TestStartDispatchesTask [C:2] [TYPE Function]
# @BRIEF Run row persisted with queued status and task_id bound to the created task.
@pytest.mark.asyncio
async def test_start_dispatches_task(self, db: Session):
from src.models.load_testing import LoadProfile
db.add(LoadProfile(id="prof-1", dashboard_id=400, environment_id="env-dev", revision=1,
concurrency_requested=4, execution_mode="iterations"))
db.commit()
tm = _FakeTaskManager(task_id="task-1")
run = await start_load_run(
db, tm,
environment_id="env-dev", dashboard_id=400,
profile_id="prof-1", profile_revision=2,
effective_concurrency=4, matrix_seed=3,
theoretical_variations=10, selected_variations=5, total_executions=20,
blast_radius_fingerprint="fp1", approval_gate_id="gate-1",
created_by="tester", extra_params={"hold_seconds": 1},
)
assert run.id is not None
assert run.status == RunStatus.QUEUED
assert run.phase == RunPhase.RAMP
assert run.task_id == "task-1"
assert run.profile_id == "prof-1"
assert run.profile_revision == 2
assert run.effective_concurrency == 4
assert run.created_by == "tester"
assert tm.calls == [("load_testing", {"load_run_id": run.id, "hold_seconds": 1})]
assert db.get(LoadRun, run.id).task_id == "task-1"
# #endregion Test.LoadTesting.Scope.TestStartDispatchesTask
# #region Test.LoadTesting.Scope.TestStartCustomIdNoExtras [C:2] [TYPE Function]
# @BRIEF Provided load_run_id is honored; no extra_params -> params carry only load_run_id.
@pytest.mark.asyncio
async def test_start_custom_id_no_extras(self, db: Session):
tm = _FakeTaskManager()
run = await start_load_run(
db, tm,
environment_id="env-dev", dashboard_id=401,
load_run_id="run-custom",
)
assert run.id == "run-custom"
assert tm.calls == [("load_testing", {"load_run_id": "run-custom"})]
# #endregion Test.LoadTesting.Scope.TestStartCustomIdNoExtras
# #region Test.LoadTesting.Scope.TestStartDefaults [C:2] [TYPE Function]
# @BRIEF Defaults: profile_revision=1, effective_concurrency=1, created_by=system.
@pytest.mark.asyncio
async def test_start_defaults(self, db: Session):
tm = _FakeTaskManager()
run = await start_load_run(db, tm, environment_id="env-dev", dashboard_id=402)
assert run.profile_revision == 1
assert run.effective_concurrency == 1
assert run.created_by == "system"
assert run.approval_gate_id is None
# #endregion Test.LoadTesting.Scope.TestStartDefaults
# #region Test.LoadTesting.Scope.TestStartTaskFailure [C:2] [TYPE Function]
# @BRIEF TaskManager failure propagates out of start_load_run.
@pytest.mark.asyncio
async def test_start_task_failure(self, db: Session):
with pytest.raises(RuntimeError, match="task manager down"):
await start_load_run(db, _FailingTaskManager(), environment_id="env-dev", dashboard_id=403)
# #endregion Test.LoadTesting.Scope.TestStartTaskFailure
# #endregion Test.LoadTesting.Scope.StartLoadRun
# #endregion Test.LoadTesting.Scope