fix(gateway): wait for systemd restart readiness

This commit is contained in:
helix4u 2026-05-05 21:55:58 -06:00 committed by Teknium
parent 3cdbf334d5
commit d797755a1c
4 changed files with 587 additions and 78 deletions

View file

@ -1,4 +1,5 @@
import asyncio
import json
import sys
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock
@ -70,6 +71,15 @@ import gateway.platforms.discord as discord_platform # noqa: E402
from gateway.platforms.discord import DiscordAdapter # noqa: E402
@pytest.fixture(autouse=True)
def _speed_up_command_sync_mutation_pacing(monkeypatch):
monkeypatch.setattr(
DiscordAdapter,
"_command_sync_mutation_interval_seconds",
lambda self: 0.0,
)
class FakeTree:
def __init__(self):
self.sync = AsyncMock(return_value=[])
@ -536,6 +546,136 @@ async def test_post_connect_initialization_skips_sync_when_policy_off(monkeypatc
fake_tree.sync.assert_not_called()
@pytest.mark.asyncio
async def test_post_connect_initialization_skips_same_fingerprint_after_success(tmp_path, monkeypatch):
adapter = DiscordAdapter(PlatformConfig(enabled=True, token="test-token"))
monkeypatch.setattr("hermes_constants.get_hermes_home", lambda: tmp_path)
class _DesiredCommand:
def to_dict(self, tree):
return {
"name": "status",
"description": "Show Hermes status",
"type": 1,
"options": [],
}
fake_tree = SimpleNamespace(
get_commands=lambda: [_DesiredCommand()],
fetch_commands=AsyncMock(return_value=[]),
)
fake_http = SimpleNamespace(
upsert_global_command=AsyncMock(),
edit_global_command=AsyncMock(),
delete_global_command=AsyncMock(),
)
adapter._client = SimpleNamespace(
tree=fake_tree,
http=fake_http,
application_id=999,
user=SimpleNamespace(id=999),
)
await adapter._run_post_connect_initialization()
await adapter._run_post_connect_initialization()
fake_tree.fetch_commands.assert_awaited_once()
fake_http.upsert_global_command.assert_awaited_once()
@pytest.mark.asyncio
async def test_post_connect_initialization_respects_discord_retry_after(tmp_path, monkeypatch):
adapter = DiscordAdapter(PlatformConfig(enabled=True, token="test-token"))
monkeypatch.setattr("hermes_constants.get_hermes_home", lambda: tmp_path)
class _DesiredCommand:
def to_dict(self, tree):
return {
"name": "status",
"description": "Show Hermes status",
"type": 1,
"options": [],
}
adapter._client = SimpleNamespace(
tree=SimpleNamespace(get_commands=lambda: [_DesiredCommand()]),
application_id=999,
user=SimpleNamespace(id=999),
)
class _DiscordRateLimit(RuntimeError):
retry_after = 123.0
sync = AsyncMock(side_effect=_DiscordRateLimit("discord rate limited"))
monkeypatch.setattr(adapter, "_safe_sync_slash_commands", sync)
await adapter._run_post_connect_initialization()
await adapter._run_post_connect_initialization()
sync.assert_awaited_once()
state = json.loads((tmp_path / discord_platform._DISCORD_COMMAND_SYNC_STATE_FILE).read_text())
entry = state["999"]
assert entry["retry_after"] == 123.0
assert entry["retry_after_until"] > entry["last_attempt_at"]
@pytest.mark.asyncio
async def test_safe_sync_slash_commands_paces_mutation_writes(monkeypatch):
adapter = DiscordAdapter(PlatformConfig(enabled=True, token="test-token"))
monkeypatch.setattr(
DiscordAdapter,
"_command_sync_mutation_interval_seconds",
lambda self: 1.25,
)
sleeps = []
async def fake_sleep(delay):
sleeps.append(delay)
monkeypatch.setattr(discord_platform.asyncio, "sleep", fake_sleep)
class _DesiredCommand:
def __init__(self, payload):
self._payload = payload
def to_dict(self, tree):
assert tree is not None
return dict(self._payload)
desired_one = {
"name": "status",
"description": "Show Hermes status",
"type": 1,
"options": [],
}
desired_two = {
"name": "debug",
"description": "Generate a debug report",
"type": 1,
"options": [],
}
fake_tree = SimpleNamespace(
get_commands=lambda: [_DesiredCommand(desired_one), _DesiredCommand(desired_two)],
fetch_commands=AsyncMock(return_value=[]),
)
fake_http = SimpleNamespace(
upsert_global_command=AsyncMock(),
edit_global_command=AsyncMock(),
delete_global_command=AsyncMock(),
)
adapter._client = SimpleNamespace(
tree=fake_tree,
http=fake_http,
application_id=999,
user=SimpleNamespace(id=999),
)
summary = await adapter._safe_sync_slash_commands()
assert summary["created"] == 2
assert fake_http.upsert_global_command.await_count == 2
assert sleeps == [1.25]
@pytest.mark.asyncio
async def test_safe_sync_reads_permission_attrs_from_existing_command():
"""Regression: AppCommand.to_dict() in discord.py does NOT include