from __future__ import annotations import os import pytest from host_agent.local_account import LocalAccountStateError, LocalAccountStore def test_local_account_store_creates_and_verifies_password(tmp_path) -> None: path = tmp_path / "state" / "host_local_account.json" store = LocalAccountStore(path) assert store.load() is None created = store.create("operator", "correct horse battery staple") assert created.username == "operator" assert store.load() == created assert store.verify(created, "correct horse battery staple") is True assert store.verify(created, "wrong password") is False raw = path.read_text(encoding="utf-8") assert "correct horse battery staple" not in raw assert "password" not in raw.lower() or "password_hash" in raw if os.name != "nt": assert path.stat().st_mode & 0o777 == 0o600 def test_local_account_state_never_exposes_password_via_repr(tmp_path) -> None: store = LocalAccountStore(tmp_path / "host_local_account.json") created = store.create("operator", "hunter2") assert "hunter2" not in repr(created) assert "salt=" not in repr(created) assert "password_hash=" not in repr(created) def test_local_account_store_rejects_corrupted_file(tmp_path) -> None: path = tmp_path / "host_local_account.json" path.write_text('{"username": "operator"}', encoding="utf-8") store = LocalAccountStore(path) with pytest.raises(LocalAccountStateError): store.load() def test_local_account_store_rejects_invalid_json(tmp_path) -> None: path = tmp_path / "host_local_account.json" path.write_text("not json", encoding="utf-8") store = LocalAccountStore(path) with pytest.raises(LocalAccountStateError): store.load() def test_local_account_store_rejects_empty_credentials(tmp_path) -> None: store = LocalAccountStore(tmp_path / "host_local_account.json") with pytest.raises(ValueError): store.create("", "password") with pytest.raises(ValueError): store.create("operator", "")