mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-27 09:40:40 +00:00
Add VoxVera Nostr static client
This commit is contained in:
1 parent
285d730369
commit
d415eb26a3
14 files changed
+12651
-11
No files matched your search
+344
-1
@@ -602,6 +602,51 @@ def test_serve_updates_url(tmp_path, monkeypatch):
|
||||
assert updated["tear_off_link"] == onion_url
|
||||
|
||||
|
||||
def test_serve_recovers_onion_url_from_session_file(tmp_path, monkeypatch):
|
||||
"""OnionShare 2.6 may not log the URL when using system Tor."""
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
cli.main(["build"])
|
||||
res_root = tmp_path / "data" / "voxvera"
|
||||
test_data_dir = tmp_path / "data"
|
||||
config = json.load(open(res_root / "src" / "config.json"))
|
||||
folder_name = config["folder_name"]
|
||||
dir_path = test_data_dir / "host" / folder_name
|
||||
|
||||
(dir_path / ".onionshare-session").write_text(
|
||||
json.dumps({"general": {"service_id": "sessionserviceid"}}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
monkeypatch.setattr(cli, "require_cmd", lambda c: True)
|
||||
orig_build = cli.build_assets
|
||||
|
||||
def safe_build_assets(cfg, download_path=None):
|
||||
dest = cli.ROOT / "host" / json.load(open(cfg))["folder_name"] / "config.json"
|
||||
if Path(cfg) == dest:
|
||||
return
|
||||
return orig_build(cfg, download_path=download_path)
|
||||
|
||||
monkeypatch.setattr(cli, "build_assets", safe_build_assets)
|
||||
monkeypatch.setattr(time, "sleep", lambda x: None)
|
||||
|
||||
class FakePopen:
|
||||
def __init__(self, cmd, stdout=None, stderr=None, env=None, **kwargs):
|
||||
self.pid = 43
|
||||
if stdout is not None:
|
||||
stdout.write("Onion.start_onion_service: key_type=ED25519-V3\n")
|
||||
stdout.flush()
|
||||
|
||||
def poll(self):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(cli.subprocess, "Popen", FakePopen)
|
||||
|
||||
cli.main(["serve"])
|
||||
|
||||
updated = json.load(open(dir_path / "config.json"))
|
||||
assert updated["tear_off_link"] == "http://sessionserviceid.onion"
|
||||
|
||||
|
||||
def test_quickstart_noninteractive(tmp_path, monkeypatch):
|
||||
"""Test quickstart builds and serves in one command."""
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
@@ -672,13 +717,311 @@ def test_install_systemd_autostart_writes_recovery_timer(tmp_path, monkeypatch):
|
||||
service_text = (service_dir / "voxvera-start.service").read_text(encoding="utf-8")
|
||||
timer_text = (service_dir / "voxvera-start.timer").read_text(encoding="utf-8")
|
||||
|
||||
assert "ExecStart=/tmp/voxvera start-all" in service_text
|
||||
assert "ExecStart=/tmp/voxvera _linux_autostart_start" in service_text
|
||||
assert "KillMode=process" in service_text
|
||||
assert "OnUnitActiveSec=5min" in timer_text
|
||||
assert "Persistent=true" in timer_text
|
||||
assert any(args[:4] == ["systemctl", "--user", "enable", "--now"] and args[4] == "voxvera-start.timer" for args in commands)
|
||||
assert any(args[:4] == ["systemctl", "--user", "start", "voxvera-start.service"] for args in commands)
|
||||
|
||||
|
||||
def test_linux_autostart_start_sets_hardened_environment(monkeypatch):
|
||||
events = []
|
||||
|
||||
monkeypatch.delenv("VOXVERA_ONIONSHARE_NO_AWAIT_PUBLICATION", raising=False)
|
||||
monkeypatch.delenv("VOXVERA_ONIONSHARE_SYSTEM_TOR", raising=False)
|
||||
monkeypatch.setattr(cli, "cleanup_onionshare_tmp", lambda max_age_minutes=60: events.append(("cleanup", max_age_minutes)) or 0)
|
||||
monkeypatch.setattr(cli, "_user_is_group_member", lambda group: False)
|
||||
monkeypatch.setattr(cli, "start_all_servers", lambda: events.append(("start_all", None)))
|
||||
|
||||
cli.linux_autostart_start()
|
||||
|
||||
assert events == [("cleanup", 60), ("start_all", None)]
|
||||
assert os.environ["VOXVERA_ONIONSHARE_NO_AWAIT_PUBLICATION"] == "1"
|
||||
assert os.environ["VOXVERA_ONIONSHARE_SYSTEM_TOR"] == "1"
|
||||
|
||||
|
||||
def test_linux_autostart_start_switches_to_debian_tor_group(monkeypatch):
|
||||
exec_call = {}
|
||||
|
||||
monkeypatch.setattr(cli, "cleanup_onionshare_tmp", lambda max_age_minutes=60: 0)
|
||||
monkeypatch.setattr(cli, "_user_is_group_member", lambda group: group == "debian-tor")
|
||||
monkeypatch.setattr(cli, "_current_group_names", lambda: {"user"})
|
||||
monkeypatch.setattr(cli, "_find_voxvera_bin", lambda: "/tmp/vox vera")
|
||||
|
||||
def fake_execvp(binary, args):
|
||||
exec_call["binary"] = binary
|
||||
exec_call["args"] = args
|
||||
raise RuntimeError("stop")
|
||||
|
||||
monkeypatch.setattr(cli.os, "execvp", fake_execvp)
|
||||
|
||||
with pytest.raises(RuntimeError, match="stop"):
|
||||
cli.linux_autostart_start()
|
||||
|
||||
assert exec_call["binary"] == "sg"
|
||||
assert exec_call["args"][:3] == ["sg", "debian-tor", "-c"]
|
||||
assert "VOXVERA_ONIONSHARE_NO_AWAIT_PUBLICATION=1" in exec_call["args"][3]
|
||||
assert "VOXVERA_ONIONSHARE_SYSTEM_TOR=1" in exec_call["args"][3]
|
||||
assert "'/tmp/vox vera' start-all" in exec_call["args"][3]
|
||||
|
||||
|
||||
def test_serve_uses_internal_onionshare_when_no_await_requested(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
cli.main(["build"])
|
||||
res_root = tmp_path / "data" / "voxvera"
|
||||
test_data_dir = tmp_path / "data"
|
||||
config = json.load(open(res_root / "src" / "config.json"))
|
||||
folder_name = config["folder_name"]
|
||||
dir_path = test_data_dir / "host" / folder_name
|
||||
|
||||
monkeypatch.setenv("VOXVERA_ONIONSHARE_NO_AWAIT_PUBLICATION", "1")
|
||||
monkeypatch.setattr(cli, "require_cmd", lambda c: True)
|
||||
monkeypatch.setattr(cli, "build_assets", lambda *args, **kwargs: None)
|
||||
monkeypatch.setattr(time, "sleep", lambda x: None)
|
||||
monkeypatch.setattr(cli.shutil, "which", lambda cmd: "/usr/bin/onionshare-cli" if cmd == "onionshare-cli" else None)
|
||||
|
||||
onion_url = "http://internalpath.onion"
|
||||
popen_calls = []
|
||||
|
||||
class FakePopen:
|
||||
def __init__(self, cmd, stdout=None, stderr=None, env=None, **kwargs):
|
||||
popen_calls.append(cmd)
|
||||
self.pid = 44
|
||||
if stdout is not None:
|
||||
stdout.write(f"URL: {onion_url}\n")
|
||||
stdout.flush()
|
||||
|
||||
def poll(self):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(cli.subprocess, "Popen", FakePopen)
|
||||
|
||||
cli.main(["serve"])
|
||||
|
||||
assert popen_calls
|
||||
assert "_internal_onionshare" in popen_calls[0]
|
||||
assert "/usr/bin/onionshare-cli" not in popen_calls[0]
|
||||
updated = json.load(open(dir_path / "config.json"))
|
||||
assert updated["tear_off_link"] == onion_url
|
||||
|
||||
|
||||
def _write_nostr_event(path, payload_overrides=None, event_overrides=None):
|
||||
payload = {
|
||||
"type": "voxvera_flyer",
|
||||
"version": 1,
|
||||
"folder_name": "nostr-flyer",
|
||||
"lang": "en",
|
||||
"name": "Nostr Flyer",
|
||||
"title": "PUBLIC NOTICE",
|
||||
"subtitle": "DO ~~NOT~~ IGNORE",
|
||||
"headline": "NOSTR SOURCE TEST",
|
||||
"content": "This flyer came from a Nostr event.",
|
||||
"url_message": "Read the source.",
|
||||
"url": "https://example.com/source",
|
||||
"footer_message": "npub test",
|
||||
"attachment_path": "",
|
||||
"attachment_filename": "",
|
||||
"qr_target": "flyer_url",
|
||||
}
|
||||
if payload_overrides:
|
||||
payload.update(payload_overrides)
|
||||
event = {
|
||||
"id": "f" * 64,
|
||||
"pubkey": "a" * 64,
|
||||
"created_at": 1760000000,
|
||||
"kind": 30078,
|
||||
"tags": [["d", "voxvera:nostr-flyer"], ["t", "voxvera"], ["t", "flyer"]],
|
||||
"content": json.dumps(payload),
|
||||
"sig": "b" * 128,
|
||||
}
|
||||
if event_overrides:
|
||||
event.update(event_overrides)
|
||||
path.write_text(json.dumps(event), encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def test_nostr_validate_accepts_local_event_file(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
event_path = _write_nostr_event(tmp_path / "event.json")
|
||||
|
||||
cli.main(["nostr", "validate", str(event_path)])
|
||||
|
||||
assert "Valid VoxVera Nostr flyer: NOSTR SOURCE TEST" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_nostr_import_writes_existing_config_schema(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
event_path = _write_nostr_event(tmp_path / "event.json")
|
||||
|
||||
cli.main(["nostr", "import", str(event_path)])
|
||||
|
||||
config_path = tmp_path / "data" / "host" / "nostr-flyer" / "config.json"
|
||||
imported = json.loads(config_path.read_text(encoding="utf-8"))
|
||||
assert imported["folder_name"] == "nostr-flyer"
|
||||
assert imported["headline"] == "NOSTR SOURCE TEST"
|
||||
assert imported["content"] == "This flyer came from a Nostr event."
|
||||
assert imported["url"] == "https://example.com/source"
|
||||
assert "Imported Nostr flyer to" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_nostr_import_rejects_existing_site_without_overwrite(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
event_path = _write_nostr_event(tmp_path / "event.json")
|
||||
cli.main(["nostr", "import", str(event_path)])
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
cli.main(["nostr", "import", str(event_path)])
|
||||
|
||||
assert "already exists" in capsys.readouterr().err
|
||||
|
||||
|
||||
def test_nostr_build_generates_existing_flyer_output(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
event_path = _write_nostr_event(tmp_path / "event.json")
|
||||
|
||||
cli.main(["nostr", "build", str(event_path)])
|
||||
|
||||
host_dir = tmp_path / "data" / "host" / "nostr-flyer"
|
||||
assert (host_dir / "config.json").exists()
|
||||
assert (host_dir / "index.html").exists()
|
||||
html = (host_dir / "index.html").read_text(encoding="utf-8")
|
||||
assert "NOSTR SOURCE TEST" in html
|
||||
|
||||
|
||||
def test_nostr_serve_builds_then_uses_existing_serve_path(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
event_path = _write_nostr_event(tmp_path / "event.json")
|
||||
served = []
|
||||
monkeypatch.setattr(cli, "serve", lambda config_path: served.append(Path(config_path).name) or "http://ok.onion")
|
||||
|
||||
cli.main(["nostr", "serve", str(event_path)])
|
||||
|
||||
assert served == ["config.json"]
|
||||
assert (tmp_path / "data" / "host" / "nostr-flyer" / "index.html").exists()
|
||||
|
||||
|
||||
def test_nostr_validate_rejects_unsafe_url_scheme(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
event_path = _write_nostr_event(tmp_path / "event.json", {"url": "javascript:alert(1)"})
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
cli.main(["nostr", "validate", str(event_path)])
|
||||
|
||||
assert "Unsupported URL scheme" in capsys.readouterr().err
|
||||
|
||||
|
||||
def test_nostr_validate_rejects_raw_html(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
event_path = _write_nostr_event(tmp_path / "event.json", {"content": "<script>alert(1)</script>"})
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
cli.main(["nostr", "validate", str(event_path)])
|
||||
|
||||
assert "contains raw HTML" in capsys.readouterr().err
|
||||
|
||||
|
||||
def test_nostr_validate_rejects_non_file_identifier_in_phase_one(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
cli.main(["nostr", "validate", "note1example"])
|
||||
|
||||
assert "Phase 1 only supports local Nostr event JSON files" in capsys.readouterr().err
|
||||
|
||||
|
||||
def test_is_server_running_returns_false_for_unreachable_onion(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
test_data_dir = tmp_path / "data"
|
||||
host_dir = test_data_dir / "host" / "voxvera"
|
||||
host_dir.mkdir(parents=True, exist_ok=True)
|
||||
(host_dir / "server.pid").write_text("123\n", encoding="utf-8")
|
||||
(host_dir / "config.json").write_text(
|
||||
json.dumps({"folder_name": "voxvera", "tear_off_link": "http://examplehiddenservice.onion"}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
old = time.time() - 1200
|
||||
os.utime(host_dir / "server.pid", (old, old))
|
||||
|
||||
monkeypatch.setattr(platform, "system", lambda: "Linux")
|
||||
monkeypatch.setattr(cli.os, "kill", lambda pid, sig: None)
|
||||
monkeypatch.setattr(cli.subprocess, "check_output", lambda *args, **kwargs: b"onionshare-cli --website")
|
||||
monkeypatch.setattr(cli.shutil, "which", lambda cmd: "/usr/bin/curl" if cmd == "curl" else None)
|
||||
|
||||
class _Result:
|
||||
returncode = 97
|
||||
stdout = ""
|
||||
|
||||
monkeypatch.setattr(cli.subprocess, "run", lambda *args, **kwargs: _Result())
|
||||
|
||||
assert cli.is_server_running("voxvera") is False
|
||||
assert (host_dir / "server.pid").exists()
|
||||
|
||||
|
||||
def test_is_server_running_keeps_recent_unreachable_onion_alive(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
test_data_dir = tmp_path / "data"
|
||||
host_dir = test_data_dir / "host" / "voxvera"
|
||||
host_dir.mkdir(parents=True, exist_ok=True)
|
||||
(host_dir / "server.pid").write_text("123\n", encoding="utf-8")
|
||||
(host_dir / "config.json").write_text(
|
||||
json.dumps({"folder_name": "voxvera", "tear_off_link": "http://examplehiddenservice.onion"}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
monkeypatch.setattr(platform, "system", lambda: "Linux")
|
||||
monkeypatch.setattr(cli.os, "kill", lambda pid, sig: None)
|
||||
monkeypatch.setattr(cli.subprocess, "check_output", lambda *args, **kwargs: b"onionshare-cli --website")
|
||||
monkeypatch.setattr(cli.shutil, "which", lambda cmd: "/usr/bin/curl" if cmd == "curl" else None)
|
||||
|
||||
class _Result:
|
||||
returncode = 97
|
||||
stdout = ""
|
||||
|
||||
monkeypatch.setattr(cli.subprocess, "run", lambda *args, **kwargs: _Result())
|
||||
|
||||
assert cli.is_server_running("voxvera") is True
|
||||
|
||||
|
||||
def test_start_all_servers_restarts_unhealthy_site(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
host_dir = tmp_path / "data" / "host" / "voxvera"
|
||||
host_dir.mkdir(parents=True, exist_ok=True)
|
||||
(host_dir / "config.json").write_text(json.dumps({"folder_name": "voxvera"}), encoding="utf-8")
|
||||
(host_dir / "server.pid").write_text("123\n", encoding="utf-8")
|
||||
|
||||
events = []
|
||||
monkeypatch.setattr(cli, "_seed_bundled_sites", lambda: None)
|
||||
monkeypatch.setattr(cli, "is_server_running", lambda name: False)
|
||||
monkeypatch.setattr(cli, "stop_server", lambda name: events.append(("stop", name)))
|
||||
monkeypatch.setattr(cli, "serve", lambda path: events.append(("serve", Path(path).name)) or "http://ok.onion")
|
||||
|
||||
cli.start_all_servers()
|
||||
|
||||
assert events == [("stop", "voxvera"), ("serve", "config.json")]
|
||||
output = capsys.readouterr().out
|
||||
assert "[RESTART] voxvera: existing process failed health check" in output
|
||||
assert "[OK] voxvera: http://ok.onion" in output
|
||||
|
||||
|
||||
def test_stop_server_kills_process_group_on_linux(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
host_dir = tmp_path / "data" / "host" / "voxvera"
|
||||
host_dir.mkdir(parents=True, exist_ok=True)
|
||||
pid_file = host_dir / "server.pid"
|
||||
pid_file.write_text("456\n", encoding="utf-8")
|
||||
|
||||
killed = []
|
||||
monkeypatch.setattr(platform, "system", lambda: "Linux")
|
||||
monkeypatch.setattr(cli.os, "killpg", lambda pid, sig: killed.append((pid, sig)))
|
||||
|
||||
cli.stop_server("voxvera")
|
||||
|
||||
assert killed == [(456, cli.signal.SIGTERM)]
|
||||
assert not pid_file.exists()
|
||||
assert "Stopped voxvera (PID 456)" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_get_tor_ports_with_env_vars(monkeypatch):
|
||||
"""Test that get_tor_ports respects environment variables."""
|
||||
monkeypatch.setenv("TOR_SOCKS_PORT", "9999")
|
||||
|
||||
Reference in new issue
Block a user