fix: IPC byte order, globales GTK-Theme, Session-Vorauswahl
- ipc.py: !I (Big-Endian) → =I (Native Byte Order) für greetd-Protokoll - Per-User GTK-Theme entfernt, stattdessen globales Theme aus moongreet.toml - Last-Session pro User in /var/cache/moongreet/last-session/ speichern/laden - PKGBUILD und install-Hook für last-session-Cache erweitert
This commit is contained in:
@@ -43,6 +43,36 @@ class TestLoadConfig:
|
||||
|
||||
assert config.background is None
|
||||
|
||||
def test_loads_gtk_theme(self, tmp_path: Path) -> None:
|
||||
toml_file = tmp_path / "moongreet.toml"
|
||||
toml_file.write_text(
|
||||
"[appearance]\n"
|
||||
'gtk-theme = "Catppuccin-Mocha-Standard-Blue-Dark"\n'
|
||||
)
|
||||
|
||||
config = load_config(toml_file)
|
||||
|
||||
assert config.gtk_theme == "Catppuccin-Mocha-Standard-Blue-Dark"
|
||||
|
||||
def test_returns_none_gtk_theme_when_missing(self, tmp_path: Path) -> None:
|
||||
toml_file = tmp_path / "moongreet.toml"
|
||||
toml_file.write_text("[appearance]\n")
|
||||
|
||||
config = load_config(toml_file)
|
||||
|
||||
assert config.gtk_theme is None
|
||||
|
||||
def test_rejects_gtk_theme_with_path_traversal(self, tmp_path: Path) -> None:
|
||||
toml_file = tmp_path / "moongreet.toml"
|
||||
toml_file.write_text(
|
||||
"[appearance]\n"
|
||||
'gtk-theme = "../../etc/evil"\n'
|
||||
)
|
||||
|
||||
config = load_config(toml_file)
|
||||
|
||||
assert config.gtk_theme is None
|
||||
|
||||
def test_resolves_relative_path_against_config_dir(self, tmp_path: Path) -> None:
|
||||
toml_file = tmp_path / "moongreet.toml"
|
||||
toml_file.write_text(
|
||||
|
||||
@@ -10,7 +10,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from moongreet.greeter import faillock_warning, FAILLOCK_MAX_ATTEMPTS
|
||||
from moongreet.greeter import faillock_warning, FAILLOCK_MAX_ATTEMPTS, LAST_SESSION_DIR
|
||||
from moongreet.i18n import load_strings
|
||||
from moongreet.ipc import create_session, post_auth_response, start_session, cancel_session
|
||||
|
||||
@@ -59,14 +59,14 @@ class MockGreetd:
|
||||
header = self._recvall(conn, 4)
|
||||
if len(header) < 4:
|
||||
break
|
||||
length = struct.unpack("!I", header)[0]
|
||||
length = struct.unpack("=I", header)[0]
|
||||
payload = self._recvall(conn, length)
|
||||
msg = json.loads(payload.decode("utf-8"))
|
||||
self._received.append(msg)
|
||||
|
||||
# Send response
|
||||
resp_payload = json.dumps(response).encode("utf-8")
|
||||
conn.sendall(struct.pack("!I", len(resp_payload)) + resp_payload)
|
||||
conn.sendall(struct.pack("=I", len(resp_payload)) + resp_payload)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@@ -264,3 +264,45 @@ class TestLastUser:
|
||||
from moongreet.greeter import GreeterWindow
|
||||
result = GreeterWindow._load_last_user()
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestLastSession:
|
||||
"""Tests for saving and loading the last session per user."""
|
||||
|
||||
def test_save_and_load_last_session(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr("moongreet.greeter.LAST_SESSION_DIR", tmp_path)
|
||||
|
||||
from moongreet.greeter import GreeterWindow
|
||||
GreeterWindow._save_last_session("dominik", "Niri")
|
||||
|
||||
session_file = tmp_path / "dominik"
|
||||
assert session_file.exists()
|
||||
assert session_file.read_text() == "Niri"
|
||||
|
||||
result = GreeterWindow._load_last_session("dominik")
|
||||
assert result == "Niri"
|
||||
|
||||
def test_load_last_session_missing_file(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr("moongreet.greeter.LAST_SESSION_DIR", tmp_path)
|
||||
|
||||
from moongreet.greeter import GreeterWindow
|
||||
result = GreeterWindow._load_last_session("nobody")
|
||||
assert result is None
|
||||
|
||||
def test_load_last_session_rejects_oversized_name(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr("moongreet.greeter.LAST_SESSION_DIR", tmp_path)
|
||||
(tmp_path / "dominik").write_text("A" * 300)
|
||||
|
||||
from moongreet.greeter import GreeterWindow
|
||||
result = GreeterWindow._load_last_session("dominik")
|
||||
assert result is None
|
||||
|
||||
def test_save_last_session_validates_username(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Usernames with path traversal should not create files outside the cache dir."""
|
||||
monkeypatch.setattr("moongreet.greeter.LAST_SESSION_DIR", tmp_path)
|
||||
|
||||
from moongreet.greeter import GreeterWindow
|
||||
GreeterWindow._save_last_session("../../etc/evil", "Niri")
|
||||
|
||||
# Should not have created any file
|
||||
assert not (tmp_path / "../../etc/evil").exists()
|
||||
|
||||
+12
-12
@@ -38,7 +38,7 @@ class FakeSocket:
|
||||
def with_response(cls, response: dict) -> "FakeSocket":
|
||||
"""Create a FakeSocket pre-loaded with a length-prefixed JSON response."""
|
||||
payload = json.dumps(response).encode("utf-8")
|
||||
data = struct.pack("!I", len(payload)) + payload
|
||||
data = struct.pack("=I", len(payload)) + payload
|
||||
return cls(recv_data=data)
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ class TestSendMessage:
|
||||
send_message(sock, msg)
|
||||
|
||||
payload = json.dumps(msg).encode("utf-8")
|
||||
expected = struct.pack("!I", len(payload)) + payload
|
||||
expected = struct.pack("=I", len(payload)) + payload
|
||||
assert bytes(sock.sent) == expected
|
||||
|
||||
def test_sends_empty_dict(self) -> None:
|
||||
@@ -82,7 +82,7 @@ class TestSendMessage:
|
||||
send_message(sock, {})
|
||||
|
||||
payload = json.dumps({}).encode("utf-8")
|
||||
expected = struct.pack("!I", len(payload)) + payload
|
||||
expected = struct.pack("=I", len(payload)) + payload
|
||||
assert bytes(sock.sent) == expected
|
||||
|
||||
def test_sends_nested_message(self) -> None:
|
||||
@@ -93,7 +93,7 @@ class TestSendMessage:
|
||||
|
||||
# Verify the payload is correctly length-prefixed
|
||||
length_bytes = bytes(sock.sent[:4])
|
||||
length = struct.unpack("!I", length_bytes)[0]
|
||||
length = struct.unpack("=I", length_bytes)[0]
|
||||
decoded = json.loads(sock.sent[4:])
|
||||
assert length == len(json.dumps(msg).encode("utf-8"))
|
||||
assert decoded == msg
|
||||
@@ -132,7 +132,7 @@ class TestRecvMessage:
|
||||
"""recv() may return fewer bytes than requested — must loop."""
|
||||
response = {"type": "success"}
|
||||
payload = json.dumps(response).encode("utf-8")
|
||||
data = struct.pack("!I", len(payload)) + payload
|
||||
data = struct.pack("=I", len(payload)) + payload
|
||||
sock = FragmentingSocket(data, chunk_size=3)
|
||||
|
||||
result = recv_message(sock)
|
||||
@@ -141,7 +141,7 @@ class TestRecvMessage:
|
||||
|
||||
def test_rejects_oversized_payload(self) -> None:
|
||||
"""Payloads exceeding the size limit must be rejected."""
|
||||
header = struct.pack("!I", 10_000_000)
|
||||
header = struct.pack("=I", 10_000_000)
|
||||
sock = FakeSocket(recv_data=header)
|
||||
|
||||
with pytest.raises(ConnectionError, match="too large"):
|
||||
@@ -162,7 +162,7 @@ class TestCreateSession:
|
||||
result = create_session(sock, "dominik")
|
||||
|
||||
# Verify sent message
|
||||
length = struct.unpack("!I", bytes(sock.sent[:4]))[0]
|
||||
length = struct.unpack("=I", bytes(sock.sent[:4]))[0]
|
||||
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
||||
assert sent_msg == {"type": "create_session", "username": "dominik"}
|
||||
assert result == response
|
||||
@@ -177,7 +177,7 @@ class TestPostAuthResponse:
|
||||
|
||||
result = post_auth_response(sock, "mypassword")
|
||||
|
||||
length = struct.unpack("!I", bytes(sock.sent[:4]))[0]
|
||||
length = struct.unpack("=I", bytes(sock.sent[:4]))[0]
|
||||
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
||||
assert sent_msg == {
|
||||
"type": "post_auth_message_response",
|
||||
@@ -192,7 +192,7 @@ class TestPostAuthResponse:
|
||||
|
||||
result = post_auth_response(sock, None)
|
||||
|
||||
length = struct.unpack("!I", bytes(sock.sent[:4]))[0]
|
||||
length = struct.unpack("=I", bytes(sock.sent[:4]))[0]
|
||||
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
||||
assert sent_msg == {
|
||||
"type": "post_auth_message_response",
|
||||
@@ -209,7 +209,7 @@ class TestStartSession:
|
||||
|
||||
result = start_session(sock, ["Hyprland"])
|
||||
|
||||
length = struct.unpack("!I", bytes(sock.sent[:4]))[0]
|
||||
length = struct.unpack("=I", bytes(sock.sent[:4]))[0]
|
||||
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
||||
assert sent_msg == {"type": "start_session", "cmd": ["Hyprland"]}
|
||||
assert result == response
|
||||
@@ -220,7 +220,7 @@ class TestStartSession:
|
||||
|
||||
result = start_session(sock, ["sway", "--config", "/etc/sway/config"])
|
||||
|
||||
length = struct.unpack("!I", bytes(sock.sent[:4]))[0]
|
||||
length = struct.unpack("=I", bytes(sock.sent[:4]))[0]
|
||||
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
||||
assert sent_msg == {
|
||||
"type": "start_session",
|
||||
@@ -237,7 +237,7 @@ class TestCancelSession:
|
||||
|
||||
result = cancel_session(sock)
|
||||
|
||||
length = struct.unpack("!I", bytes(sock.sent[:4]))[0]
|
||||
length = struct.unpack("=I", bytes(sock.sent[:4]))[0]
|
||||
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
||||
assert sent_msg == {"type": "cancel_session"}
|
||||
assert result == response
|
||||
|
||||
Reference in New Issue
Block a user