fix: pure Python keypair generation, no wg CLI dependency
Replace subprocess calls to wg genkey/pubkey with cryptography library's X25519PrivateKey. This eliminates the wg CLI dependency for key generation, fixes device creation on machines without wireguard-tools, and removes the event loop blocking that caused WebSocket disconnects during device creation. Also fix E2E test teardown to use a fresh engine for cleanup, avoiding cross-event-loop issues with asyncpg connection pools.
This commit is contained in:
parent
92554d4089
commit
41a62832f7
8 changed files with 62 additions and 71 deletions
|
|
@ -1,9 +1,12 @@
|
|||
"""E2E test configuration — loads NiceGUI testing plugin and app."""
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
from sqlmodel import select
|
||||
|
||||
from wiregui.auth.passwords import hash_password
|
||||
from wiregui.config import get_settings
|
||||
from wiregui.db import async_session
|
||||
from wiregui.models.configuration import Configuration
|
||||
from wiregui.models.user import User
|
||||
|
|
@ -14,24 +17,32 @@ FAKE_SERVER_KEY = "SFake0ServerPubKey0000000000000000000000000w="
|
|||
TEST_EMAIL = "e2e-test@example.com"
|
||||
TEST_PASSWORD = "testpass123"
|
||||
|
||||
async def _delete_user_cascade(session, user_id):
|
||||
"""Delete a user and all related objects via raw SQL to avoid stale ORM cache issues."""
|
||||
from sqlalchemy import text
|
||||
for table in ("devices", "rules", "mfa_methods", "api_tokens", "oidc_connections"):
|
||||
await session.execute(text(f"DELETE FROM {table} WHERE user_id = :uid"), {"uid": user_id}) # noqa: S608
|
||||
await session.execute(text("DELETE FROM users WHERE id = :uid"), {"uid": user_id})
|
||||
_CHILD_TABLES = ("devices", "rules", "mfa_methods", "api_tokens", "oidc_connections")
|
||||
|
||||
|
||||
async def _cleanup_test_user():
|
||||
"""Delete the test user and all related objects using a fresh engine."""
|
||||
engine = create_async_engine(get_settings().database_url)
|
||||
async with engine.begin() as conn:
|
||||
# Find user id by email
|
||||
row = (await conn.execute(
|
||||
text("SELECT id FROM users WHERE email = :email"), {"email": TEST_EMAIL}
|
||||
)).first()
|
||||
if row:
|
||||
uid = row[0]
|
||||
for table in _CHILD_TABLES:
|
||||
await conn.execute(text(f"DELETE FROM {table} WHERE user_id = :uid"), {"uid": uid}) # noqa: S608
|
||||
await conn.execute(text("DELETE FROM users WHERE id = :uid"), {"uid": uid})
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def test_user():
|
||||
"""Create a test user and ensure server config has a public key."""
|
||||
async with async_session() as session:
|
||||
# Clean up any leftover from a previous failed run
|
||||
existing = (await session.execute(select(User).where(User.email == TEST_EMAIL))).scalar_one_or_none()
|
||||
if existing:
|
||||
await _delete_user_cascade(session, existing.id)
|
||||
await session.commit()
|
||||
# Clean up any leftover from a previous failed run
|
||||
await _cleanup_test_user()
|
||||
|
||||
async with async_session() as session:
|
||||
# Ensure a Configuration with a server key exists
|
||||
config = (await session.execute(select(Configuration).limit(1))).scalar_one_or_none()
|
||||
if config:
|
||||
|
|
@ -53,7 +64,4 @@ async def test_user():
|
|||
|
||||
yield user
|
||||
|
||||
# Teardown
|
||||
async with async_session() as session:
|
||||
await _delete_user_cascade(session, user.id)
|
||||
await session.commit()
|
||||
await _cleanup_test_user()
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
"""End-to-end tests for device management UI using NiceGUI's User fixture."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from nicegui.testing import User
|
||||
|
||||
from wiregui.models.user import User as UserModel
|
||||
from tests.e2e.conftest import TEST_EMAIL, TEST_PASSWORD
|
||||
|
||||
# Fake WG keys for testing (valid base64, 32 bytes)
|
||||
FAKE_PRIVATE_KEY = "YFake0PrivateKey00000000000000000000000000w="
|
||||
FAKE_PUBLIC_KEY = "ZFake0PublicKey000000000000000000000000000w="
|
||||
|
||||
|
||||
async def _login(user: User):
|
||||
"""Helper to log in via the UI."""
|
||||
|
|
@ -25,21 +19,18 @@ async def _login(user: User):
|
|||
@pytest.mark.parametrize("user", [{"storage": {}}], indirect=True)
|
||||
async def test_add_device_via_ui(user: User, test_user: UserModel):
|
||||
"""Test the full flow: login → devices → add device → see it in table."""
|
||||
with patch("wiregui.pages.devices.generate_keypair", new_callable=AsyncMock, return_value=(FAKE_PRIVATE_KEY, FAKE_PUBLIC_KEY)), \
|
||||
patch("wiregui.pages.devices.generate_preshared_key", return_value="cHJlc2hhcmVkMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA="):
|
||||
await _login(user)
|
||||
|
||||
await _login(user)
|
||||
# Open create dialog
|
||||
user.find("Add Device").click()
|
||||
await user.should_see("New Device")
|
||||
|
||||
# Open create dialog
|
||||
user.find("Add Device").click()
|
||||
await user.should_see("New Device")
|
||||
# Fill device name and submit
|
||||
user.find("Device Name").type("Test Laptop")
|
||||
user.find("Create").click()
|
||||
|
||||
# Fill device name and submit
|
||||
user.find("Device Name").type("Test Laptop")
|
||||
user.find("Create").click()
|
||||
|
||||
# Should see config dialog with the device config
|
||||
await user.should_see("Test Laptop")
|
||||
# Should see config dialog with the device config
|
||||
await user.should_see("Test Laptop")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("user", [{"storage": {}}], indirect=True)
|
||||
|
|
|
|||
|
|
@ -103,18 +103,16 @@ def test_build_client_config_no_psk():
|
|||
# --- Crypto (only if wg is installed) ---
|
||||
|
||||
|
||||
async def test_generate_keypair():
|
||||
"""Test keypair generation — requires `wg` CLI to be installed."""
|
||||
try:
|
||||
subprocess.run(["wg", "--version"], capture_output=True, check=True)
|
||||
except FileNotFoundError:
|
||||
pytest.skip("wg CLI not installed")
|
||||
|
||||
def test_generate_keypair():
|
||||
"""Test keypair generation (pure Python, no wg CLI needed)."""
|
||||
from wiregui.utils.crypto import generate_keypair, generate_preshared_key
|
||||
|
||||
priv, pub = await generate_keypair()
|
||||
priv, pub = generate_keypair()
|
||||
assert len(priv) == 44 # base64-encoded 32 bytes
|
||||
assert len(pub) == 44
|
||||
|
||||
psk = generate_preshared_key()
|
||||
assert len(psk) == 44
|
||||
|
||||
psk = generate_preshared_key()
|
||||
assert len(psk) == 44
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue