feat: add --reset-device-id CLI command (task-6)

Add reset_device_id_command() function to cli.py that:
- loads the current device_id via load_device_id()
- generates a new device_id via reset_device_id()
- prints new device_id, identity file path, previous device_id
- warns that existing session keys are now orphaned

Register 'reset-device-id' subparser with appropriate help text
and add dispatch branch in main().

Tests added:
- test_reset_device_id_writes_new_id: verifies command writes new ID
  and prints required output including orphan warning
- test_main_dispatches_to_reset_device_id: verifies CLI routing
This commit is contained in:
Brian Krabach
2026-04-15 11:26:32 -07:00
parent ad2c0e1fe2
commit 569ba44848
2 changed files with 85 additions and 0 deletions
+19
View File
@@ -149,6 +149,18 @@ def reset_secret() -> None:
print("Warning: all active sessions are now invalid.") print("Warning: all active sessions are now invalid.")
def reset_device_id_command() -> None:
"""Regenerate the device identity UUID and warn about orphaned session keys."""
from muxplex.identity import IDENTITY_PATH, load_device_id, reset_device_id # noqa: PLC0415
old_id = load_device_id()
new_id = reset_device_id()
print(f"New device_id: {new_id}")
print(f"Identity file: {IDENTITY_PATH}")
print(f"Previous device_id: {old_id}")
print("Warning: existing session keys are now orphaned.")
def show_password() -> None: def show_password() -> None:
"""Print the current muxplex password or indicate PAM mode.""" """Print the current muxplex password or indicate PAM mode."""
auth_mode = os.environ.get("MUXPLEX_AUTH", "").lower() auth_mode = os.environ.get("MUXPLEX_AUTH", "").lower()
@@ -953,6 +965,11 @@ def main() -> None:
"reset-secret", help="Regenerate signing secret (invalidates sessions)" "reset-secret", help="Regenerate signing secret (invalidates sessions)"
) )
sub.add_parser(
"reset-device-id",
help="Regenerate device identity UUID (orphans existing session keys)",
)
sub.add_parser( sub.add_parser(
"generate-federation-key", "generate-federation-key",
help="Generate a random federation key and write it to disk", help="Generate a random federation key and write it to disk",
@@ -1005,6 +1022,8 @@ def main() -> None:
show_password() show_password()
elif args.command == "reset-secret": elif args.command == "reset-secret":
reset_secret() reset_secret()
elif args.command == "reset-device-id":
reset_device_id_command()
elif args.command == "generate-federation-key": elif args.command == "generate-federation-key":
generate_federation_key() generate_federation_key()
elif args.command == "doctor": elif args.command == "doctor":
+66
View File
@@ -2154,6 +2154,72 @@ def test_upgrade_pypi_install_uses_package_name(monkeypatch, capsys):
assert not any("git+" in str(arg) for arg in install_cmd) assert not any("git+" in str(arg) for arg in install_cmd)
# ---------------------------------------------------------------------------
# task-6-reset-device-id: --reset-device-id CLI command tests
# ---------------------------------------------------------------------------
def test_reset_device_id_writes_new_id(tmp_path, monkeypatch, capsys):
"""reset_device_id_command() writes a new device_id different from the previous one.
Monkeypatches the identity path, creates an initial identity, calls the
command, and verifies that:
- The file now has a different device_id
- Output mentions 'device_id' or 'identity'
- Output includes a warning or mentions 'orphan'
"""
import json
import muxplex.identity as identity_mod
from muxplex.cli import reset_device_id_command
# Set up fake identity path
identity_path = tmp_path / "identity.json"
monkeypatch.setattr(identity_mod, "IDENTITY_PATH", identity_path)
# Create an initial identity
original_id = "11111111-1111-4111-a111-111111111111"
identity_path.write_text(json.dumps({"device_id": original_id}))
# Run the command
reset_device_id_command()
# Verify new ID was written and is different
data = json.loads(identity_path.read_text())
new_id = data["device_id"]
assert new_id != original_id, (
f"reset_device_id_command() must write a new different device_id, "
f"got: {new_id!r} (same as original)"
)
# Verify output mentions device_id/identity
captured = capsys.readouterr()
output_lower = captured.out.lower()
assert "device_id" in output_lower or "identity" in output_lower, (
f"Output must mention 'device_id' or 'identity', got: {captured.out!r}"
)
# Verify output includes warning about orphaned session keys
assert "orphan" in output_lower or "warning" in output_lower, (
f"Output must include warning about orphaned session keys, got: {captured.out!r}"
)
def test_main_dispatches_to_reset_device_id(monkeypatch):
"""main() with 'reset-device-id' subcommand must invoke reset_device_id_command()."""
import muxplex.cli as cli_mod
calls = []
monkeypatch.setattr(cli_mod, "reset_device_id_command", lambda: calls.append(True))
with patch("sys.argv", ["muxplex", "reset-device-id"]):
cli_mod.main()
assert len(calls) == 1, (
"reset_device_id_command() must be called once for 'reset-device-id' subcommand"
)
def test_upgrade_git_install_uses_git_url(monkeypatch, capsys): def test_upgrade_git_install_uses_git_url(monkeypatch, capsys):
"""upgrade() for git installs must still use git+https URL.""" """upgrade() for git installs must still use git+https URL."""
import subprocess import subprocess