wireguard: Improve reliability functional tests by waiting after ops

Tests:

- Functional tests run much more reliably. Although there were several failures
still observed due to following reasons:

  - Due to FreedomBox service receiving SIGSEGV (presumably during NM DBus
  operations).

  - Error something like Invalid UTF-8: setting not known during removal of
  a client.

  - Unknown failures caused by newly added clients not showing up on the page.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2026-08-25 16:02:52 -07:00 committed by James Valleroy
parent fa5c5706be
commit 06595e0a55
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
3 changed files with 139 additions and 30 deletions

View File

@ -73,23 +73,35 @@ class TestWireguardApp(functional.BaseAppTests):
with functional.wait_for_page_update(browser): with functional.wait_for_page_update(browser):
start_server_button.first.click() start_server_button.first.click()
browser.find_by_css('.btn-add-client').first.click() with functional.wait_for_page_update(browser):
browser.find_by_css('.btn-add-client').first.click()
browser.find_by_id('id_public_key').fill(key) browser.find_by_id('id_public_key').fill(key)
functional.submit(browser, form_class='form-add-client') functional.submit(browser, form_class='form-add-client')
def _edit_client(self, browser, key1, key2): def _edit_client(self, browser, key1, key2):
"""Edit a client""" """Edit a client"""
functional.nav_to_module(browser, 'wireguard') functional.nav_to_module(browser, 'wireguard')
browser.links.find_by_href(self._get_client_href(key1)).first.click() with functional.wait_for_page_update(browser):
browser.find_by_css('.btn-edit-client').first.click() browser.links.find_by_href(
self._get_client_href(key1)).first.click()
with functional.wait_for_page_update(browser):
browser.find_by_css('.btn-edit-client').first.click()
browser.find_by_id('id_public_key').fill(key2) browser.find_by_id('id_public_key').fill(key2)
functional.submit(browser, form_class='form-edit-client') functional.submit(browser, form_class='form-edit-client')
def _delete_client(self, browser, key): def _delete_client(self, browser, key):
"""Delete a client""" """Delete a client"""
functional.nav_to_module(browser, 'wireguard') functional.nav_to_module(browser, 'wireguard')
browser.links.find_by_href(self._get_client_href(key)).first.click() with functional.wait_for_page_update(browser):
browser.find_by_css('.btn-delete-client').first.click() browser.links.find_by_href(
self._get_client_href(key)).first.click()
with functional.wait_for_page_update(browser):
browser.find_by_css('.btn-delete-client').first.click()
functional.submit(browser, form_class='form-delete-client') functional.submit(browser, form_class='form-delete-client')
def test_add_edit_delete_client(self, session_browser): def test_add_edit_delete_client(self, session_browser):
@ -124,37 +136,38 @@ class TestWireguardApp(functional.BaseAppTests):
with functional.wait_for_page_update(session_browser): with functional.wait_for_page_update(session_browser):
start_server_button.first.click() start_server_button.first.click()
session_browser.find_by_css('.btn-auto-add-client').first.click() with functional.wait_for_page_update(session_browser):
session_browser.find_by_css('.btn-auto-add-client').first.click()
client_pubkey = session_browser.find_by_css( client_pubkey = session_browser.find_by_css(
'.pubkey-val').first.text.strip() '.pubkey-val').first.text.strip()
# Verify private key reveal # Verify private key reveal
privkey_reveal = session_browser.find_by_css( privkey_reveal = session_browser.find_by_css('.privkey-val')
'.privkey-val')
assert privkey_reveal, "Private key reveal should be present" assert privkey_reveal, "Private key reveal should be present"
privkey_reveal.click() privkey_reveal.click()
client_privkey = session_browser.find_by_css( client_privkey = session_browser.find_by_css(
'.privkey-val').text.splitlines()[1] '.privkey-val').text.splitlines()[1]
assert len(client_privkey) == 44, (("Private key should be base64 " assert len(client_privkey) == 44, (("Private key should be base64 "
"(44 chars)")) "(44 chars)"))
# Verify config download and QR links # Verify config download and QR links
download_link = session_browser.links.find_by_href( download_link = session_browser.links.find_by_href(
'/freedombox/apps/wireguard/client/auto-add/action/download/') '/freedombox/apps/wireguard/client/auto-add/action/download/')
qr_link = session_browser.links.find_by_href( qr_link = session_browser.links.find_by_href(
'/freedombox/apps/wireguard/client/auto-add/action/qr/') '/freedombox/apps/wireguard/client/auto-add/action/qr/')
assert download_link, "Download config link should exist" assert download_link, "Download config link should exist"
assert qr_link, "QR code link should exist" assert qr_link, "QR code link should exist"
# Submit to add the client # Submit to add the client
with functional.wait_for_page_update(session_browser): with functional.wait_for_page_update(session_browser):
session_browser.find_by_css( session_browser.find_by_css(
'.btn-auto-add-connection').first.click() '.btn-auto-add-connection').first.click()
# Verify client was added successfully # Verify client was added successfully
assert self._client_exists(session_browser, client_pubkey), (( assert self._client_exists(
"Auto-generated client should exist")) session_browser,
client_pubkey), (("Auto-generated client should exist"))
# Clean up # Clean up
self._delete_client(session_browser, client_pubkey) self._delete_client(session_browser, client_pubkey)
@ -179,8 +192,10 @@ class TestWireguardApp(functional.BaseAppTests):
return browser.find_by_css(f'tr.{key} > td').first.text return browser.find_by_css(f'tr.{key} > td').first.text
functional.nav_to_module(browser, 'wireguard') functional.nav_to_module(browser, 'wireguard')
href = self._get_server_href(browser, config['peer_public_key']) with functional.wait_for_page_update(browser):
href.first.click() href = self._get_server_href(browser, config['peer_public_key'])
href.first.click()
assert get_value('peer-endpoint') == config['peer_endpoint'] assert get_value('peer-endpoint') == config['peer_endpoint']
assert get_value('peer-public-key') == config['peer_public_key'] assert get_value('peer-public-key') == config['peer_public_key']
assert get_value('server-ip-address-and-network' assert get_value('server-ip-address-and-network'
@ -192,7 +207,9 @@ class TestWireguardApp(functional.BaseAppTests):
def _add_server(browser, config): def _add_server(browser, config):
"""Add a server.""" """Add a server."""
functional.nav_to_module(browser, 'wireguard') functional.nav_to_module(browser, 'wireguard')
browser.find_by_css('.btn-add-server').first.click() with functional.wait_for_page_update(browser):
browser.find_by_css('.btn-add-server').first.click()
browser.find_by_id('id_peer_endpoint').fill(config['peer_endpoint']) browser.find_by_id('id_peer_endpoint').fill(config['peer_endpoint'])
browser.find_by_id('id_peer_public_key').fill( browser.find_by_id('id_peer_public_key').fill(
config['peer_public_key']) config['peer_public_key'])
@ -205,9 +222,13 @@ class TestWireguardApp(functional.BaseAppTests):
def _edit_server(self, browser, config1, config2): def _edit_server(self, browser, config1, config2):
"""Edit a server.""" """Edit a server."""
functional.nav_to_module(browser, 'wireguard') functional.nav_to_module(browser, 'wireguard')
self._get_server_href(browser, with functional.wait_for_page_update(browser):
config1['peer_public_key']).first.click() href = self._get_server_href(browser, config1['peer_public_key'])
browser.find_by_css('.btn-edit-server').first.click() href.first.click()
with functional.wait_for_page_update(browser):
browser.find_by_css('.btn-edit-server').first.click()
browser.find_by_id('id_peer_endpoint').fill(config2['peer_endpoint']) browser.find_by_id('id_peer_endpoint').fill(config2['peer_endpoint'])
browser.find_by_id('id_peer_public_key').fill( browser.find_by_id('id_peer_public_key').fill(
config2['peer_public_key']) config2['peer_public_key'])
@ -220,8 +241,13 @@ class TestWireguardApp(functional.BaseAppTests):
def _delete_server(self, browser, config): def _delete_server(self, browser, config):
"""Delete a server""" """Delete a server"""
functional.nav_to_module(browser, 'wireguard') functional.nav_to_module(browser, 'wireguard')
self._get_server_href(browser, config['peer_public_key']).first.click() with functional.wait_for_page_update(browser):
browser.find_by_css('.btn-delete-server').first.click() href = self._get_server_href(browser, config['peer_public_key'])
href.first.click()
with functional.wait_for_page_update(browser):
browser.find_by_css('.btn-delete-server').first.click()
functional.submit(browser, form_class='form-delete-server') functional.submit(browser, form_class='form-delete-server')
def test_add_edit_delete_server(self, session_browser): def test_add_edit_delete_server(self, session_browser):

View File

@ -79,8 +79,7 @@ def get_nm_info():
settings_ipv6 = connection.get_setting_ip6_config() settings_ipv6 = connection.get_setting_ip6_config()
ip_address, ip_address_and_network = _get_nm_address_info( ip_address, ip_address_and_network = _get_nm_address_info(
settings_ipv4, settings_ipv6 settings_ipv4, settings_ipv6)
)
info['ip_address'] = ip_address info['ip_address'] = ip_address
info['ip_address_and_network'] = ip_address_and_network info['ip_address_and_network'] = ip_address_and_network
@ -162,7 +161,11 @@ def delete_connections():
connection.delete() connection.delete()
def _get_public_key_from_private_key(private_key): def _get_public_key_from_private_key(private_key: str | None) -> str | None:
"""Return public key from private key running wg command."""
if not private_key:
return None
process = subprocess.run(['wg', 'pubkey'], check=True, capture_output=True, process = subprocess.run(['wg', 'pubkey'], check=True, capture_output=True,
input=private_key.encode()) input=private_key.encode())
return process.stdout.decode().strip() return process.stdout.decode().strip()
@ -207,6 +210,28 @@ def add_server(settings):
network.add_connection(settings) network.add_connection(settings)
# Wait upto 10 seconds for server to appear
public_key = settings['wireguard']['peer_public_key']
for _ in range(10):
info = get_info()
if info['my_client'] and info['my_client']['servers']:
found = False
for _, server in info['my_client']['servers'].items():
for _, peer in server['peers'].items():
if peer['public_key'] == public_key:
found = True
break
if found:
break
if found:
break
time.sleep(1)
else:
logging.warning('Could not add server')
def edit_server(interface, settings): def edit_server(interface, settings):
"""Edit information for connecting to a server.""" """Edit information for connecting to a server."""
@ -220,6 +245,22 @@ def edit_server(interface, settings):
network.reactivate_connection(connection.get_uuid()) network.reactivate_connection(connection.get_uuid())
def delete_server(interface):
"""Delete information for connecting to a server."""
connection = network.get_connection_by_interface_name(interface)
network.delete_connection(connection.get_uuid())
# Wait upto 10 seconds for server to disappear
for _ in range(10):
info = get_nm_info()
if interface not in info:
break
time.sleep(1)
else:
logging.warning('Could not delete server')
def setup_server(): def setup_server():
"""Setup a server connection that clients can connect to.""" """Setup a server connection that clients can connect to."""
app = app_module.App.get('wireguard') app = app_module.App.get('wireguard')
@ -249,6 +290,16 @@ def setup_server():
network.add_connection(settings) network.add_connection(settings)
logger.info('Created new WireGuard server connection') logger.info('Created new WireGuard server connection')
# Wait upto 10 seconds for server to appear
for _ in range(10):
info = get_info()
if info['my_server'] and info['my_server']['public_key']:
break
time.sleep(1)
else:
logging.warning('Could not add server')
def _get_next_available_ip_address(settings): def _get_next_available_ip_address(settings):
"""Get the next available IP address to allocate to a client.""" """Get the next available IP address to allocate to a client."""
@ -311,6 +362,23 @@ def add_client(public_key):
connection.commit_changes(True) connection.commit_changes(True)
network.reactivate_connection(connection.get_uuid()) network.reactivate_connection(connection.get_uuid())
# Wait upto 10 seconds for client to appear
for _ in range(10):
info = get_info()
if info['my_server'] and info['my_server']['peers']:
found = False
for _, peer in info['my_server']['peers'].items():
if peer['public_key'] == public_key:
found = True
break
if found:
break
time.sleep(1)
else:
logging.warning('Could not add client %s', public_key)
def remove_client(public_key): def remove_client(public_key):
"""Remove permission for a client to connect our server.""" """Remove permission for a client to connect our server."""
@ -325,6 +393,23 @@ def remove_client(public_key):
connection.commit_changes(True) connection.commit_changes(True)
network.reactivate_connection(connection.get_uuid()) network.reactivate_connection(connection.get_uuid())
# Wait upto 10 seconds for client to disappear
for _ in range(10):
info = get_info()
if info['my_server']:
found = False
for _, peer in info['my_server']['peers'].items():
if peer['public_key'] == public_key:
found = True
break
if not found:
break
time.sleep(1)
else:
logging.warning('Could not remove client %s', public_key)
def build_client_config(client_ip: str, client_privkey: str, def build_client_config(client_ip: str, client_privkey: str,
client_pubkey: str, endpoint: str) -> str: client_pubkey: str, endpoint: str) -> str:

View File

@ -3,8 +3,8 @@
Views for WireGuard application. Views for WireGuard application.
""" """
from io import BytesIO
import urllib.parse import urllib.parse
from io import BytesIO
from django.contrib import messages from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
@ -14,7 +14,6 @@ from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import FormView, TemplateView, View from django.views.generic import FormView, TemplateView, View
from freedombox import network
from freedombox.modules.names.components import DomainName from freedombox.modules.names.components import DomainName
from freedombox.views import AppView from freedombox.views import AppView
@ -381,8 +380,7 @@ class DeleteServerView(SuccessMessageMixin, TemplateView):
def post(self, request, interface): def post(self, request, interface):
"""Delete the server.""" """Delete the server."""
connection = network.get_connection_by_interface_name(interface) utils.delete_server(interface)
network.delete_connection(connection.get_uuid())
messages.success(request, _('Server deleted.')) messages.success(request, _('Server deleted.'))
return redirect('wireguard:index') return redirect('wireguard:index')