79 lines
2.9 KiB
Python
79 lines
2.9 KiB
Python
"""G5 -- chaos flags toggle at runtime, without a restart."""
|
|||
|
|
import pytest
|
||
|
|
|
||
|
|
from harness.config import POSTGRES_DSN
|
||
|
|
from harness.pg import statement_count
|
||
|
|
|
||
|
|
pytestmark = pytest.mark.contract
|
||
|
|
|
||
|
|
ALL_FLAGS = {
|
||
|
|
"slow_sanctions", "sanctions_errors", "n_plus_one", "pool_starvation",
|
||
|
|
"row_lock_contention", "blocking_event_loop", "retry_storm", "no_batching",
|
||
|
|
"unbounded_cache", "slow_consumer", "cold_cache", "poison_data", "chatty_health",
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
async def test_chaos_lists_all_thirteen_flags(chaos):
|
||
|
|
"""G5: GET /api/admin/chaos lists all thirteen flags by name."""
|
||
|
|
state = await chaos.get()
|
||
|
|
assert set(state["flags"]) == ALL_FLAGS, state["flags"]
|
||
|
|
|
||
|
|
|
||
|
|
async def test_toggling_one_flag_changes_only_that_flag(chaos):
|
||
|
|
"""G5: toggling a single flag changes only that flag."""
|
||
|
|
before = (await chaos.get())["flags"]
|
||
|
|
await chaos.set_flag("slow_sanctions", True)
|
||
|
|
after = (await chaos.get())["flags"]
|
||
|
|
|
||
|
|
assert after["slow_sanctions"] is True
|
||
|
|
for flag in ALL_FLAGS - {"slow_sanctions"}:
|
||
|
|
assert after[flag] == before[flag], f"{flag} changed unexpectedly"
|
||
|
|
|
||
|
|
|
||
|
|
async def test_profile_clean_sets_all_false(chaos):
|
||
|
|
"""G5: the `clean` profile sets every flag to false."""
|
||
|
|
state = await chaos.set_profile("clean")
|
||
|
|
assert all(v is False for v in state["flags"].values()), state["flags"]
|
||
|
|
|
||
|
|
|
||
|
|
async def test_profile_nightmare_sets_all_true(chaos):
|
||
|
|
"""G5: the `nightmare` profile sets every flag to true."""
|
||
|
|
state = await chaos.set_profile("nightmare")
|
||
|
|
assert all(v is True for v in state["flags"].values()), state["flags"]
|
||
|
|
|
||
|
|
|
||
|
|
async def test_profile_light_sets_exactly_two_flags(chaos):
|
||
|
|
"""G5: the `light` profile sets exactly slow_sanctions and cold_cache."""
|
||
|
|
state = await chaos.set_profile("light")
|
||
|
|
enabled = {flag for flag, v in state["flags"].items() if v}
|
||
|
|
assert enabled == {"slow_sanctions", "cold_cache"}, enabled
|
||
|
|
|
||
|
|
|
||
|
|
async def test_flag_change_is_live_no_restart(api, chaos, manifest):
|
||
|
|
"""G5: enabling n_plus_one via the API changes the query count for the very
|
||
|
|
next insights request -- with no container restart -- proving live
|
||
|
|
reconfiguration rather than timing noise."""
|
||
|
|
account_id = manifest["clean_account_id"]
|
||
|
|
await chaos.set_profile("clean")
|
||
|
|
|
||
|
|
with statement_count(POSTGRES_DSN) as baseline:
|
||
|
|
resp = await api.get(f"/api/accounts/{account_id}/insights")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
|
||
|
|
await chaos.set_flag("n_plus_one", True)
|
||
|
|
|
||
|
|
with statement_count(POSTGRES_DSN) as degraded:
|
||
|
|
resp = await api.get(f"/api/accounts/{account_id}/insights")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
|
||
|
|
assert degraded.calls > baseline.calls, (
|
||
|
|
f"expected more SQL statements after enabling n_plus_one live "
|
||
|
|
f"(baseline={baseline.calls}, degraded={degraded.calls})"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
async def test_unknown_flag_returns_400(chaos):
|
||
|
|
"""G5: setting an unknown flag name returns 400."""
|
||
|
|
resp = await chaos.set_flag("not_a_real_flag", True)
|
||
|
|
assert resp.status_code == 400
|