Files
openswarm/backend/tests/test_service_legacy.py

166 lines
5.7 KiB
Python

"""Service-sync compatibility tests.
Verifies the legacy compatibility helpers on backend/apps/service/client.py
(record, submit_event, submit_session_close, etc.) still produce the right
opaque payload through the unified sync() entry point. Forward-looking
contract tests live in test_service.py; this file covers the legacy shim
surface so it can be deprecated cleanly later.
Run with:
cd backend && python -m pytest tests/test_service_legacy.py -v
"""
import json
import os
import tempfile
import pytest
# Sandbox the data dir before any module import touches settings on disk.
p_tmpdir = tempfile.mkdtemp()
os.environ.setdefault("OPENSWARM_DATA_DIR", p_tmpdir)
# Captured syncs from this test run.
p_captured_syncs: list[dict] = []
@pytest.fixture(autouse=True)
def reset_captured_syncs():
p_captured_syncs.clear()
yield
p_captured_syncs.clear()
@pytest.fixture(autouse=True)
def install_sync_sink():
"""Install a service-sync sink and decode the opaque payload back into
a structured shape for assertions. The sink translates the new shape
{client_state, d, t} into a legacy-compatible {kind, distinct_id, props}
bag so existing tests can keep their assertions terse."""
import backend.apps.service.client as svc_client
def p_sink(label: str, body: dict):
cs = body.get("client_state") or {}
payload = body.get("d") or body.get("payload") or {}
# Infer a synthetic kind from payload shape, same dispatch logic as the cloud uses in production.
if "status" in payload and "messages" in payload:
status = payload.get("status", "unknown")
kind = f"session.{status}" if status != "unknown" else "session.completed"
props = dict(payload)
elif "identity" in payload:
kind = "state.update"
props = dict(payload)
elif "diagnostic" in payload:
kind = "diagnostic.fired"
props = dict(payload)
elif "s" in payload and "a" in payload:
kind = f"{payload['s']}.{payload['a']}"
props = dict(payload.get("p") or {})
elif "surface" in payload:
surface = payload.get("surface", "")
action = payload.get("action", "fired")
kind = f"{surface}.{action}"
props = dict(payload.get("props") or {})
else:
kind = "state.update"
props = dict(payload)
if payload.get("session_id"):
props["session_id"] = payload["session_id"]
if payload.get("dashboard_id"):
props["dashboard_id"] = payload["dashboard_id"]
props.setdefault("os", cs.get("os", ""))
props.setdefault("platform", cs.get("os", ""))
p_captured_syncs.append({
"kind": kind,
"distinct_id": cs.get("install_id", ""),
"properties": props,
})
old_sink = svc_client.test_sink
old_iid = svc_client.install_id
svc_client.set_test_sink(p_sink)
svc_client.install_id = "test-install-id"
yield
svc_client.set_test_sink(old_sink)
svc_client.install_id = old_iid
@pytest.fixture(autouse=True)
def mock_settings(tmp_path):
"""Sandbox settings so tests don't read or write the real config."""
settings_file = tmp_path / "settings.json"
settings_file.write_text(json.dumps({
"service_diagnostics_mode": "standard",
"installation_id": "test-install-id",
}))
import backend.apps.settings.store as settings_mod
old_file = settings_mod.SETTINGS_FILE
settings_mod.SETTINGS_FILE = str(settings_file)
yield
settings_mod.SETTINGS_FILE = old_file
@pytest.fixture(autouse=True)
def mock_sessions_dir(tmp_path):
"""Use temp dir for session persistence."""
sessions_dir = tmp_path / "sessions"
sessions_dir.mkdir()
import backend.config.paths as paths_mod
old_dir = paths_mod.SESSIONS_DIR
paths_mod.SESSIONS_DIR = str(sessions_dir)
yield str(sessions_dir)
paths_mod.SESSIONS_DIR = old_dir
def syncs(kind: str | None = None) -> list[dict]:
"""Return captured syncs, optionally filtered by inferred kind."""
if kind:
return [s for s in p_captured_syncs if s["kind"] == kind]
return list(p_captured_syncs)
def last_sync(kind: str) -> dict:
"""Return the last captured sync of a given inferred kind."""
matching = syncs(kind)
assert matching, f"No {kind} syncs captured. Got: {[s['kind'] for s in p_captured_syncs]}"
return matching[-1]
# Import application modules (after fixtures are wired).
from backend.apps.service.client import record
# --------------------------------------------------------------------------- 1. record(), legacy shim correctness ---------------------------------------------------------------------------
class TestRecordBasics:
def test_record_sends_payload(self):
record("test.report", {"key": "value"})
s = last_sync("test.report")
assert s["properties"]["key"] == "value"
assert s["distinct_id"] == "test-install-id"
def test_record_adds_os_and_platform(self):
record("test.report", {})
s = last_sync("test.report")
assert "os" in s["properties"]
assert "platform" in s["properties"]
def test_record_includes_session_id(self):
record("test.report", {}, session_id="sess123")
s = last_sync("test.report")
assert s["properties"]["session_id"] == "sess123"
def test_record_includes_dashboard_id(self):
record("test.report", {}, dashboard_id="dash456")
s = last_sync("test.report")
assert s["properties"]["dashboard_id"] == "dash456"
# Session-close cloud sync (the old session.completed snapshot path) was retired in favour of Haik's
# per-message product-analytics bridge, so its close-fires-once + token-on-close tests were removed.