mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-20 03:35:40 +02:00
63 lines
2.3 KiB
Python
63 lines
2.3 KiB
Python
"""Concurrent SettingsWrite must not lose updates.
|
|
|
|
SettingsWrite is a read-modify-write that routes through update_settings (which
|
|
awaits), so two autonomous agents writing at the same time would interleave: each
|
|
loads the same snapshot, each writes the WHOLE object back, and the last writer
|
|
silently reverts the other's field, while BOTH agents are told "applied". This
|
|
drives two genuinely concurrent writes (different fields) through the ASGI app
|
|
and asserts neither is lost. It's the regression guard for the asyncio lock that
|
|
serializes these writes; without the lock this fails reproducibly.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from backend.main import app
|
|
|
|
|
|
def p_auth_headers():
|
|
import backend.auth as auth_mod
|
|
if not auth_mod.TOKEN:
|
|
import secrets
|
|
auth_mod.TOKEN = secrets.token_urlsafe(32)
|
|
return {"Authorization": f"Bearer {auth_mod.TOKEN}"}
|
|
|
|
|
|
@pytest.fixture
|
|
def reset_settings():
|
|
from backend.apps.settings.settings import load_settings, save_settings
|
|
original = load_settings().model_copy(deep=True)
|
|
yield
|
|
save_settings(original)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_concurrent_writes_to_different_fields_both_survive(reset_settings):
|
|
from backend.apps.settings.settings import load_settings, save_settings
|
|
|
|
base = load_settings()
|
|
base.theme = "dark"
|
|
base.default_mode = "agent"
|
|
save_settings(base)
|
|
|
|
headers = p_auth_headers()
|
|
transport = httpx.ASGITransport(app=app)
|
|
async with httpx.AsyncClient(transport=transport, base_url="http://test", headers=headers) as client:
|
|
r1, r2 = await asyncio.gather(
|
|
client.post("/api/settings-meta/write", json={"changes": {"theme": "light"}}),
|
|
client.post("/api/settings-meta/write", json={"changes": {"default_mode": "chat"}}),
|
|
)
|
|
|
|
assert r1.status_code == 200 and r2.status_code == 200
|
|
assert r1.json()["outcomes"]["theme"]["status"] == "applied"
|
|
assert r2.json()["outcomes"]["default_mode"]["status"] == "applied"
|
|
|
|
final = load_settings()
|
|
# Both concurrent edits must persist; neither agent's "applied" result is a lie.
|
|
assert final.theme == "light", "lost update: theme was clobbered by the concurrent write"
|
|
assert final.default_mode == "chat", "lost update: default_mode was clobbered"
|