Files
muxplex/muxplex/tests/test_identity.py
T
Brian Krabach ab5560a623 fix: address COE verification findings — bell-clear regression, active_view fallback, device_id consistency
- BUG 1: Replace isinstance(active_remote_id, int) guard with _lookup_remote_by_device_id()
  so bell-clear fires correctly when active_remote_id is a UUID string (new format).
  Old code silently skipped the POST for all non-integer active_remote_id values.
  Regression test: test_poll_cycle_fires_federation_bell_clear_for_remote_session_with_uuid

- GAP 4: Add _resolveActiveView(activeView, views) helper in app.js.
  Falls back to 'all' when active_view references a deleted/unknown view name.
  Ready for Phase 2 view-switching code to call at read time.

- ISSUE 6: _createDeviceSelect now uses remotes[i].device_id || String(i) for option
  values instead of always String(i), so session creation routes receive correct device_id.

- ISSUE 9: identity.json written with indent=2 + trailing newline, consistent with
  settings.json and state.json formatting conventions.

- ISSUE 11: Fix state.py module docstring — 'tmux-web muxplex' → 'muxplex'.

- ISSUE 12: Settings panel hidden_sessions checkboxes now use s.sessionKey || s.name
  as the checkbox value (and checked state) so remote sessions are stored in
  device_id:name format, consistent with getVisibleSessions() lookups.
2026-04-15 14:42:50 -07:00

176 lines
6.2 KiB
Python

"""
Tests for muxplex/identity.py — Device Identity Module.
Each muxplex instance gets a persistent device_id (UUID v4) stored in
~/.config/muxplex/identity.json (outside federation sync boundary).
"""
import json
import uuid
from muxplex.identity import load_device_id, reset_device_id
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _is_valid_uuid4(value: str) -> bool:
"""Return True if value is a valid UUID v4 string."""
try:
parsed = uuid.UUID(value)
return parsed.version == 4
except (ValueError, AttributeError):
return False
# ---------------------------------------------------------------------------
# load_device_id tests
# ---------------------------------------------------------------------------
def test_load_creates_file_when_absent(tmp_path, monkeypatch):
"""load_device_id() creates identity.json when it does not exist."""
identity_path = tmp_path / "identity.json"
monkeypatch.setattr("muxplex.identity.IDENTITY_PATH", identity_path)
device_id = load_device_id()
assert identity_path.exists(), "identity.json should be created"
assert _is_valid_uuid4(device_id), f"Expected UUID v4, got: {device_id}"
data = json.loads(identity_path.read_text())
assert data["device_id"] == device_id
def test_load_returns_same_id_on_repeated_calls(tmp_path, monkeypatch):
"""load_device_id() returns the same ID on repeated calls (idempotent)."""
identity_path = tmp_path / "identity.json"
monkeypatch.setattr("muxplex.identity.IDENTITY_PATH", identity_path)
id_first = load_device_id()
id_second = load_device_id()
id_third = load_device_id()
assert id_first == id_second == id_third, "Repeated calls must return the same ID"
def test_load_reads_existing_file(tmp_path, monkeypatch):
"""load_device_id() reads the existing device_id from identity.json."""
identity_path = tmp_path / "identity.json"
monkeypatch.setattr("muxplex.identity.IDENTITY_PATH", identity_path)
known_id = str(uuid.uuid4())
identity_path.write_text(json.dumps({"device_id": known_id}))
result = load_device_id()
assert result == known_id, "Should return the pre-existing device_id"
def test_load_creates_parent_dirs(tmp_path, monkeypatch):
"""load_device_id() creates parent directories if they do not exist."""
identity_path = tmp_path / "nested" / "dirs" / "identity.json"
monkeypatch.setattr("muxplex.identity.IDENTITY_PATH", identity_path)
device_id = load_device_id()
assert identity_path.exists(), "identity.json should be created in nested dirs"
assert _is_valid_uuid4(device_id)
def test_load_regenerates_on_corrupt_json(tmp_path, monkeypatch):
"""load_device_id() regenerates device_id when identity.json contains corrupt JSON."""
identity_path = tmp_path / "identity.json"
monkeypatch.setattr("muxplex.identity.IDENTITY_PATH", identity_path)
identity_path.write_text("this is not valid json {{{")
device_id = load_device_id()
assert _is_valid_uuid4(device_id), (
f"Expected UUID v4 after regeneration, got: {device_id}"
)
data = json.loads(identity_path.read_text())
assert data["device_id"] == device_id, (
"File should be rewritten with the new device_id"
)
def test_load_regenerates_on_missing_key(tmp_path, monkeypatch):
"""load_device_id() regenerates device_id when identity.json lacks the device_id key."""
identity_path = tmp_path / "identity.json"
monkeypatch.setattr("muxplex.identity.IDENTITY_PATH", identity_path)
identity_path.write_text(json.dumps({"other_key": "some_value"}))
device_id = load_device_id()
assert _is_valid_uuid4(device_id), (
f"Expected UUID v4 after regeneration, got: {device_id}"
)
data = json.loads(identity_path.read_text())
assert data["device_id"] == device_id, (
"File should be rewritten with the new device_id"
)
# ---------------------------------------------------------------------------
# reset_device_id tests
# ---------------------------------------------------------------------------
def test_reset_generates_new_id(tmp_path, monkeypatch):
"""reset_device_id() generates a new device_id different from the existing one."""
identity_path = tmp_path / "identity.json"
monkeypatch.setattr("muxplex.identity.IDENTITY_PATH", identity_path)
# Establish an initial ID
original_id = load_device_id()
# Reset should produce a new, different ID
new_id = reset_device_id()
assert _is_valid_uuid4(new_id), f"Expected UUID v4 from reset, got: {new_id}"
assert new_id != original_id, (
"reset_device_id() should return a different ID than the original"
)
# Verify the file now contains the new ID
data = json.loads(identity_path.read_text())
assert data["device_id"] == new_id, "File should contain the newly reset device_id"
def test_reset_creates_parent_dirs(tmp_path, monkeypatch):
"""reset_device_id() creates parent directories if they do not exist."""
identity_path = tmp_path / "no" / "parent" / "identity.json"
monkeypatch.setattr("muxplex.identity.IDENTITY_PATH", identity_path)
new_id = reset_device_id()
assert identity_path.exists(), "identity.json should be created in new directories"
assert _is_valid_uuid4(new_id)
data = json.loads(identity_path.read_text())
assert data["device_id"] == new_id
def test_identity_json_is_human_readable(tmp_path, monkeypatch):
"""identity.json must be written with indentation for human readability.
Consistent with settings.py and state.py which both write indented JSON.
"""
identity_path = tmp_path / "identity.json"
monkeypatch.setattr("muxplex.identity.IDENTITY_PATH", identity_path)
load_device_id()
raw = identity_path.read_text()
# Human-readable JSON has newlines; compact JSON does not
assert "\n" in raw, (
"identity.json must be written with indent=2 for human readability "
"(consistent with settings.json and state.json formatting)"
)
# Must still be valid JSON
data = json.loads(raw)
assert "device_id" in data