mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-03-11 09:04:54 +00:00
tests: functional: Re-organize step definitions and helper methods
- Move non-reusable app specific step definitions and helper methods into <app>/tests/test_functional.py. - Merge reusable helper methods into plinth.tests.functional - Merge reusable step definitions into plinth.tests.functional.step_definitions - avahi, datetime, ikiwiki: Reuse common methods to avoid repetition. Avoid mapping from app nicknames to actual app names. - deluge, transmission: Make a copy of sample.torrent for each app to avoid clogging common place. - Implement functional.visit() to simplify a lot of browser.visit() calls. - Ensure that name of the mark on functional tests for an app is same as name of the app. This will help with predicting the mark when running tests for a particular app. Tests performed: - Run all functional tests. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: Joseph Nuthalapati <njoseph@riseup.net>
This commit is contained in:
parent
8fac6a71fe
commit
80dff7bf9c
@ -15,11 +15,7 @@ try:
|
||||
except ImportError:
|
||||
_bdd_available = False
|
||||
else:
|
||||
from plinth.tests.functional.step_definitions.application import *
|
||||
from plinth.tests.functional.step_definitions.interface import *
|
||||
from plinth.tests.functional.step_definitions.service import *
|
||||
from plinth.tests.functional.step_definitions.site import *
|
||||
from plinth.tests.functional.step_definitions.system import *
|
||||
from plinth.tests.functional.step_definitions import *
|
||||
|
||||
|
||||
def pytest_ignore_collect(path, config):
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@system @essential @service_discovery
|
||||
Feature: Service Discovery
|
||||
@system @essential @avahi
|
||||
Feature: Avahi Service Discovery
|
||||
Configure service discovery.
|
||||
|
||||
Background:
|
||||
Given I'm a logged in user
|
||||
|
||||
Scenario: Disable service discovery application
|
||||
Given the service discovery application is enabled
|
||||
When I disable the service discovery application
|
||||
Then the service discovery service should not be running
|
||||
Scenario: Disable avahi application
|
||||
Given the avahi application is enabled
|
||||
When I disable the avahi application
|
||||
Then the avahi service should not be running
|
||||
|
||||
Scenario: Enable service discovery application
|
||||
Given the service discovery application is disabled
|
||||
When I enable the service discovery application
|
||||
Then the service discovery service should be running
|
||||
Scenario: Enable avahi application
|
||||
Given the avahi application is disabled
|
||||
When I enable the avahi application
|
||||
Then the avahi service should be running
|
||||
|
||||
@ -3,6 +3,88 @@
|
||||
Functional, browser based tests for backups app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
import os
|
||||
import tempfile
|
||||
import urllib.parse
|
||||
|
||||
import requests
|
||||
from pytest import fixture
|
||||
from pytest_bdd import parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('backups.feature')
|
||||
|
||||
|
||||
@fixture(scope='session')
|
||||
def downloaded_file_info():
|
||||
return dict()
|
||||
|
||||
|
||||
@when(parsers.parse('I open the main page'))
|
||||
def open_main_page(session_browser):
|
||||
_open_main_page(session_browser)
|
||||
|
||||
|
||||
@then(parsers.parse('the main page should be shown'))
|
||||
def main_page_is_shown(session_browser):
|
||||
assert (session_browser.url.endswith('/plinth/'))
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I download the app data backup with name {archive_name:w}'))
|
||||
def backup_download(session_browser, downloaded_file_info, archive_name):
|
||||
file_path = _download(session_browser, archive_name)
|
||||
downloaded_file_info['path'] = file_path
|
||||
|
||||
|
||||
@when(parsers.parse('I restore the downloaded app data backup'))
|
||||
def backup_restore_from_upload(session_browser, app_name,
|
||||
downloaded_file_info):
|
||||
path = downloaded_file_info["path"]
|
||||
try:
|
||||
_upload_and_restore(session_browser, app_name, path)
|
||||
except Exception as err:
|
||||
raise err
|
||||
finally:
|
||||
os.remove(path)
|
||||
|
||||
|
||||
def _open_main_page(browser):
|
||||
with functional.wait_for_page_update(browser):
|
||||
browser.find_link_by_href('/plinth/').first.click()
|
||||
|
||||
|
||||
def _download_file_logged_in(browser, url, suffix=''):
|
||||
"""Download a file from Plinth, pretend being logged in via cookies"""
|
||||
if not url.startswith("http"):
|
||||
current_url = urllib.parse.urlparse(browser.url)
|
||||
url = "%s://%s%s" % (current_url.scheme, current_url.netloc, url)
|
||||
cookies = browser.driver.get_cookies()
|
||||
cookies = {cookie["name"]: cookie["value"] for cookie in cookies}
|
||||
response = requests.get(url, verify=False, cookies=cookies)
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
|
||||
for chunk in response.iter_content(chunk_size=128):
|
||||
temp_file.write(chunk)
|
||||
return temp_file.name
|
||||
|
||||
|
||||
def _download(browser, archive_name=None):
|
||||
functional.nav_to_module(browser, 'backups')
|
||||
href = f'/plinth/sys/backups/root/download/{archive_name}/'
|
||||
url = functional.base_url + href
|
||||
file_path = _download_file_logged_in(browser, url, suffix='.tar.gz')
|
||||
return file_path
|
||||
|
||||
|
||||
def _upload_and_restore(browser, app_name, downloaded_file_path):
|
||||
functional.nav_to_module(browser, 'backups')
|
||||
browser.find_link_by_href('/plinth/sys/backups/upload/').first.click()
|
||||
fileinput = browser.driver.find_element_by_id('id_backups-file')
|
||||
fileinput.send_keys(downloaded_file_path)
|
||||
# submit upload form
|
||||
functional.submit(browser)
|
||||
# submit restore form
|
||||
with functional.wait_for_page_update(browser,
|
||||
expected_url='/plinth/sys/backups/'):
|
||||
functional.submit(browser)
|
||||
|
||||
@ -3,6 +3,42 @@
|
||||
Functional, browser based tests for bind app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('bind.feature')
|
||||
|
||||
|
||||
@given(parsers.parse('bind DNSSEC is {enable:w}'))
|
||||
def bind_given_enable_dnssec(session_browser, enable):
|
||||
should_enable = (enable == 'enabled')
|
||||
_enable_dnssec(session_browser, should_enable)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable:w} bind DNSSEC'))
|
||||
def bind_enable_dnssec(session_browser, enable):
|
||||
should_enable = (enable == 'enable')
|
||||
_enable_dnssec(session_browser, should_enable)
|
||||
|
||||
|
||||
@then(parsers.parse('bind DNSSEC should be {enabled:w}'))
|
||||
def bind_assert_dnssec(session_browser, enabled):
|
||||
assert _get_dnssec(session_browser) == (enabled == 'enabled')
|
||||
|
||||
|
||||
def _enable_dnssec(browser, enable):
|
||||
"""Enable/disable DNSSEC in bind configuration."""
|
||||
functional.nav_to_module(browser, 'bind')
|
||||
if enable:
|
||||
browser.check('enable_dnssec')
|
||||
else:
|
||||
browser.uncheck('enable_dnssec')
|
||||
|
||||
functional.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def _get_dnssec(browser):
|
||||
"""Return whether DNSSEC is enabled/disabled in bind configuration."""
|
||||
functional.nav_to_module(browser, 'bind')
|
||||
return browser.find_by_name('enable_dnssec').first.checked
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@system @essential @configuration
|
||||
@system @essential @config
|
||||
Feature: Configuration
|
||||
Configure the system.
|
||||
|
||||
@ -21,4 +21,3 @@ Scenario: Change webserver home page
|
||||
And the home page is syncthing
|
||||
When I change the home page to plinth
|
||||
Then the home page should be plinth
|
||||
|
||||
|
||||
@ -3,6 +3,75 @@
|
||||
Functional, browser based tests for config app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('config.feature')
|
||||
|
||||
|
||||
@given(parsers.parse('the home page is {app_name:w}'))
|
||||
def set_home_page(session_browser, app_name):
|
||||
_set_home_page(session_browser, app_name)
|
||||
|
||||
|
||||
@when(parsers.parse('I change the hostname to {hostname:w}'))
|
||||
def change_hostname_to(session_browser, hostname):
|
||||
_set_hostname(session_browser, hostname)
|
||||
|
||||
|
||||
@when(parsers.parse('I change the domain name to {domain:S}'))
|
||||
def change_domain_name_to(session_browser, domain):
|
||||
functional.set_domain_name(session_browser, domain)
|
||||
|
||||
|
||||
@when(parsers.parse('I change the home page to {app_name:w}'))
|
||||
def change_home_page_to(session_browser, app_name):
|
||||
_set_home_page(session_browser, app_name)
|
||||
|
||||
|
||||
@then(parsers.parse('the hostname should be {hostname:w}'))
|
||||
def hostname_should_be(session_browser, hostname):
|
||||
assert _get_hostname(session_browser) == hostname
|
||||
|
||||
|
||||
@then(parsers.parse('the domain name should be {domain:S}'))
|
||||
def domain_name_should_be(session_browser, domain):
|
||||
assert _get_domain_name(session_browser) == domain
|
||||
|
||||
|
||||
@then(parsers.parse('the home page should be {app_name:w}'))
|
||||
def home_page_should_be(session_browser, app_name):
|
||||
assert _check_home_page_redirect(session_browser, app_name)
|
||||
|
||||
|
||||
def _get_hostname(browser):
|
||||
functional.nav_to_module(browser, 'config')
|
||||
return browser.find_by_id('id_hostname').value
|
||||
|
||||
|
||||
def _set_hostname(browser, hostname):
|
||||
functional.nav_to_module(browser, 'config')
|
||||
browser.find_by_id('id_hostname').fill(hostname)
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _get_domain_name(browser):
|
||||
functional.nav_to_module(browser, 'config')
|
||||
return browser.find_by_id('id_domainname').value
|
||||
|
||||
|
||||
def _set_home_page(browser, home_page):
|
||||
if 'plinth' not in home_page and 'apache' not in home_page:
|
||||
home_page = 'shortcut-' + home_page
|
||||
|
||||
functional.nav_to_module(browser, 'config')
|
||||
drop_down = browser.find_by_id('id_homepage')
|
||||
drop_down.select(home_page)
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _check_home_page_redirect(browser, app_name):
|
||||
functional.visit(browser, '/')
|
||||
return browser.find_by_xpath(
|
||||
"//a[contains(@href, '/plinth/') and @title='FreedomBox']")
|
||||
|
||||
@ -3,6 +3,124 @@
|
||||
Functional, browser based tests for coquelicot app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
import random
|
||||
import tempfile
|
||||
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('coquelicot.feature')
|
||||
|
||||
|
||||
@given('a sample local file')
|
||||
def sample_local_file():
|
||||
file_path, contents = _create_sample_local_file()
|
||||
return dict(file_path=file_path, contents=contents)
|
||||
|
||||
|
||||
@when(parsers.parse('I modify the maximum file size of coquelicot to {size:d}')
|
||||
)
|
||||
def modify_max_file_size(session_browser, size):
|
||||
_modify_max_file_size(session_browser, size)
|
||||
|
||||
|
||||
@then(parsers.parse('the maximum file size of coquelicot should be {size:d}'))
|
||||
def assert_max_file_size(session_browser, size):
|
||||
assert _get_max_file_size(session_browser) == size
|
||||
|
||||
|
||||
@when(parsers.parse('I modify the coquelicot upload password to {password:w}'))
|
||||
def modify_upload_password(session_browser, password):
|
||||
_modify_upload_password(session_browser, password)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'I should be able to login to coquelicot with password {password:w}'))
|
||||
def verify_upload_password(session_browser, password):
|
||||
_verify_upload_password(session_browser, password)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I upload the sample local file to coquelicot with password '
|
||||
'{password:w}'))
|
||||
def coquelicot_upload_file(session_browser, sample_local_file, password):
|
||||
url = _upload_file(session_browser, sample_local_file['file_path'],
|
||||
password)
|
||||
sample_local_file['upload_url'] = url
|
||||
|
||||
|
||||
@when('I download the uploaded file from coquelicot')
|
||||
def coquelicot_download_file(sample_local_file):
|
||||
file_path = functional.download_file_outside_browser(
|
||||
sample_local_file['upload_url'])
|
||||
sample_local_file['download_path'] = file_path
|
||||
|
||||
|
||||
@then('contents of downloaded sample file should be same as sample local file')
|
||||
def coquelicot_compare_upload_download_files(sample_local_file):
|
||||
_compare_files(sample_local_file['file_path'],
|
||||
sample_local_file['download_path'])
|
||||
|
||||
|
||||
def _create_sample_local_file():
|
||||
"""Create a sample file for upload using browser."""
|
||||
contents = bytearray(random.getrandbits(8) for _ in range(64))
|
||||
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
||||
temp_file.write(contents)
|
||||
|
||||
return temp_file.name, contents
|
||||
|
||||
|
||||
def _verify_upload_password(browser, password):
|
||||
functional.visit(browser, '/coquelicot')
|
||||
# ensure the password form is scrolled into view
|
||||
browser.execute_script('window.scrollTo(100, 0)')
|
||||
browser.find_by_id('upload_password').fill(password)
|
||||
actions = ActionChains(browser.driver)
|
||||
actions.send_keys(Keys.RETURN)
|
||||
actions.perform()
|
||||
assert functional.eventually(browser.is_element_present_by_css,
|
||||
args=['div[style*="display: none;"]'])
|
||||
|
||||
|
||||
def _upload_file(browser, file_path, password):
|
||||
"""Upload a local file from disk to coquelicot."""
|
||||
_verify_upload_password(browser, password)
|
||||
browser.attach_file('file', file_path)
|
||||
functional.submit(browser)
|
||||
assert functional.eventually(browser.is_element_present_by_css,
|
||||
args=['#content .url'])
|
||||
url_textarea = browser.find_by_css('#content .url textarea').first
|
||||
return url_textarea.value
|
||||
|
||||
|
||||
def _modify_max_file_size(browser, size):
|
||||
"""Change the maximum file size of coquelicot to the given value"""
|
||||
functional.visit(browser, '/plinth/apps/coquelicot/')
|
||||
browser.find_by_id('id_max_file_size').fill(size)
|
||||
functional.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def _get_max_file_size(browser):
|
||||
"""Get the maximum file size of coquelicot"""
|
||||
functional.visit(browser, '/plinth/apps/coquelicot/')
|
||||
return int(browser.find_by_id('id_max_file_size').value)
|
||||
|
||||
|
||||
def _modify_upload_password(browser, password):
|
||||
"""Change the upload password for coquelicot to the given value"""
|
||||
functional.visit(browser, '/plinth/apps/coquelicot/')
|
||||
browser.find_by_id('id_upload_password').fill(password)
|
||||
functional.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def _compare_files(file1, file2):
|
||||
"""Assert that the contents of two files are the same."""
|
||||
file1_contents = open(file1, 'rb').read()
|
||||
file2_contents = open(file2, 'rb').read()
|
||||
|
||||
assert file1_contents == file2_contents
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
@essential @date_and_time @system
|
||||
@essential @datetime @system
|
||||
Feature: Date and Time
|
||||
Configure time zone and network time service.
|
||||
|
||||
@ -8,16 +8,16 @@ Background:
|
||||
Given I'm a logged in user
|
||||
|
||||
Scenario: Disable network time application
|
||||
Given the network time application can be disabled
|
||||
And the network time application is enabled
|
||||
When I disable the network time application
|
||||
Then the network time service should not be running
|
||||
Given the datetime application can be disabled
|
||||
And the datetime application is enabled
|
||||
When I disable the datetime application
|
||||
Then the datetime service should not be running
|
||||
|
||||
Scenario: Enable network time application
|
||||
Given the network time application can be disabled
|
||||
And the network time application is disabled
|
||||
When I enable the network time application
|
||||
Then the network time service should be running
|
||||
Given the datetime application can be disabled
|
||||
And the datetime application is disabled
|
||||
When I enable the datetime application
|
||||
Then the datetime service should be running
|
||||
|
||||
Scenario: Set timezone
|
||||
When I set the time zone to Africa/Abidjan
|
||||
|
||||
@ -3,6 +3,31 @@
|
||||
Functional, browser based tests for datetime app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('datetime.feature')
|
||||
|
||||
|
||||
@when(parsers.parse('I set the time zone to {time_zone:S}'))
|
||||
def time_zone_set(session_browser, time_zone):
|
||||
_time_zone_set(session_browser, time_zone)
|
||||
|
||||
|
||||
@then(parsers.parse('the time zone should be {time_zone:S}'))
|
||||
def time_zone_assert(session_browser, time_zone):
|
||||
assert time_zone == _time_zone_get(session_browser)
|
||||
|
||||
|
||||
def _time_zone_set(browser, time_zone):
|
||||
"""Set the system time zone."""
|
||||
functional.nav_to_module(browser, 'datetime')
|
||||
browser.select('time_zone', time_zone)
|
||||
functional.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def _time_zone_get(browser):
|
||||
"""Set the system time zone."""
|
||||
functional.nav_to_module(browser, 'datetime')
|
||||
return browser.find_by_name('time_zone').first.value
|
||||
|
||||
@ -3,6 +3,172 @@
|
||||
Functional, browser based tests for deluge app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
import os
|
||||
import time
|
||||
|
||||
from pytest_bdd import parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('deluge.feature')
|
||||
|
||||
|
||||
@when('all torrents are removed from deluge')
|
||||
def deluge_remove_all_torrents(session_browser):
|
||||
_remove_all_torrents(session_browser)
|
||||
|
||||
|
||||
@when('I upload a sample torrent to deluge')
|
||||
def deluge_upload_sample_torrent(session_browser):
|
||||
_upload_sample_torrent(session_browser)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'there should be {torrents_number:d} torrents listed in deluge'))
|
||||
def deluge_assert_number_of_torrents(session_browser, torrents_number):
|
||||
assert torrents_number == _get_number_of_torrents(session_browser)
|
||||
|
||||
|
||||
def _get_active_window_title(browser):
|
||||
"""Return the title of the currently active window in Deluge."""
|
||||
return browser.evaluate_script(
|
||||
'Ext.WindowMgr.getActive() ? Ext.WindowMgr.getActive().title : null')
|
||||
|
||||
|
||||
def _ensure_logged_in(browser):
|
||||
"""Ensure that password dialog is answered and we can interact."""
|
||||
url = functional.base_url + '/deluge'
|
||||
|
||||
def service_is_available():
|
||||
if browser.is_element_present_by_xpath(
|
||||
'//h1[text()="Service Unavailable"]'):
|
||||
functional.access_url(browser, 'deluge')
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
if browser.url != url:
|
||||
browser.visit(url)
|
||||
# After a backup restore, service may not be available immediately
|
||||
functional.eventually(service_is_available)
|
||||
|
||||
time.sleep(1) # Wait for Ext.js application in initialize
|
||||
|
||||
if _get_active_window_title(browser) != 'Login':
|
||||
return
|
||||
|
||||
browser.find_by_id('_password').first.fill('deluge')
|
||||
_click_active_window_button(browser, 'Login')
|
||||
|
||||
assert functional.eventually(
|
||||
lambda: _get_active_window_title(browser) != 'Login')
|
||||
functional.eventually(browser.is_element_not_present_by_css,
|
||||
args=['#add.x-item-disabled'], timeout=0.3)
|
||||
|
||||
|
||||
def _open_connection_manager(browser):
|
||||
"""Open the connection manager dialog if not already open."""
|
||||
title = 'Connection Manager'
|
||||
if _get_active_window_title(browser) == title:
|
||||
return
|
||||
|
||||
browser.find_by_css('button.x-deluge-connection-manager').first.click()
|
||||
functional.eventually(lambda: _get_active_window_title(browser) == title)
|
||||
|
||||
|
||||
def _ensure_connected(browser):
|
||||
"""Type the connection password if required and start Deluge daemon."""
|
||||
_ensure_logged_in(browser)
|
||||
|
||||
# Change Default Password window appears once.
|
||||
if _get_active_window_title(browser) == 'Change Default Password':
|
||||
_click_active_window_button(browser, 'No')
|
||||
|
||||
assert functional.eventually(browser.is_element_not_present_by_css,
|
||||
args=['#add.x-item-disabled'])
|
||||
|
||||
|
||||
def _remove_all_torrents(browser):
|
||||
"""Remove all torrents from deluge."""
|
||||
_ensure_connected(browser)
|
||||
|
||||
while browser.find_by_css('#torrentGrid .torrent-name'):
|
||||
browser.find_by_css('#torrentGrid .torrent-name').first.click()
|
||||
|
||||
# Click remove toolbar button
|
||||
browser.find_by_id('remove').first.click()
|
||||
|
||||
# Remove window shows up
|
||||
assert functional.eventually(
|
||||
lambda: _get_active_window_title(browser) == 'Remove Torrent')
|
||||
|
||||
_click_active_window_button(browser, 'Remove With Data')
|
||||
|
||||
# Remove window disappears
|
||||
assert functional.eventually(
|
||||
lambda: not _get_active_window_title(browser))
|
||||
|
||||
|
||||
def _get_active_window_id(browser):
|
||||
"""Return the ID of the currently active window."""
|
||||
return browser.evaluate_script('Ext.WindowMgr.getActive().id')
|
||||
|
||||
|
||||
def _click_active_window_button(browser, button_text):
|
||||
"""Click an action button in the active window."""
|
||||
browser.execute_script('''
|
||||
active_window = Ext.WindowMgr.getActive();
|
||||
active_window.buttons.forEach(function (button) {{
|
||||
if (button.text == "{button_text}")
|
||||
button.btnEl.dom.click()
|
||||
}})'''.format(button_text=button_text))
|
||||
|
||||
|
||||
def _upload_sample_torrent(browser):
|
||||
"""Upload a sample torrent into deluge."""
|
||||
_ensure_connected(browser)
|
||||
|
||||
number_of_torrents = _get_number_of_torrents(browser)
|
||||
|
||||
# Click add toolbar button
|
||||
browser.find_by_id('add').first.click()
|
||||
|
||||
# Add window appears
|
||||
functional.eventually(
|
||||
lambda: _get_active_window_title(browser) == 'Add Torrents')
|
||||
|
||||
file_path = os.path.join(os.path.dirname(__file__), 'data',
|
||||
'sample.torrent')
|
||||
|
||||
if browser.find_by_id('fileUploadForm'): # deluge-web 2.x
|
||||
browser.attach_file('file', file_path)
|
||||
else: # deluge-web 1.x
|
||||
browser.find_by_css('button.x-deluge-add-file').first.click()
|
||||
|
||||
# Add from file window appears
|
||||
functional.eventually(
|
||||
lambda: _get_active_window_title(browser) == 'Add from File')
|
||||
|
||||
# Attach file
|
||||
browser.attach_file('file', file_path)
|
||||
|
||||
# Click Add
|
||||
_click_active_window_button(browser, 'Add')
|
||||
|
||||
functional.eventually(
|
||||
lambda: _get_active_window_title(browser) == 'Add Torrents')
|
||||
|
||||
# Click Add
|
||||
time.sleep(1)
|
||||
_click_active_window_button(browser, 'Add')
|
||||
|
||||
functional.eventually(
|
||||
lambda: _get_number_of_torrents(browser) > number_of_torrents)
|
||||
|
||||
|
||||
def _get_number_of_torrents(browser):
|
||||
"""Return the number torrents currently in deluge."""
|
||||
_ensure_connected(browser)
|
||||
|
||||
return len(browser.find_by_css('#torrentGrid .torrent-name'))
|
||||
|
||||
@ -3,6 +3,71 @@
|
||||
Functional, browser based tests for dynamicdns app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('dynamicdns.feature')
|
||||
|
||||
|
||||
@given('dynamicdns is configured')
|
||||
def dynamicdns_configure(session_browser):
|
||||
_configure(session_browser)
|
||||
|
||||
|
||||
@when('I change the dynamicdns configuration')
|
||||
def dynamicdns_change_config(session_browser):
|
||||
_change_config(session_browser)
|
||||
|
||||
|
||||
@then('dynamicdns should have the original configuration')
|
||||
def dynamicdns_has_original_config(session_browser):
|
||||
assert _has_original_config(session_browser)
|
||||
|
||||
|
||||
def _configure(browser):
|
||||
functional.nav_to_module(browser, 'dynamicdns')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/sys/dynamicdns/configure/').first.click()
|
||||
browser.find_by_id('id_enabled').check()
|
||||
browser.find_by_id('id_service_type').select('GnuDIP')
|
||||
browser.find_by_id('id_dynamicdns_server').fill('example.com')
|
||||
browser.find_by_id('id_dynamicdns_domain').fill('freedombox.example.com')
|
||||
browser.find_by_id('id_dynamicdns_user').fill('tester')
|
||||
browser.find_by_id('id_dynamicdns_secret').fill('testingtesting')
|
||||
browser.find_by_id('id_dynamicdns_ipurl').fill(
|
||||
'http://myip.datasystems24.de')
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _has_original_config(browser):
|
||||
functional.nav_to_module(browser, 'dynamicdns')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/sys/dynamicdns/configure/').first.click()
|
||||
enabled = browser.find_by_id('id_enabled').value
|
||||
service_type = browser.find_by_id('id_service_type').value
|
||||
server = browser.find_by_id('id_dynamicdns_server').value
|
||||
domain = browser.find_by_id('id_dynamicdns_domain').value
|
||||
user = browser.find_by_id('id_dynamicdns_user').value
|
||||
ipurl = browser.find_by_id('id_dynamicdns_ipurl').value
|
||||
if enabled and service_type == 'GnuDIP' and server == 'example.com' \
|
||||
and domain == 'freedombox.example.com' and user == 'tester' \
|
||||
and ipurl == 'http://myip.datasystems24.de':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def _change_config(browser):
|
||||
functional.nav_to_module(browser, 'dynamicdns')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/sys/dynamicdns/configure/').first.click()
|
||||
browser.find_by_id('id_enabled').check()
|
||||
browser.find_by_id('id_service_type').select('GnuDIP')
|
||||
browser.find_by_id('id_dynamicdns_server').fill('2.example.com')
|
||||
browser.find_by_id('id_dynamicdns_domain').fill('freedombox2.example.com')
|
||||
browser.find_by_id('id_dynamicdns_user').fill('tester2')
|
||||
browser.find_by_id('id_dynamicdns_secret').fill('testingtesting2')
|
||||
browser.find_by_id('id_dynamicdns_ipurl').fill(
|
||||
'http://myip2.datasystems24.de')
|
||||
functional.submit(browser)
|
||||
|
||||
@ -3,6 +3,90 @@
|
||||
Functional, browser based tests for ejabberd app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('ejabberd.feature')
|
||||
|
||||
|
||||
@given('I have added a contact to my roster')
|
||||
def ejabberd_add_contact(session_browser):
|
||||
_jsxc_add_contact(session_browser)
|
||||
|
||||
|
||||
@when('I delete the contact from my roster')
|
||||
def ejabberd_delete_contact(session_browser):
|
||||
_jsxc_delete_contact(session_browser)
|
||||
|
||||
|
||||
@then('I should have a contact on my roster')
|
||||
def ejabberd_should_have_contact(session_browser):
|
||||
assert functional.eventually(_jsxc_has_contact, [session_browser])
|
||||
|
||||
|
||||
@when(parsers.parse('I enable message archive management'))
|
||||
def ejabberd_enable_archive_management(session_browser):
|
||||
_enable_message_archive_management(session_browser)
|
||||
|
||||
|
||||
@when(parsers.parse('I disable message archive management'))
|
||||
def ejabberd_disable_archive_management(session_browser):
|
||||
_disable_message_archive_management(session_browser)
|
||||
|
||||
|
||||
def _enable_message_archive_management(browser):
|
||||
"""Enable Message Archive Management in Ejabberd."""
|
||||
functional.nav_to_module(browser, 'ejabberd')
|
||||
functional.change_checkbox_status(browser, 'ejabberd', 'id_MAM_enabled',
|
||||
'enabled')
|
||||
|
||||
|
||||
def _disable_message_archive_management(browser):
|
||||
"""Enable Message Archive Management in Ejabberd."""
|
||||
functional.nav_to_module(browser, 'ejabberd')
|
||||
functional.change_checkbox_status(browser, 'ejabberd', 'id_MAM_enabled',
|
||||
'disabled')
|
||||
|
||||
|
||||
def _jsxc_login(browser):
|
||||
"""Login to JSXC."""
|
||||
username = functional.config['DEFAULT']['username']
|
||||
password = functional.config['DEFAULT']['password']
|
||||
functional.access_url(browser, 'jsxc')
|
||||
browser.find_by_id('jsxc-username').fill(username)
|
||||
browser.find_by_id('jsxc-password').fill(password)
|
||||
browser.find_by_id('jsxc-submit').click()
|
||||
relogin = browser.find_by_text('relogin')
|
||||
if relogin:
|
||||
relogin.first.click()
|
||||
browser.find_by_id('jsxc_username').fill(username)
|
||||
browser.find_by_id('jsxc_password').fill(password)
|
||||
browser.find_by_text('Connect').first.click()
|
||||
|
||||
|
||||
def _jsxc_add_contact(browser):
|
||||
"""Add a contact to JSXC user's roster."""
|
||||
functional.set_domain_name(browser, 'localhost')
|
||||
functional.install(browser, 'jsxc')
|
||||
_jsxc_login(browser)
|
||||
new = browser.find_by_text('new contact')
|
||||
if new: # roster is empty
|
||||
new.first.click()
|
||||
browser.find_by_id('jsxc_username').fill('alice@localhost')
|
||||
browser.find_by_text('Add').first.click()
|
||||
|
||||
|
||||
def _jsxc_delete_contact(browser):
|
||||
"""Delete the contact from JSXC user's roster."""
|
||||
_jsxc_login(browser)
|
||||
browser.find_by_css('div.jsxc_more').first.click()
|
||||
browser.find_by_text('delete contact').first.click()
|
||||
browser.find_by_text('Remove').first.click()
|
||||
|
||||
|
||||
def _jsxc_has_contact(browser):
|
||||
"""Check whether the contact is in JSXC user's roster."""
|
||||
_jsxc_login(browser)
|
||||
contact = browser.find_by_text('alice@localhost')
|
||||
return bool(contact)
|
||||
|
||||
@ -3,6 +3,311 @@
|
||||
Functional, browser based tests for gitweb app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
import contextlib
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('gitweb.feature')
|
||||
|
||||
_default_url = functional.config['DEFAULT']['url']
|
||||
|
||||
|
||||
@given('a public repository')
|
||||
@given('a repository')
|
||||
@given('at least one repository exists')
|
||||
def gitweb_repo(session_browser):
|
||||
_create_repo(session_browser, 'Test-repo', 'public', True)
|
||||
|
||||
|
||||
@given('a private repository')
|
||||
def gitweb_private_repo(session_browser):
|
||||
_create_repo(session_browser, 'Test-repo', 'private', True)
|
||||
|
||||
|
||||
@given('both public and private repositories exist')
|
||||
def gitweb_public_and_private_repo(session_browser):
|
||||
_create_repo(session_browser, 'Test-repo', 'public', True)
|
||||
_create_repo(session_browser, 'Test-repo2', 'private', True)
|
||||
|
||||
|
||||
@given(parsers.parse("a {access:w} repository that doesn't exist"))
|
||||
def gitweb_nonexistent_repo(session_browser, access):
|
||||
_delete_repo(session_browser, 'Test-repo', ignore_missing=True)
|
||||
return dict(access=access)
|
||||
|
||||
|
||||
@given('all repositories are private')
|
||||
def gitweb_all_repositories_private(session_browser):
|
||||
_set_all_repos_private(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse('a repository metadata:\n{metadata}'))
|
||||
def gitweb_repo_metadata(session_browser, metadata):
|
||||
metadata_dict = {}
|
||||
for item in metadata.split('\n'):
|
||||
item = item.split(': ')
|
||||
metadata_dict[item[0]] = item[1]
|
||||
return metadata_dict
|
||||
|
||||
|
||||
@when('I create the repository')
|
||||
def gitweb_create_repo(session_browser, access):
|
||||
_create_repo(session_browser, 'Test-repo', access)
|
||||
|
||||
|
||||
@when('I delete the repository')
|
||||
def gitweb_delete_repo(session_browser):
|
||||
_delete_repo(session_browser, 'Test-repo')
|
||||
|
||||
|
||||
@when('I set the metadata of the repository')
|
||||
def gitweb_edit_repo_metadata(session_browser, gitweb_repo_metadata):
|
||||
_edit_repo_metadata(session_browser, 'Test-repo', gitweb_repo_metadata)
|
||||
|
||||
|
||||
@when('using a git client')
|
||||
def gitweb_using_git_client():
|
||||
pass
|
||||
|
||||
|
||||
@then('the repository should be restored')
|
||||
@then('the repository should be listed as a public')
|
||||
def gitweb_repo_should_exists(session_browser):
|
||||
assert _repo_exists(session_browser, 'Test-repo', access='public')
|
||||
|
||||
|
||||
@then('the repository should be listed as a private')
|
||||
def gitweb_private_repo_should_exists(session_browser):
|
||||
assert _repo_exists(session_browser, 'Test-repo', 'private')
|
||||
|
||||
|
||||
@then('the repository should not be listed')
|
||||
def gitweb_repo_should_not_exist(session_browser, gitweb_repo):
|
||||
assert not _repo_exists(session_browser, gitweb_repo)
|
||||
|
||||
|
||||
@then('the public repository should be listed on gitweb')
|
||||
@then('the repository should be listed on gitweb')
|
||||
def gitweb_repo_should_exist_on_gitweb(session_browser):
|
||||
assert _site_repo_exists(session_browser, 'Test-repo')
|
||||
|
||||
|
||||
@then('the private repository should not be listed on gitweb')
|
||||
def gitweb_private_repo_should_exists_on_gitweb(session_browser):
|
||||
assert not _site_repo_exists(session_browser, 'Test-repo2')
|
||||
|
||||
|
||||
@then('the metadata of the repository should be as set')
|
||||
def gitweb_repo_metadata_should_match(session_browser, gitweb_repo_metadata):
|
||||
actual_metadata = _get_repo_metadata(session_browser, 'Test-repo')
|
||||
assert all(item in actual_metadata.items()
|
||||
for item in gitweb_repo_metadata.items())
|
||||
|
||||
|
||||
@then('the repository should be publicly readable')
|
||||
def gitweb_repo_publicly_readable():
|
||||
assert _repo_is_readable('Test-repo')
|
||||
assert _repo_is_readable('Test-repo', url_git_extension=True)
|
||||
|
||||
|
||||
@then('the repository should not be publicly readable')
|
||||
def gitweb_repo_not_publicly_readable():
|
||||
assert not _repo_is_readable('Test-repo')
|
||||
assert not _repo_is_readable('Test-repo', url_git_extension=True)
|
||||
|
||||
|
||||
@then('the repository should not be publicly writable')
|
||||
def gitweb_repo_not_publicly_writable():
|
||||
assert not _repo_is_writable('Test-repo')
|
||||
assert not _repo_is_writable('Test-repo', url_git_extension=True)
|
||||
|
||||
|
||||
@then('the repository should be privately readable')
|
||||
def gitweb_repo_privately_readable():
|
||||
assert _repo_is_readable('Test-repo', with_auth=True)
|
||||
assert _repo_is_readable('Test-repo', with_auth=True,
|
||||
url_git_extension=True)
|
||||
|
||||
|
||||
@then('the repository should be privately writable')
|
||||
def gitweb_repo_privately_writable():
|
||||
assert _repo_is_writable('Test-repo', with_auth=True)
|
||||
assert _repo_is_writable('Test-repo', with_auth=True,
|
||||
url_git_extension=True)
|
||||
|
||||
|
||||
def _create_repo(browser, repo, access=None, ok_if_exists=False):
|
||||
"""Create repository."""
|
||||
if not _repo_exists(browser, repo, access):
|
||||
_delete_repo(browser, repo, ignore_missing=True)
|
||||
browser.find_link_by_href('/plinth/apps/gitweb/create/').first.click()
|
||||
browser.find_by_id('id_gitweb-name').fill(repo)
|
||||
if access == 'private':
|
||||
browser.find_by_id('id_gitweb-is_private').check()
|
||||
elif access == 'public':
|
||||
browser.find_by_id('id_gitweb-is_private').uncheck()
|
||||
functional.submit(browser)
|
||||
elif not ok_if_exists:
|
||||
assert False, 'Repo already exists.'
|
||||
|
||||
|
||||
def _delete_repo(browser, repo, ignore_missing=False):
|
||||
"""Delete repository."""
|
||||
functional.nav_to_module(browser, 'gitweb')
|
||||
delete_link = browser.find_link_by_href(
|
||||
'/plinth/apps/gitweb/{}/delete/'.format(repo))
|
||||
if delete_link or not ignore_missing:
|
||||
delete_link.first.click()
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _edit_repo_metadata(browser, repo, metadata):
|
||||
"""Set repository metadata."""
|
||||
functional.nav_to_module(browser, 'gitweb')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/apps/gitweb/{}/edit/'.format(repo)).first.click()
|
||||
if 'name' in metadata:
|
||||
browser.find_by_id('id_gitweb-name').fill(metadata['name'])
|
||||
if 'description' in metadata:
|
||||
browser.find_by_id('id_gitweb-description').fill(
|
||||
metadata['description'])
|
||||
if 'owner' in metadata:
|
||||
browser.find_by_id('id_gitweb-owner').fill(metadata['owner'])
|
||||
if 'access' in metadata:
|
||||
if metadata['access'] == 'private':
|
||||
browser.find_by_id('id_gitweb-is_private').check()
|
||||
else:
|
||||
browser.find_by_id('id_gitweb-is_private').uncheck()
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _get_repo_metadata(browser, repo):
|
||||
"""Get repository metadata."""
|
||||
functional.nav_to_module(browser, 'gitweb')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/apps/gitweb/{}/edit/'.format(repo)).first.click()
|
||||
metadata = {}
|
||||
for item in ['name', 'description', 'owner']:
|
||||
metadata[item] = browser.find_by_id('id_gitweb-' + item).value
|
||||
if browser.find_by_id('id_gitweb-is_private').value:
|
||||
metadata['access'] = 'private'
|
||||
else:
|
||||
metadata['access'] = 'public'
|
||||
return metadata
|
||||
|
||||
|
||||
def _get_repo_url(repo, with_auth):
|
||||
""""Get repository URL"""
|
||||
scheme = 'http'
|
||||
if _default_url.startswith('https://'):
|
||||
scheme = 'https'
|
||||
url = _default_url.split(
|
||||
'://')[1] if '://' in _default_url else _default_url
|
||||
password = 'gitweb_wrong_password'
|
||||
if with_auth:
|
||||
password = functional.config['DEFAULT']['password']
|
||||
|
||||
return '{0}://{1}:{2}@{3}/gitweb/{4}'.format(
|
||||
scheme, functional.config['DEFAULT']['username'], password, url, repo)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _gitweb_temp_directory():
|
||||
"""Create temporary directory"""
|
||||
name = tempfile.mkdtemp(prefix='plinth_test_gitweb_')
|
||||
yield name
|
||||
shutil.rmtree(name)
|
||||
|
||||
|
||||
def _gitweb_git_command_is_successful(command, cwd):
|
||||
"""Check if a command runs successfully or gives authentication error"""
|
||||
process = subprocess.run(command, capture_output=True, cwd=cwd)
|
||||
if process.returncode != 0:
|
||||
if 'Authentication failed' in process.stderr.decode():
|
||||
return False
|
||||
print(process.stdout.decode())
|
||||
# raise exception
|
||||
process.check_returncode()
|
||||
return True
|
||||
|
||||
|
||||
def _repo_exists(browser, repo, access=None):
|
||||
"""Check whether the repository exists."""
|
||||
functional.nav_to_module(browser, 'gitweb')
|
||||
links_found = browser.find_link_by_href('/gitweb/{}.git'.format(repo))
|
||||
access_matches = True
|
||||
if links_found and access:
|
||||
parent = links_found.first.find_by_xpath('..').first
|
||||
private_icon = parent.find_by_css('.repo-private-icon')
|
||||
if access == 'private':
|
||||
access_matches = bool(private_icon)
|
||||
if access == 'public':
|
||||
access_matches = not bool(private_icon)
|
||||
return bool(links_found) and access_matches
|
||||
|
||||
|
||||
def _repo_is_readable(repo, with_auth=False, url_git_extension=False):
|
||||
"""Check if a git repo is readable with git client."""
|
||||
url = _get_repo_url(repo, with_auth)
|
||||
if url_git_extension:
|
||||
url = url + '.git'
|
||||
git_command = ['git', 'clone', '-c', 'http.sslverify=false', url]
|
||||
with _gitweb_temp_directory() as cwd:
|
||||
return _gitweb_git_command_is_successful(git_command, cwd)
|
||||
|
||||
|
||||
def _repo_is_writable(repo, with_auth=False, url_git_extension=False):
|
||||
"""Check if a git repo is writable with git client."""
|
||||
url = _get_repo_url(repo, with_auth)
|
||||
if url_git_extension:
|
||||
url = url + '.git'
|
||||
|
||||
with _gitweb_temp_directory() as cwd:
|
||||
subprocess.run(['mkdir', 'test-project'], check=True, cwd=cwd)
|
||||
cwd = os.path.join(cwd, 'test-project')
|
||||
prepare_git_repo_commands = [
|
||||
'git init -q', 'git config http.sslVerify false',
|
||||
'git -c "user.name=Tester" -c "user.email=tester" '
|
||||
'commit -q --allow-empty -m "test"'
|
||||
]
|
||||
for command in prepare_git_repo_commands:
|
||||
subprocess.run(command, shell=True, check=True, cwd=cwd)
|
||||
git_push_command = ['git', 'push', '-qf', url, 'master']
|
||||
|
||||
return _gitweb_git_command_is_successful(git_push_command, cwd)
|
||||
|
||||
|
||||
def _set_repo_access(browser, repo, access):
|
||||
"""Set repository as public or private."""
|
||||
functional.nav_to_module(browser, 'gitweb')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/apps/gitweb/{}/edit/'.format(repo)).first.click()
|
||||
if access == 'private':
|
||||
browser.find_by_id('id_gitweb-is_private').check()
|
||||
else:
|
||||
browser.find_by_id('id_gitweb-is_private').uncheck()
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _set_all_repos_private(browser):
|
||||
"""Set all repositories private"""
|
||||
functional.nav_to_module(browser, 'gitweb')
|
||||
public_repos = []
|
||||
for element in browser.find_by_css('#gitweb-repo-list .list-group-item'):
|
||||
if not element.find_by_css('.repo-private-icon'):
|
||||
repo = element.find_by_css('.repo-label').first.text
|
||||
public_repos.append(repo)
|
||||
for repo in public_repos:
|
||||
_set_repo_access(browser, repo, 'private')
|
||||
|
||||
|
||||
def _site_repo_exists(browser, repo):
|
||||
"""Check whether the repository exists on Gitweb site."""
|
||||
browser.visit('{}/gitweb'.format(_default_url))
|
||||
return browser.find_by_css('a[href="/gitweb/{0}.git"]'.format(repo))
|
||||
|
||||
@ -3,6 +3,26 @@
|
||||
Functional, browser based tests for help app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('help.feature')
|
||||
|
||||
|
||||
@when('I go to the status logs page')
|
||||
def help_go_to_status_logs(session_browser):
|
||||
_go_to_status_logs(session_browser)
|
||||
|
||||
|
||||
@then('status logs should be shown')
|
||||
def help_status_logs_are_shown(session_browser):
|
||||
assert _are_status_logs_shown(session_browser)
|
||||
|
||||
|
||||
def _go_to_status_logs(browser):
|
||||
functional.visit(browser, '/plinth/help/status-log/')
|
||||
|
||||
|
||||
def _are_status_logs_shown(browser):
|
||||
return browser.is_text_present('Logs begin')
|
||||
|
||||
@ -6,23 +6,23 @@ Feature: ikiwiki Wiki and Blog
|
||||
|
||||
Background:
|
||||
Given I'm a logged in user
|
||||
Given the wiki application is installed
|
||||
Given the ikiwiki application is installed
|
||||
|
||||
Scenario: Enable wiki application
|
||||
Given the wiki application is disabled
|
||||
When I enable the wiki application
|
||||
Then the wiki site should be available
|
||||
Scenario: Enable ikiwiki application
|
||||
Given the ikiwiki application is disabled
|
||||
When I enable the ikiwiki application
|
||||
Then the ikiwiki site should be available
|
||||
|
||||
@backups
|
||||
Scenario: Backup and restore wiki
|
||||
Given the wiki application is enabled
|
||||
Scenario: Backup and restore ikiwiki
|
||||
Given the ikiwiki application is enabled
|
||||
When there is an ikiwiki wiki
|
||||
And I create a backup of the ikiwiki app data with name test_ikiwiki
|
||||
And I delete the ikiwiki wiki
|
||||
And I restore the ikiwiki app data backup with name test_ikiwiki
|
||||
Then the ikiwiki wiki should be restored
|
||||
|
||||
Scenario: Disable wiki application
|
||||
Given the wiki application is enabled
|
||||
When I disable the wiki application
|
||||
Then the wiki site should not be available
|
||||
Scenario: Disable ikiwiki application
|
||||
Given the ikiwiki application is enabled
|
||||
When I disable the ikiwiki application
|
||||
Then the ikiwiki site should not be available
|
||||
|
||||
@ -3,6 +3,52 @@
|
||||
Functional, browser based tests for ikiwiki app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('ikiwiki.feature')
|
||||
|
||||
|
||||
@when('there is an ikiwiki wiki')
|
||||
def ikiwiki_create_wiki_if_needed(session_browser):
|
||||
_create_wiki_if_needed(session_browser)
|
||||
|
||||
|
||||
@when('I delete the ikiwiki wiki')
|
||||
def ikiwiki_delete_wiki(session_browser):
|
||||
_delete_wiki(session_browser)
|
||||
|
||||
|
||||
@then('the ikiwiki wiki should be restored')
|
||||
def ikiwiki_should_exist(session_browser):
|
||||
assert _wiki_exists(session_browser)
|
||||
|
||||
|
||||
def _create_wiki_if_needed(browser):
|
||||
"""Create wiki if it does not exist."""
|
||||
functional.nav_to_module(browser, 'ikiwiki')
|
||||
wiki = browser.find_link_by_href('/ikiwiki/wiki')
|
||||
if not wiki:
|
||||
browser.find_link_by_href('/plinth/apps/ikiwiki/create/').first.click()
|
||||
browser.find_by_id('id_ikiwiki-name').fill('wiki')
|
||||
browser.find_by_id('id_ikiwiki-admin_name').fill(
|
||||
functional.config['DEFAULT']['username'])
|
||||
browser.find_by_id('id_ikiwiki-admin_password').fill(
|
||||
functional.config['DEFAULT']['password'])
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _delete_wiki(browser):
|
||||
"""Delete wiki."""
|
||||
functional.nav_to_module(browser, 'ikiwiki')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/apps/ikiwiki/wiki/delete/').first.click()
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _wiki_exists(browser):
|
||||
"""Check whether the wiki exists."""
|
||||
functional.nav_to_module(browser, 'ikiwiki')
|
||||
wiki = browser.find_link_by_href('/ikiwiki/wiki')
|
||||
return bool(wiki)
|
||||
|
||||
@ -3,6 +3,216 @@
|
||||
Functional, browser based tests for mediawiki app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
import pathlib
|
||||
|
||||
from pytest_bdd import parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('mediawiki.feature')
|
||||
|
||||
|
||||
@when(parsers.parse('I enable mediawiki public registrations'))
|
||||
def enable_mediawiki_public_registrations(session_browser):
|
||||
_enable_public_registrations(session_browser)
|
||||
|
||||
|
||||
@when(parsers.parse('I disable mediawiki public registrations'))
|
||||
def disable_mediawiki_public_registrations(session_browser):
|
||||
_disable_public_registrations(session_browser)
|
||||
|
||||
|
||||
@when(parsers.parse('I enable mediawiki private mode'))
|
||||
def enable_mediawiki_private_mode(session_browser):
|
||||
_enable_private_mode(session_browser)
|
||||
|
||||
|
||||
@when(parsers.parse('I disable mediawiki private mode'))
|
||||
def disable_mediawiki_private_mode(session_browser):
|
||||
_disable_private_mode(session_browser)
|
||||
|
||||
|
||||
@when(parsers.parse('I set the mediawiki admin password to {password}'))
|
||||
def set_mediawiki_admin_password(session_browser, password):
|
||||
_set_admin_password(session_browser, password)
|
||||
|
||||
|
||||
@then(parsers.parse('the mediawiki site should allow creating accounts'))
|
||||
def mediawiki_allows_creating_accounts(session_browser):
|
||||
_verify_create_account_link(session_browser)
|
||||
|
||||
|
||||
@then(parsers.parse('the mediawiki site should not allow creating accounts'))
|
||||
def mediawiki_does_not_allow_creating_accounts(session_browser):
|
||||
_verify_no_create_account_link(session_browser)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse('the mediawiki site should allow anonymous reads and writes')
|
||||
)
|
||||
def mediawiki_allows_anonymous_reads_edits(session_browser):
|
||||
_verify_anonymous_reads_edits_link(session_browser)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'the mediawiki site should not allow anonymous reads and writes'))
|
||||
def mediawiki_does_not_allow__account_creation_anonymous_reads_edits(
|
||||
session_browser):
|
||||
_verify_no_anonymous_reads_edits_link(session_browser)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'I should see the Upload File option in the side pane when logged in '
|
||||
'with credentials {username:w} and {password:w}'))
|
||||
def login_to_mediawiki_with_credentials(session_browser, username, password):
|
||||
_login_with_credentials(session_browser, username, password)
|
||||
|
||||
|
||||
@when('I delete the mediawiki main page')
|
||||
def mediawiki_delete_main_page(session_browser):
|
||||
_delete_main_page(session_browser)
|
||||
|
||||
|
||||
@then('the mediawiki main page should be restored')
|
||||
def mediawiki_verify_text(session_browser):
|
||||
assert _has_main_page(session_browser)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse(
|
||||
'I upload an image named {image:S} to mediawiki with credentials '
|
||||
'{username:w} and {password:w}'))
|
||||
def upload_image(session_browser, username, password, image):
|
||||
_upload_image(session_browser, username, password, image)
|
||||
|
||||
|
||||
@then(parsers.parse('there should be {image:S} image'))
|
||||
def uploaded_image_should_be_available(session_browser, image):
|
||||
uploaded_image = _get_uploaded_image(session_browser, image)
|
||||
assert image.lower() == uploaded_image.lower()
|
||||
|
||||
|
||||
def _enable_public_registrations(browser):
|
||||
"""Enable public registrations in MediaWiki."""
|
||||
functional.nav_to_module(browser, 'mediawiki')
|
||||
functional.change_checkbox_status(browser, 'mediawiki',
|
||||
'id_enable_public_registrations',
|
||||
'enabled')
|
||||
|
||||
|
||||
def _disable_public_registrations(browser):
|
||||
"""Enable public registrations in MediaWiki."""
|
||||
functional.nav_to_module(browser, 'mediawiki')
|
||||
functional.change_checkbox_status(browser, 'mediawiki',
|
||||
'id_enable_public_registrations',
|
||||
'disabled')
|
||||
|
||||
|
||||
def _enable_private_mode(browser):
|
||||
"""Enable public registrations in MediaWiki."""
|
||||
functional.nav_to_module(browser, 'mediawiki')
|
||||
functional.change_checkbox_status(browser, 'mediawiki',
|
||||
'id_enable_private_mode', 'enabled')
|
||||
|
||||
|
||||
def _disable_private_mode(browser):
|
||||
"""Enable public registrations in MediaWiki."""
|
||||
functional.nav_to_module(browser, 'mediawiki')
|
||||
functional.change_checkbox_status(browser, 'mediawiki',
|
||||
'id_enable_private_mode', 'disabled')
|
||||
|
||||
|
||||
def _set_admin_password(browser, password):
|
||||
"""Set a password for the MediaWiki user called admin."""
|
||||
functional.nav_to_module(browser, 'mediawiki')
|
||||
browser.find_by_id('id_password').fill(password)
|
||||
functional.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def _verify_create_account_link(browser):
|
||||
functional.visit(browser, '/mediawiki/index.php/Special:CreateAccount')
|
||||
assert functional.eventually(browser.is_element_present_by_id,
|
||||
args=['wpCreateaccount'])
|
||||
|
||||
|
||||
def _verify_no_create_account_link(browser):
|
||||
functional.visit(browser, '/mediawiki/index.php/Special:CreateAccount')
|
||||
assert functional.eventually(browser.is_element_not_present_by_id,
|
||||
args=['wpCreateaccount'])
|
||||
|
||||
|
||||
def _verify_anonymous_reads_edits_link(browser):
|
||||
functional.visit(browser, '/mediawiki')
|
||||
assert functional.eventually(browser.is_element_present_by_id,
|
||||
args=['ca-nstab-main'])
|
||||
|
||||
|
||||
def _verify_no_anonymous_reads_edits_link(browser):
|
||||
functional.visit(browser, '/mediawiki')
|
||||
assert functional.eventually(browser.is_element_not_present_by_id,
|
||||
args=['ca-nstab-main'])
|
||||
assert functional.eventually(browser.is_element_present_by_id,
|
||||
args=['ca-nstab-special'])
|
||||
|
||||
|
||||
def _login(browser, username, password):
|
||||
functional.visit(browser, '/mediawiki/index.php?title=Special:Login')
|
||||
browser.find_by_id('wpName1').fill(username)
|
||||
browser.find_by_id('wpPassword1').fill(password)
|
||||
with functional.wait_for_page_update(browser):
|
||||
browser.find_by_id('wpLoginAttempt').click()
|
||||
|
||||
|
||||
def _login_with_credentials(browser, username, password):
|
||||
_login(browser, username, password)
|
||||
# Had to put it in the same step because sessions don't
|
||||
# persist between steps
|
||||
assert functional.eventually(browser.is_element_present_by_id,
|
||||
args=['t-upload'])
|
||||
|
||||
|
||||
def _upload_image(browser, username, password, image):
|
||||
"""Upload an image to MediaWiki. Idempotent."""
|
||||
functional.visit(browser, '/mediawiki')
|
||||
_login(browser, username, password)
|
||||
|
||||
# Upload file
|
||||
functional.visit(browser, '/mediawiki/Special:Upload')
|
||||
file_path = pathlib.Path(__file__).parent
|
||||
file_path /= '../../../../static/themes/default/img/' + image
|
||||
browser.attach_file('wpUploadFile', str(file_path.resolve()))
|
||||
functional.submit(browser, element=browser.find_by_name('wpUpload')[0])
|
||||
|
||||
|
||||
def _get_number_of_uploaded_images(browser):
|
||||
functional.visit(browser, '/mediawiki/Special:ListFiles')
|
||||
return len(browser.find_by_css('.TablePager_col_img_timestamp'))
|
||||
|
||||
|
||||
def _get_uploaded_image(browser, image):
|
||||
functional.visit(browser, '/mediawiki/Special:ListFiles')
|
||||
elements = browser.find_link_by_partial_href(image)
|
||||
return elements[0].value
|
||||
|
||||
|
||||
def _delete_main_page(browser):
|
||||
"""Delete the mediawiki main page."""
|
||||
_login(browser, 'admin', 'whatever123')
|
||||
functional.visit(browser,
|
||||
'/mediawiki/index.php?title=Main_Page&action=delete')
|
||||
with functional.wait_for_page_update(browser):
|
||||
browser.find_by_id('wpConfirmB').first.click()
|
||||
|
||||
|
||||
def _has_main_page(browser):
|
||||
"""Check if mediawiki main page exists."""
|
||||
return functional.eventually(__has_main_page, [browser])
|
||||
|
||||
|
||||
def __has_main_page(browser):
|
||||
"""Check if mediawiki main page exists."""
|
||||
functional.visit(browser, '/mediawiki/Main_Page')
|
||||
content = browser.find_by_id('mw-content-text').first
|
||||
return 'This page has been deleted.' not in content.text
|
||||
|
||||
@ -3,6 +3,60 @@
|
||||
Functional, browser based tests for mldonkey app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('mldonkey.feature')
|
||||
|
||||
|
||||
@when('all ed2k files are removed from mldonkey')
|
||||
def mldonkey_remove_all_ed2k_files(session_browser):
|
||||
_remove_all_ed2k_files(session_browser)
|
||||
|
||||
|
||||
@when('I upload a sample ed2k file to mldonkey')
|
||||
def mldonkey_upload_sample_ed2k_file(session_browser):
|
||||
_upload_sample_ed2k_file(session_browser)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'there should be {ed2k_files_number:d} ed2k files listed in mldonkey'))
|
||||
def mldonkey_assert_number_of_ed2k_files(session_browser, ed2k_files_number):
|
||||
assert ed2k_files_number == _get_number_of_ed2k_files(session_browser)
|
||||
|
||||
|
||||
def _submit_command(browser, command):
|
||||
"""Submit a command to mldonkey."""
|
||||
with browser.get_iframe('commands') as commands_frame:
|
||||
commands_frame.find_by_css('.txt2').fill(command)
|
||||
commands_frame.find_by_css('.but2').click()
|
||||
|
||||
|
||||
def _remove_all_ed2k_files(browser):
|
||||
"""Remove all ed2k files from mldonkey."""
|
||||
functional.visit(browser, '/mldonkey/')
|
||||
_submit_command(browser, 'cancel all')
|
||||
_submit_command(browser, 'confirm yes')
|
||||
|
||||
|
||||
def _upload_sample_ed2k_file(browser):
|
||||
"""Upload a sample ed2k file into mldonkey."""
|
||||
functional.visit(browser, '/mldonkey/')
|
||||
dllink_command = 'dllink ed2k://|file|foo.bar|123|' \
|
||||
'0123456789ABCDEF0123456789ABCDEF|/'
|
||||
_submit_command(browser, dllink_command)
|
||||
|
||||
|
||||
def _get_number_of_ed2k_files(browser):
|
||||
"""Return the number of ed2k files currently in mldonkey."""
|
||||
functional.visit(browser, '/mldonkey/')
|
||||
|
||||
with browser.get_iframe('commands') as commands_frame:
|
||||
commands_frame.find_by_xpath(
|
||||
'//tr//td[contains(text(), "Transfers")]').click()
|
||||
|
||||
with browser.get_iframe('output') as output_frame:
|
||||
return len(output_frame.find_by_css('.dl-1')) + len(
|
||||
output_frame.find_by_css('.dl-2'))
|
||||
|
||||
@ -3,6 +3,79 @@
|
||||
Functional, browser based tests for monkeysphere app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('monkeysphere.feature')
|
||||
|
||||
|
||||
@given(
|
||||
parsers.parse(
|
||||
'the {key_type:w} key for {domain:S} is imported in monkeysphere'))
|
||||
def monkeysphere_given_import_key(session_browser, key_type, domain):
|
||||
_import_key(session_browser, key_type.lower(), domain)
|
||||
|
||||
|
||||
@when(parsers.parse('I import {key_type:w} key for {domain:S} in monkeysphere')
|
||||
)
|
||||
def monkeysphere_import_key(session_browser, key_type, domain):
|
||||
_import_key(session_browser, key_type.lower(), domain)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'the {key_type:w} key should imported for {domain:S} in monkeysphere'))
|
||||
def monkeysphere_assert_imported_key(session_browser, key_type, domain):
|
||||
_assert_imported_key(session_browser, key_type.lower(), domain)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse('I should be able to publish {key_type:w} key for '
|
||||
'{domain:S} in monkeysphere'))
|
||||
def monkeysphere_publish_key(session_browser, key_type, domain):
|
||||
_publish_key(session_browser, key_type.lower(), domain)
|
||||
|
||||
|
||||
def _find_domain(browser, key_type, domain_type, domain):
|
||||
"""Iterate every domain of a given type which given key type."""
|
||||
keys_of_type = browser.find_by_css(
|
||||
'.monkeysphere-service-{}'.format(key_type))
|
||||
for key_of_type in keys_of_type:
|
||||
search_domains = key_of_type.find_by_css(
|
||||
'.monkeysphere-{}-domain'.format(domain_type))
|
||||
for search_domain in search_domains:
|
||||
if search_domain.text == domain:
|
||||
return key_of_type, search_domain
|
||||
|
||||
raise IndexError('Domain not found')
|
||||
|
||||
|
||||
def _import_key(browser, key_type, domain):
|
||||
"""Import a key of specified type for given domain into monkeysphere."""
|
||||
try:
|
||||
monkeysphere_assert_imported_key(browser, key_type, domain)
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
|
||||
key, _ = _find_domain(browser, key_type, 'importable', domain)
|
||||
with functional.wait_for_page_update(browser):
|
||||
key.find_by_css('.button-import').click()
|
||||
|
||||
|
||||
def _assert_imported_key(browser, key_type, domain):
|
||||
"""Assert that a key of specified type for given domain was imported.."""
|
||||
functional.nav_to_module(browser, 'monkeysphere')
|
||||
return _find_domain(browser, key_type, 'imported', domain)
|
||||
|
||||
|
||||
def _publish_key(browser, key_type, domain):
|
||||
"""Publish a key of specified type for given domain from monkeysphere."""
|
||||
functional.nav_to_module(browser, 'monkeysphere')
|
||||
key, _ = _find_domain(browser, key_type, 'imported', domain)
|
||||
with functional.wait_for_page_update(browser):
|
||||
key.find_by_css('.button-publish').click()
|
||||
|
||||
functional.wait_for_config_update(browser, 'monkeysphere')
|
||||
|
||||
@ -3,6 +3,44 @@
|
||||
Functional, browser based tests for openvpn app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('openvpn.feature')
|
||||
|
||||
|
||||
@given(parsers.parse('the openvpn application is setup'))
|
||||
def openvpn_setup(session_browser):
|
||||
"""Setup the OpenVPN application after installation."""
|
||||
functional.nav_to_module(session_browser, 'openvpn')
|
||||
setup_form = session_browser.find_by_css('.form-setup')
|
||||
if not setup_form:
|
||||
return
|
||||
|
||||
functional.submit(session_browser, form_class='form-setup')
|
||||
functional.wait_for_config_update(session_browser, 'openvpn')
|
||||
|
||||
|
||||
@given('I download openvpn profile')
|
||||
def openvpn_download_profile(session_browser):
|
||||
return _download_profile(session_browser)
|
||||
|
||||
|
||||
@then('the openvpn profile should be downloadable')
|
||||
def openvpn_profile_downloadable(session_browser):
|
||||
_download_profile(session_browser)
|
||||
|
||||
|
||||
@then('the openvpn profile downloaded should be same as before')
|
||||
def openvpn_profile_download_compare(session_browser,
|
||||
openvpn_download_profile):
|
||||
new_profile = _download_profile(session_browser)
|
||||
assert openvpn_download_profile == new_profile
|
||||
|
||||
|
||||
def _download_profile(browser):
|
||||
"""Download the current user's profile into a file and return path."""
|
||||
functional.nav_to_module(browser, 'openvpn')
|
||||
url = browser.find_by_css('.form-profile')['action']
|
||||
return functional.download_file(browser, url)
|
||||
|
||||
@ -3,6 +3,45 @@
|
||||
Functional, browser based tests for pagekite app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('pagekite.feature')
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I configure pagekite with host {host:S}, port {port:d}, '
|
||||
'kite name {kite_name:S} and kite secret {kite_secret:w}'))
|
||||
def pagekite_configure(session_browser, host, port, kite_name, kite_secret):
|
||||
_configure(session_browser, host, port, kite_name, kite_secret)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'pagekite should be configured with host {host:S}, port {port:d}, '
|
||||
'kite name {kite_name:S} and kite secret {kite_secret:w}'))
|
||||
def pagekite_assert_configured(session_browser, host, port, kite_name,
|
||||
kite_secret):
|
||||
assert (host, port, kite_name,
|
||||
kite_secret) == _get_configuration(session_browser)
|
||||
|
||||
|
||||
def _configure(browser, host, port, kite_name, kite_secret):
|
||||
"""Configure pagekite basic parameters."""
|
||||
functional.nav_to_module(browser, 'pagekite')
|
||||
# time.sleep(0.250) # Wait for 200ms show animation to complete
|
||||
browser.fill('pagekite-server_domain', host)
|
||||
browser.fill('pagekite-server_port', str(port))
|
||||
browser.fill('pagekite-kite_name', kite_name)
|
||||
browser.fill('pagekite-kite_secret', kite_secret)
|
||||
functional.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def _get_configuration(browser):
|
||||
"""Return pagekite basic parameters."""
|
||||
functional.nav_to_module(browser, 'pagekite')
|
||||
return (browser.find_by_name('pagekite-server_domain').value,
|
||||
int(browser.find_by_name('pagekite-server_port').value),
|
||||
browser.find_by_name('pagekite-kite_name').value,
|
||||
browser.find_by_name('pagekite-kite_secret').value)
|
||||
|
||||
@ -3,6 +3,118 @@
|
||||
Functional, browser based tests for radicale app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from pytest_bdd import given, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
scenarios('radicale.feature')
|
||||
|
||||
|
||||
@given('the access rights are set to "only the owner can view or make changes"'
|
||||
)
|
||||
def radicale_given_owner_only(session_browser):
|
||||
_set_access_rights(session_browser, 'owner_only')
|
||||
|
||||
|
||||
@given('the access rights are set to "any user can view, but only the '
|
||||
'owner can make changes"')
|
||||
def radicale_given_owner_write(session_browser):
|
||||
_set_access_rights(session_browser, 'owner_write')
|
||||
|
||||
|
||||
@given('the access rights are set to "any user can view or make changes"')
|
||||
def radicale_given_authenticated(session_browser):
|
||||
_set_access_rights(session_browser, 'authenticated')
|
||||
|
||||
|
||||
@when('I change the access rights to "only the owner can view or make changes"'
|
||||
)
|
||||
def radicale_set_owner_only(session_browser):
|
||||
_set_access_rights(session_browser, 'owner_only')
|
||||
|
||||
|
||||
@when('I change the access rights to "any user can view, but only the '
|
||||
'owner can make changes"')
|
||||
def radicale_set_owner_write(session_browser):
|
||||
_set_access_rights(session_browser, 'owner_write')
|
||||
|
||||
|
||||
@when('I change the access rights to "any user can view or make changes"')
|
||||
def radicale_set_authenticated(session_browser):
|
||||
_set_access_rights(session_browser, 'authenticated')
|
||||
|
||||
|
||||
@then('the access rights should be "only the owner can view or make changes"')
|
||||
def radicale_check_owner_only(session_browser):
|
||||
assert _get_access_rights(session_browser) == 'owner_only'
|
||||
|
||||
|
||||
@then('the access rights should be "any user can view, but only the '
|
||||
'owner can make changes"')
|
||||
def radicale_check_owner_write(session_browser):
|
||||
assert _get_access_rights(session_browser) == 'owner_write'
|
||||
|
||||
|
||||
@then('the access rights should be "any user can view or make changes"')
|
||||
def radicale_check_authenticated(session_browser):
|
||||
assert _get_access_rights(session_browser) == 'authenticated'
|
||||
|
||||
|
||||
@then('the calendar should be available')
|
||||
def assert_calendar_is_available(session_browser):
|
||||
assert _calendar_is_available(session_browser)
|
||||
|
||||
|
||||
@then('the calendar should not be available')
|
||||
def assert_calendar_is_not_available(session_browser):
|
||||
assert not _calendar_is_available(session_browser)
|
||||
|
||||
|
||||
@then('the addressbook should be available')
|
||||
def assert_addressbook_is_available(session_browser):
|
||||
assert _addressbook_is_available(session_browser)
|
||||
|
||||
|
||||
@then('the addressbook should not be available')
|
||||
def assert_addressbook_is_not_available(session_browser):
|
||||
assert not _addressbook_is_available(session_browser)
|
||||
|
||||
|
||||
def _get_access_rights(browser):
|
||||
access_rights_types = ['owner_only', 'owner_write', 'authenticated']
|
||||
functional.nav_to_module(browser, 'radicale')
|
||||
for access_rights_type in access_rights_types:
|
||||
if browser.find_by_value(access_rights_type).checked:
|
||||
return access_rights_type
|
||||
|
||||
|
||||
def _set_access_rights(browser, access_rights_type):
|
||||
functional.nav_to_module(browser, 'radicale')
|
||||
browser.choose('access_rights', access_rights_type)
|
||||
functional.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def _calendar_is_available(browser):
|
||||
"""Return whether calendar is available at well-known URL."""
|
||||
conf = functional.config['DEFAULT']
|
||||
url = functional.base_url + '/.well-known/caldav'
|
||||
logging.captureWarnings(True)
|
||||
request = requests.get(url, auth=(conf['username'], conf['password']),
|
||||
verify=False)
|
||||
logging.captureWarnings(False)
|
||||
return request.status_code != 404
|
||||
|
||||
|
||||
def _addressbook_is_available(browser):
|
||||
"""Return whether addressbook is available at well-known URL."""
|
||||
conf = functional.config['DEFAULT']
|
||||
url = functional.base_url + '/.well-known/carddav'
|
||||
logging.captureWarnings(True)
|
||||
request = requests.get(url, auth=(conf['username'], conf['password']),
|
||||
verify=False)
|
||||
logging.captureWarnings(False)
|
||||
return request.status_code != 404
|
||||
|
||||
@ -3,6 +3,117 @@
|
||||
Functional, browser based tests for samba app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
import random
|
||||
import string
|
||||
import subprocess
|
||||
import urllib
|
||||
|
||||
from pytest_bdd import parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('samba.feature')
|
||||
|
||||
|
||||
@when(parsers.parse('I {task:w} the {share_type:w} samba share'))
|
||||
def samba_enable_share(session_browser, task, share_type):
|
||||
if task == 'enable':
|
||||
_set_share(session_browser, share_type, status='enabled')
|
||||
elif task == 'disable':
|
||||
_set_share(session_browser, share_type, status='disabled')
|
||||
|
||||
|
||||
@then(parsers.parse('I can write to the {share_type:w} samba share'))
|
||||
def samba_share_should_be_writable(share_type):
|
||||
_assert_share_is_writable(share_type)
|
||||
|
||||
|
||||
@then(parsers.parse('a guest user can write to the {share_type:w} samba share')
|
||||
)
|
||||
def samba_share_should_be_writable_to_guest(share_type):
|
||||
_assert_share_is_writable(share_type, as_guest=True)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse('a guest user can\'t access the {share_type:w} samba share'))
|
||||
def samba_share_should_not_be_accessible_to_guest(share_type):
|
||||
_assert_share_is_not_accessible(share_type, as_guest=True)
|
||||
|
||||
|
||||
@then(parsers.parse('the {share_type:w} samba share should not be available'))
|
||||
def samba_share_should_not_be_available(share_type):
|
||||
_assert_share_is_not_available(share_type)
|
||||
|
||||
|
||||
def _set_share(browser, share_type, status='enabled'):
|
||||
"""Enable or disable samba share."""
|
||||
disk_name = 'disk'
|
||||
share_type_name = '{0}_share'.format(share_type)
|
||||
functional.nav_to_module(browser, 'samba')
|
||||
for elem in browser.find_by_tag('td'):
|
||||
if elem.text == disk_name:
|
||||
share_form = elem.find_by_xpath('(..//*)[2]/form').first
|
||||
share_btn = share_form.find_by_name(share_type_name).first
|
||||
if status == 'enabled' and share_btn['value'] == 'enable':
|
||||
share_btn.click()
|
||||
elif status == 'disabled' and share_btn['value'] == 'disable':
|
||||
share_btn.click()
|
||||
break
|
||||
|
||||
|
||||
def _write_to_share(share_type, as_guest=False):
|
||||
"""Write to the samba share, return output messages as string."""
|
||||
disk_name = 'disk'
|
||||
default_url = functional.config['DEFAULT']['url']
|
||||
if share_type == 'open':
|
||||
share_name = disk_name
|
||||
else:
|
||||
share_name = '{0}_{1}'.format(disk_name, share_type)
|
||||
hostname = urllib.parse.urlparse(default_url).hostname
|
||||
servicename = '\\\\{0}\\{1}'.format(hostname, share_name)
|
||||
directory = '_plinth-test_{0}'.format(''.join(
|
||||
random.SystemRandom().choices(string.ascii_letters, k=8)))
|
||||
port = functional.config['DEFAULT']['samba_port']
|
||||
|
||||
smb_command = ['smbclient', '-W', 'WORKGROUP', '-p', port]
|
||||
if as_guest:
|
||||
smb_command += ['-N']
|
||||
else:
|
||||
smb_command += [
|
||||
'-U', '{0}%{1}'.format(functional.config['DEFAULT']['username'],
|
||||
functional.config['DEFAULT']['password'])
|
||||
]
|
||||
smb_command += [
|
||||
servicename, '-c', 'mkdir {0}; rmdir {0}'.format(directory)
|
||||
]
|
||||
|
||||
return subprocess.check_output(smb_command).decode()
|
||||
|
||||
|
||||
def _assert_share_is_writable(share_type, as_guest=False):
|
||||
"""Assert that samba share is writable."""
|
||||
output = _write_to_share(share_type, as_guest=False)
|
||||
|
||||
assert not output, output
|
||||
|
||||
|
||||
def _assert_share_is_not_accessible(share_type, as_guest=False):
|
||||
"""Assert that samba share is not accessible."""
|
||||
try:
|
||||
_write_to_share(share_type, as_guest)
|
||||
except subprocess.CalledProcessError as err:
|
||||
err_output = err.output.decode()
|
||||
assert 'NT_STATUS_ACCESS_DENIED' in err_output, err_output
|
||||
else:
|
||||
assert False, 'Can access the share.'
|
||||
|
||||
|
||||
def _assert_share_is_not_available(share_type):
|
||||
"""Assert that samba share is not accessible."""
|
||||
try:
|
||||
_write_to_share(share_type)
|
||||
except subprocess.CalledProcessError as err:
|
||||
err_output = err.output.decode()
|
||||
assert 'NT_STATUS_BAD_NETWORK_NAME' in err_output, err_output
|
||||
else:
|
||||
assert False, 'Can access the share.'
|
||||
|
||||
@ -3,6 +3,37 @@
|
||||
Functional, browser based tests for searx app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, scenarios, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('searx.feature')
|
||||
|
||||
|
||||
@given('public access is enabled in searx')
|
||||
def searx_public_access_enabled(session_browser):
|
||||
_enable_public_access(session_browser)
|
||||
|
||||
|
||||
@when('I enable public access in searx')
|
||||
def searx_enable_public_access(session_browser):
|
||||
_enable_public_access(session_browser)
|
||||
|
||||
|
||||
@when('I disable public access in searx')
|
||||
def searx_disable_public_access(session_browser):
|
||||
_disable_public_access(session_browser)
|
||||
|
||||
|
||||
def _enable_public_access(browser):
|
||||
"""Enable Public Access in SearX"""
|
||||
functional.nav_to_module(browser, 'searx')
|
||||
browser.find_by_id('id_public_access').check()
|
||||
functional.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def _disable_public_access(browser):
|
||||
"""Enable Public Access in SearX"""
|
||||
functional.nav_to_module(browser, 'searx')
|
||||
browser.find_by_id('id_public_access').uncheck()
|
||||
functional.submit(browser, form_class='form-configuration')
|
||||
|
||||
@ -3,6 +3,43 @@
|
||||
Functional, browser based tests for security app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('security.feature')
|
||||
|
||||
|
||||
@given(parsers.parse('restricted console logins are {enabled}'))
|
||||
def security_given_enable_restricted_logins(session_browser, enabled):
|
||||
should_enable = (enabled == 'enabled')
|
||||
_enable_restricted_logins(session_browser, should_enable)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable} restricted console logins'))
|
||||
def security_enable_restricted_logins(session_browser, enable):
|
||||
should_enable = (enable == 'enable')
|
||||
_enable_restricted_logins(session_browser, should_enable)
|
||||
|
||||
|
||||
@then(parsers.parse('restricted console logins should be {enabled}'))
|
||||
def security_assert_restricted_logins(session_browser, enabled):
|
||||
enabled = (enabled == 'enabled')
|
||||
assert _get_restricted_logins(session_browser) == enabled
|
||||
|
||||
|
||||
def _enable_restricted_logins(browser, should_enable):
|
||||
"""Enable/disable restricted logins in security module."""
|
||||
functional.nav_to_module(browser, 'security')
|
||||
if should_enable:
|
||||
browser.check('security-restricted_access')
|
||||
else:
|
||||
browser.uncheck('security-restricted_access')
|
||||
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _get_restricted_logins(browser):
|
||||
"""Return whether restricted console logins is enabled."""
|
||||
functional.nav_to_module(browser, 'security')
|
||||
return browser.find_by_name('security-restricted_access').first.checked
|
||||
|
||||
@ -3,6 +3,43 @@
|
||||
Functional, browser based tests for shadowsocks app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('shadowsocks.feature')
|
||||
|
||||
|
||||
@given('the shadowsocks application is configured')
|
||||
def configure_shadowsocks(session_browser):
|
||||
_configure(session_browser, 'example.com', 'fakepassword')
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I configure shadowsocks with server {server:S} and '
|
||||
'password {password:w}'))
|
||||
def configure_shadowsocks_with_details(session_browser, server, password):
|
||||
_configure(session_browser, server, password)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse('shadowsocks should be configured with server {server:S} '
|
||||
'and password {password:w}'))
|
||||
def assert_shadowsocks_configuration(session_browser, server, password):
|
||||
assert (server, password) == _get_configuration(session_browser)
|
||||
|
||||
|
||||
def _configure(browser, server, password):
|
||||
"""Configure shadowsocks client with given server details."""
|
||||
functional.visit(browser, '/plinth/apps/shadowsocks/')
|
||||
browser.find_by_id('id_server').fill(server)
|
||||
browser.find_by_id('id_password').fill(password)
|
||||
functional.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def _get_configuration(browser):
|
||||
"""Return the server and password currently configured in shadowsocks."""
|
||||
functional.visit(browser, '/plinth/apps/shadowsocks/')
|
||||
server = browser.find_by_id('id_server').value
|
||||
password = browser.find_by_id('id_password').value
|
||||
return server, password
|
||||
|
||||
@ -3,6 +3,145 @@
|
||||
Functional, browser based tests for sharing app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
import pytest
|
||||
import splinter
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('sharing.feature')
|
||||
|
||||
|
||||
@given(parsers.parse('share {name:w} is not available'))
|
||||
def remove_share(session_browser, name):
|
||||
_remove_share(session_browser, name)
|
||||
|
||||
|
||||
@when(parsers.parse('I add a share {name:w} from path {path} for {group:w}'))
|
||||
def add_share(session_browser, name, path, group):
|
||||
_add_share(session_browser, name, path, group)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I edit share {old_name:w} to {new_name:w} from path {path} '
|
||||
'for {group:w}'))
|
||||
def edit_share(session_browser, old_name, new_name, path, group):
|
||||
_edit_share(session_browser, old_name, new_name, path, group)
|
||||
|
||||
|
||||
@when(parsers.parse('I remove share {name:w}'))
|
||||
def remove_share2(session_browser, name):
|
||||
_remove_share(session_browser, name)
|
||||
|
||||
|
||||
@when(parsers.parse('I edit share {name:w} to be public'))
|
||||
def edit_share_public_access(session_browser, name):
|
||||
_make_share_public(session_browser, name)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'the share {name:w} should be listed from path {path} for {group:w}'))
|
||||
def verify_share(session_browser, name, path, group):
|
||||
_verify_share(session_browser, name, path, group)
|
||||
|
||||
|
||||
@then(parsers.parse('the share {name:w} should not be listed'))
|
||||
def verify_invalid_share(session_browser, name):
|
||||
with pytest.raises(splinter.exceptions.ElementDoesNotExist):
|
||||
_get_share(session_browser, name)
|
||||
|
||||
|
||||
@then(parsers.parse('the share {name:w} should be accessible'))
|
||||
def access_share(session_browser, name):
|
||||
_access_share(session_browser, name)
|
||||
|
||||
|
||||
@then(parsers.parse('the share {name:w} should not exist'))
|
||||
def verify_nonexistant_share(session_browser, name):
|
||||
_verify_nonexistant_share(session_browser, name)
|
||||
|
||||
|
||||
@then(parsers.parse('the share {name:w} should not be accessible'))
|
||||
def verify_inaccessible_share(session_browser, name):
|
||||
_verify_inaccessible_share(session_browser, name)
|
||||
|
||||
|
||||
def _remove_share(browser, name):
|
||||
"""Remove a share in sharing app."""
|
||||
try:
|
||||
share_row = _get_share(browser, name)
|
||||
except splinter.exceptions.ElementDoesNotExist:
|
||||
pass
|
||||
else:
|
||||
share_row.find_by_css('.share-remove')[0].click()
|
||||
|
||||
|
||||
def _add_share(browser, name, path, group):
|
||||
"""Add a share in sharing app."""
|
||||
functional.visit(browser, '/plinth/apps/sharing/add/')
|
||||
browser.fill('sharing-name', name)
|
||||
browser.fill('sharing-path', path)
|
||||
browser.find_by_css(
|
||||
'#id_sharing-groups input[value="{}"]'.format(group)).check()
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _edit_share(browser, old_name, new_name, path, group):
|
||||
"""Edit a share in sharing app."""
|
||||
row = _get_share(browser, old_name)
|
||||
with functional.wait_for_page_update(browser):
|
||||
row.find_by_css('.share-edit')[0].click()
|
||||
browser.fill('sharing-name', new_name)
|
||||
browser.fill('sharing-path', path)
|
||||
browser.find_by_css('#id_sharing-groups input').uncheck()
|
||||
browser.find_by_css(
|
||||
'#id_sharing-groups input[value="{}"]'.format(group)).check()
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _get_share(browser, name):
|
||||
"""Return the row for a given share."""
|
||||
functional.visit(browser, '/plinth/apps/sharing/')
|
||||
return browser.find_by_id('share-{}'.format(name))[0]
|
||||
|
||||
|
||||
def _verify_share(browser, name, path, group):
|
||||
"""Verfiy that a share exists in list of shares."""
|
||||
href = f'{functional.base_url}/share/{name}'
|
||||
url = f'/share/{name}'
|
||||
row = _get_share(browser, name)
|
||||
assert row.find_by_css('.share-name')[0].text == name
|
||||
assert row.find_by_css('.share-path')[0].text == path
|
||||
assert row.find_by_css('.share-url a')[0]['href'] == href
|
||||
assert row.find_by_css('.share-url a')[0].text == url
|
||||
assert row.find_by_css('.share-groups')[0].text == group
|
||||
|
||||
|
||||
def _access_share(browser, name):
|
||||
"""Visit a share and see if it is accessible."""
|
||||
row = _get_share(browser, name)
|
||||
url = row.find_by_css('.share-url a')[0]['href']
|
||||
browser.visit(url)
|
||||
assert '/share/{}'.format(name) in browser.title
|
||||
|
||||
|
||||
def _make_share_public(browser, name):
|
||||
"""Make share publicly accessible."""
|
||||
row = _get_share(browser, name)
|
||||
with functional.wait_for_page_update(browser):
|
||||
row.find_by_css('.share-edit')[0].click()
|
||||
browser.find_by_id('id_sharing-is_public').check()
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _verify_nonexistant_share(browser, name):
|
||||
"""Verify that given URL for a given share name is a 404."""
|
||||
functional.visit(browser, f'/share/{name}')
|
||||
assert '404' in browser.title
|
||||
|
||||
|
||||
def _verify_inaccessible_share(browser, name):
|
||||
"""Verify that given URL for a given share name denies permission."""
|
||||
functional.visit(browser, f'/share/{name}')
|
||||
functional.eventually(lambda: '/plinth' in browser.url, args=[])
|
||||
|
||||
@ -3,6 +3,125 @@
|
||||
Functional, browser based tests for snapshot app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('snapshot.feature')
|
||||
|
||||
|
||||
@given('the list of snapshots is empty')
|
||||
def empty_snapshots_list(session_browser):
|
||||
_delete_all(session_browser)
|
||||
|
||||
|
||||
@when('I manually create a snapshot')
|
||||
def create_snapshot(session_browser):
|
||||
_create(session_browser)
|
||||
|
||||
|
||||
@then(parsers.parse('there should be {count:d} snapshot in the list'))
|
||||
def verify_snapshot_count(session_browser, count):
|
||||
num_snapshots = _get_count(session_browser)
|
||||
assert num_snapshots == count
|
||||
|
||||
|
||||
@given(
|
||||
parsers.parse(
|
||||
'snapshots are configured with free space {free_space:d}, timeline '
|
||||
'snapshots {timeline_enabled:w}, software snapshots '
|
||||
'{software_enabled:w}, hourly limit {hourly:d}, daily limit {daily:d}'
|
||||
', weekly limit {weekly:d}, monthly limit {monthly:d}, yearly limit '
|
||||
'{yearly:d}'))
|
||||
def snapshot_given_set_configuration(session_browser, free_space,
|
||||
timeline_enabled, software_enabled,
|
||||
hourly, daily, weekly, monthly, yearly):
|
||||
timeline_enabled = (timeline_enabled == 'enabled')
|
||||
software_enabled = (software_enabled == 'enabled')
|
||||
_set_configuration(session_browser, free_space, timeline_enabled,
|
||||
software_enabled, hourly, daily, weekly, monthly,
|
||||
yearly)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse(
|
||||
'I configure snapshots with free space {free_space:d}, '
|
||||
'timeline snapshots {timeline_enabled:w}, '
|
||||
'software snapshots {software_enabled:w}, hourly limit {hourly:d}, '
|
||||
'daily limit {daily:d}, weekly limit {weekly:d}, monthly limit '
|
||||
'{monthly:d}, yearly limit {yearly:d}'))
|
||||
def snapshot_set_configuration(session_browser, free_space, timeline_enabled,
|
||||
software_enabled, hourly, daily, weekly,
|
||||
monthly, yearly):
|
||||
timeline_enabled = (timeline_enabled == 'enabled')
|
||||
software_enabled = (software_enabled == 'enabled')
|
||||
_set_configuration(session_browser, free_space, timeline_enabled,
|
||||
software_enabled, hourly, daily, weekly, monthly,
|
||||
yearly)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'snapshots should be configured with free space {free_space:d}, '
|
||||
'timeline snapshots {timeline_enabled:w}, software snapshots '
|
||||
'{software_enabled:w}, hourly limit {hourly:d}, daily limit '
|
||||
'{daily:d}, weekly limit {weekly:d}, monthly limit {monthly:d}, '
|
||||
'yearly limit {yearly:d}'))
|
||||
def snapshot_assert_configuration(session_browser, free_space,
|
||||
timeline_enabled, software_enabled, hourly,
|
||||
daily, weekly, monthly, yearly):
|
||||
timeline_enabled = (timeline_enabled == 'enabled')
|
||||
software_enabled = (software_enabled == 'enabled')
|
||||
assert (free_space, timeline_enabled, software_enabled, hourly, daily,
|
||||
weekly, monthly, yearly) == _get_configuration(session_browser)
|
||||
|
||||
|
||||
def _delete_all(browser):
|
||||
if _get_count(browser):
|
||||
browser.find_by_id('select-all').check()
|
||||
functional.submit(browser, browser.find_by_name('delete_selected'))
|
||||
|
||||
confirm_button = browser.find_by_name('delete_confirm')
|
||||
if confirm_button: # Only if redirected to confirm page
|
||||
functional.submit(browser, confirm_button)
|
||||
|
||||
|
||||
def _create(browser):
|
||||
functional.visit(browser, '/plinth/sys/snapshot/manage/')
|
||||
functional.submit(browser) # Click on 'Create Snapshot'
|
||||
|
||||
|
||||
def _get_count(browser):
|
||||
functional.visit(browser, '/plinth/sys/snapshot/manage/')
|
||||
# Subtract 1 for table header
|
||||
return len(browser.find_by_xpath('//tr')) - 1
|
||||
|
||||
|
||||
def _set_configuration(browser, free_space, timeline_enabled, software_enabled,
|
||||
hourly, daily, weekly, monthly, yearly):
|
||||
"""Set the configuration for snapshots."""
|
||||
functional.nav_to_module(browser, 'snapshot')
|
||||
browser.find_by_name('free_space').select(free_space / 100)
|
||||
browser.find_by_name('enable_timeline_snapshots').select(
|
||||
'yes' if timeline_enabled else 'no')
|
||||
browser.find_by_name('enable_software_snapshots').select(
|
||||
'yes' if software_enabled else 'no')
|
||||
browser.find_by_name('hourly_limit').fill(hourly)
|
||||
browser.find_by_name('daily_limit').fill(daily)
|
||||
browser.find_by_name('weekly_limit').fill(weekly)
|
||||
browser.find_by_name('monthly_limit').fill(monthly)
|
||||
browser.find_by_name('yearly_limit').fill(yearly)
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _get_configuration(browser):
|
||||
"""Return the current configuration for snapshots."""
|
||||
functional.nav_to_module(browser, 'snapshot')
|
||||
return (int(float(browser.find_by_name('free_space').value) * 100),
|
||||
browser.find_by_name('enable_timeline_snapshots').value == 'yes',
|
||||
browser.find_by_name('enable_software_snapshots').value == 'yes',
|
||||
int(browser.find_by_name('hourly_limit').value),
|
||||
int(browser.find_by_name('daily_limit').value),
|
||||
int(browser.find_by_name('weekly_limit').value),
|
||||
int(browser.find_by_name('monthly_limit').value),
|
||||
int(browser.find_by_name('yearly_limit').value))
|
||||
|
||||
@ -3,6 +3,23 @@
|
||||
Functional, browser based tests for storage app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('storage.feature')
|
||||
|
||||
|
||||
@then('the root disk should be shown')
|
||||
def storage_root_disk_is_shown(session_browser):
|
||||
assert _is_root_disk_shown(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse("I'm on the {name:w} page"))
|
||||
def go_to_module(session_browser, name):
|
||||
functional.nav_to_module(session_browser, name)
|
||||
|
||||
|
||||
def _is_root_disk_shown(browser):
|
||||
table_cells = browser.find_by_tag('td')
|
||||
return any(cell.text == '/' for cell in table_cells)
|
||||
|
||||
@ -3,6 +3,141 @@
|
||||
Functional, browser based tests for syncthing app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('syncthing.feature')
|
||||
|
||||
|
||||
@given(parsers.parse('syncthing folder {folder_name:w} is not present'))
|
||||
def syncthing_folder_not_present(session_browser, folder_name):
|
||||
if _folder_is_present(session_browser, folder_name):
|
||||
_remove_folder(session_browser, folder_name)
|
||||
|
||||
|
||||
@given(
|
||||
parsers.parse(
|
||||
'folder {folder_path:S} is present as syncthing folder {folder_name:w}'
|
||||
))
|
||||
def syncthing_folder_present(session_browser, folder_name, folder_path):
|
||||
if not _folder_is_present(session_browser, folder_name):
|
||||
_add_folder(session_browser, folder_name, folder_path)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse(
|
||||
'I add a folder {folder_path:S} as syncthing folder {folder_name:w}'))
|
||||
def syncthing_add_folder(session_browser, folder_name, folder_path):
|
||||
_add_folder(session_browser, folder_name, folder_path)
|
||||
|
||||
|
||||
@when(parsers.parse('I remove syncthing folder {folder_name:w}'))
|
||||
def syncthing_remove_folder(session_browser, folder_name):
|
||||
_remove_folder(session_browser, folder_name)
|
||||
|
||||
|
||||
@then(parsers.parse('syncthing folder {folder_name:w} should be present'))
|
||||
def syncthing_assert_folder_present(session_browser, folder_name):
|
||||
assert _folder_is_present(session_browser, folder_name)
|
||||
|
||||
|
||||
@then(parsers.parse('syncthing folder {folder_name:w} should not be present'))
|
||||
def syncthing_assert_folder_not_present(session_browser, folder_name):
|
||||
assert not _folder_is_present(session_browser, folder_name)
|
||||
|
||||
|
||||
def _load_main_interface(browser):
|
||||
"""Close the dialog boxes that many popup after visiting the URL."""
|
||||
functional.access_url(browser, 'syncthing')
|
||||
|
||||
def service_is_available():
|
||||
if browser.is_element_present_by_xpath(
|
||||
'//h1[text()="Service Unavailable"]'):
|
||||
functional.access_url(browser, 'syncthing')
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# After a backup restore, service may not be available immediately
|
||||
functional.eventually(service_is_available)
|
||||
|
||||
# Wait for javascript loading process to complete
|
||||
browser.execute_script('''
|
||||
document.is_ui_online = false;
|
||||
var old_console_log = console.log;
|
||||
console.log = function(message) {
|
||||
old_console_log.apply(null, arguments);
|
||||
if (message == 'UIOnline') {
|
||||
document.is_ui_online = true;
|
||||
console.log = old_console_log;
|
||||
}
|
||||
};
|
||||
''')
|
||||
functional.eventually(
|
||||
lambda: browser.evaluate_script('document.is_ui_online'), timeout=5)
|
||||
|
||||
# Dismiss the Usage Reporting consent dialog
|
||||
usage_reporting = browser.find_by_id('ur').first
|
||||
functional.eventually(lambda: usage_reporting.visible, timeout=2)
|
||||
if usage_reporting.visible:
|
||||
yes_xpath = './/button[contains(@ng-click, "declineUR")]'
|
||||
usage_reporting.find_by_xpath(yes_xpath).first.click()
|
||||
functional.eventually(lambda: not usage_reporting.visible)
|
||||
|
||||
|
||||
def _folder_is_present(browser, folder_name):
|
||||
"""Return whether a folder is present in Syncthing."""
|
||||
_load_main_interface(browser)
|
||||
folder_names = browser.find_by_css('#folders .panel-title-text span')
|
||||
folder_names = [folder_name.text for folder_name in folder_names]
|
||||
return folder_name in folder_names
|
||||
|
||||
|
||||
def _add_folder(browser, folder_name, folder_path):
|
||||
"""Add a new folder to Synthing."""
|
||||
_load_main_interface(browser)
|
||||
add_folder_xpath = '//button[contains(@ng-click, "addFolder")]'
|
||||
browser.find_by_xpath(add_folder_xpath).click()
|
||||
|
||||
folder_dialog = browser.find_by_id('editFolder').first
|
||||
functional.eventually(lambda: folder_dialog.visible)
|
||||
browser.find_by_id('folderLabel').fill(folder_name)
|
||||
browser.find_by_id('folderPath').fill(folder_path)
|
||||
save_folder_xpath = './/button[contains(@ng-click, "saveFolder")]'
|
||||
folder_dialog.find_by_xpath(save_folder_xpath).first.click()
|
||||
functional.eventually(lambda: not folder_dialog.visible)
|
||||
|
||||
|
||||
def _remove_folder(browser, folder_name):
|
||||
"""Remove a folder from Synthing."""
|
||||
_load_main_interface(browser)
|
||||
|
||||
# Find folder
|
||||
folder = None
|
||||
for current_folder in browser.find_by_css('#folders > .panel'):
|
||||
name = current_folder.find_by_css('.panel-title-text span').first.text
|
||||
if name == folder_name:
|
||||
folder = current_folder
|
||||
break
|
||||
|
||||
# Edit folder button
|
||||
folder.find_by_css('button.panel-heading').first.click()
|
||||
functional.eventually(lambda: folder.find_by_css('div.collapse.in'))
|
||||
edit_folder_xpath = './/button[contains(@ng-click, "editFolder")]'
|
||||
edit_folder_button = folder.find_by_xpath(edit_folder_xpath).first
|
||||
edit_folder_button.click()
|
||||
|
||||
# Edit folder dialog
|
||||
folder_dialog = browser.find_by_id('editFolder').first
|
||||
functional.eventually(lambda: folder_dialog.visible)
|
||||
remove_button_xpath = './/button[contains(@data-target, "remove-folder")]'
|
||||
folder_dialog.find_by_xpath(remove_button_xpath).first.click()
|
||||
|
||||
# Remove confirmation dialog
|
||||
remove_folder_dialog = browser.find_by_id('remove-folder-confirmation')
|
||||
functional.eventually(lambda: remove_folder_dialog.visible)
|
||||
remove_button_xpath = './/button[contains(@ng-click, "deleteFolder")]'
|
||||
remove_folder_dialog.find_by_xpath(remove_button_xpath).first.click()
|
||||
|
||||
functional.eventually(lambda: not folder_dialog.visible)
|
||||
|
||||
@ -3,6 +3,72 @@
|
||||
Functional, browser based tests for tahoe app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('tahoe.feature')
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'{domain:S} should be a tahoe {introducer_type:w} introducer'))
|
||||
def tahoe_assert_introducer(session_browser, domain, introducer_type):
|
||||
assert _get_introducer(session_browser, domain, introducer_type)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'{domain:S} should not be a tahoe {introducer_type:w} introducer'))
|
||||
def tahoe_assert_not_introducer(session_browser, domain, introducer_type):
|
||||
assert not _get_introducer(session_browser, domain, introducer_type)
|
||||
|
||||
|
||||
@given(parsers.parse('{domain:S} is not a tahoe introducer'))
|
||||
def tahoe_given_remove_introducer(session_browser, domain):
|
||||
if _get_introducer(session_browser, domain, 'connected'):
|
||||
_remove_introducer(session_browser, domain)
|
||||
|
||||
|
||||
@when(parsers.parse('I add {domain:S} as a tahoe introducer'))
|
||||
def tahoe_add_introducer(session_browser, domain):
|
||||
_add_introducer(session_browser, domain)
|
||||
|
||||
|
||||
@given(parsers.parse('{domain:S} is a tahoe introducer'))
|
||||
def tahoe_given_add_introducer(session_browser, domain):
|
||||
if not _get_introducer(session_browser, domain, 'connected'):
|
||||
_add_introducer(session_browser, domain)
|
||||
|
||||
|
||||
@when(parsers.parse('I remove {domain:S} as a tahoe introducer'))
|
||||
def tahoe_remove_introducer(session_browser, domain):
|
||||
_remove_introducer(session_browser, domain)
|
||||
|
||||
|
||||
def _get_introducer(browser, domain, introducer_type):
|
||||
"""Return an introducer element with a given type from tahoe-lafs."""
|
||||
functional.nav_to_module(browser, 'tahoe')
|
||||
css_class = '.{}-introducers .introducer-furl'.format(introducer_type)
|
||||
for furl in browser.find_by_css(css_class):
|
||||
if domain in furl.text:
|
||||
return furl.parent
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _add_introducer(browser, domain):
|
||||
"""Add a new introducer into tahoe-lafs."""
|
||||
functional.nav_to_module(browser, 'tahoe')
|
||||
|
||||
furl = 'pb://ewe4zdz6kxn7xhuvc7izj2da2gpbgeir@tcp:{}:3456/' \
|
||||
'fko4ivfwgqvybppwar3uehkx6spaaou7'.format(domain)
|
||||
browser.fill('pet_name', 'testintroducer')
|
||||
browser.fill('furl', furl)
|
||||
functional.submit(browser, form_class='form-add-introducer')
|
||||
|
||||
|
||||
def _remove_introducer(browser, domain):
|
||||
"""Remove an introducer from tahoe-lafs."""
|
||||
introducer = _get_introducer(browser, domain, 'connected')
|
||||
functional.submit(browser, element=introducer.find_by_css('.form-remove'))
|
||||
|
||||
@ -3,6 +3,134 @@
|
||||
Functional, browser based tests for tor app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
_TOR_FEATURE_TO_ELEMENT = {
|
||||
'relay': 'tor-relay_enabled',
|
||||
'bridge-relay': 'tor-bridge_relay_enabled',
|
||||
'hidden-services': 'tor-hs_enabled',
|
||||
'software': 'tor-apt_transport_tor_enabled'
|
||||
}
|
||||
|
||||
scenarios('tor.feature')
|
||||
|
||||
|
||||
@given(parsers.parse('tor relay is {enabled:w}'))
|
||||
def tor_given_relay_enable(session_browser, enabled):
|
||||
_feature_enable(session_browser, 'relay', enabled)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable:w} tor relay'))
|
||||
def tor_relay_enable(session_browser, enable):
|
||||
_feature_enable(session_browser, 'relay', enable)
|
||||
|
||||
|
||||
@then(parsers.parse('tor relay should be {enabled:w}'))
|
||||
def tor_assert_relay_enabled(session_browser, enabled):
|
||||
_assert_feature_enabled(session_browser, 'relay', enabled)
|
||||
|
||||
|
||||
@then(parsers.parse('tor {port_name:w} port should be displayed'))
|
||||
def tor_assert_port_displayed(session_browser, port_name):
|
||||
assert port_name in _get_relay_ports(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse('tor bridge relay is {enabled:w}'))
|
||||
def tor_given_bridge_relay_enable(session_browser, enabled):
|
||||
_feature_enable(session_browser, 'bridge-relay', enabled)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable:w} tor bridge relay'))
|
||||
def tor_bridge_relay_enable(session_browser, enable):
|
||||
_feature_enable(session_browser, 'bridge-relay', enable)
|
||||
|
||||
|
||||
@then(parsers.parse('tor bridge relay should be {enabled:w}'))
|
||||
def tor_assert_bridge_relay_enabled(session_browser, enabled):
|
||||
_assert_feature_enabled(session_browser, 'bridge-relay', enabled)
|
||||
|
||||
|
||||
@given(parsers.parse('tor hidden services are {enabled:w}'))
|
||||
def tor_given_hidden_services_enable(session_browser, enabled):
|
||||
_feature_enable(session_browser, 'hidden-services', enabled)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable:w} tor hidden services'))
|
||||
def tor_hidden_services_enable(session_browser, enable):
|
||||
_feature_enable(session_browser, 'hidden-services', enable)
|
||||
|
||||
|
||||
@then(parsers.parse('tor hidden services should be {enabled:w}'))
|
||||
def tor_assert_hidden_services_enabled(session_browser, enabled):
|
||||
_assert_feature_enabled(session_browser, 'hidden-services', enabled)
|
||||
|
||||
|
||||
@then(parsers.parse('tor hidden services information should be displayed'))
|
||||
def tor_assert_hidden_services(session_browser):
|
||||
_assert_hidden_services(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse('download software packages over tor is {enabled:w}'))
|
||||
def tor_given_download_software_over_tor_enable(session_browser, enabled):
|
||||
_feature_enable(session_browser, 'software', enabled)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable:w} download software packages over tor'))
|
||||
def tor_download_software_over_tor_enable(session_browser, enable):
|
||||
_feature_enable(session_browser, 'software', enable)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse('download software packages over tor should be {enabled:w}'))
|
||||
def tor_assert_download_software_over_tor(session_browser, enabled):
|
||||
_assert_feature_enabled(session_browser, 'software', enabled)
|
||||
|
||||
|
||||
def _feature_enable(browser, feature, should_enable):
|
||||
"""Enable/disable a Tor feature."""
|
||||
if not isinstance(should_enable, bool):
|
||||
should_enable = should_enable in ('enable', 'enabled')
|
||||
|
||||
element_name = _TOR_FEATURE_TO_ELEMENT[feature]
|
||||
functional.nav_to_module(browser, 'tor')
|
||||
checkbox_element = browser.find_by_name(element_name).first
|
||||
if should_enable == checkbox_element.checked:
|
||||
return
|
||||
|
||||
if should_enable:
|
||||
if feature == 'bridge-relay':
|
||||
browser.find_by_name('tor-relay_enabled').first.check()
|
||||
|
||||
checkbox_element.check()
|
||||
else:
|
||||
checkbox_element.uncheck()
|
||||
|
||||
functional.submit(browser, form_class='form-configuration')
|
||||
functional.wait_for_config_update(browser, 'tor')
|
||||
|
||||
|
||||
def _assert_feature_enabled(browser, feature, enabled):
|
||||
"""Assert whether Tor relay is enabled or disabled."""
|
||||
if not isinstance(enabled, bool):
|
||||
enabled = enabled in ('enable', 'enabled')
|
||||
|
||||
element_name = _TOR_FEATURE_TO_ELEMENT[feature]
|
||||
functional.nav_to_module(browser, 'tor')
|
||||
assert browser.find_by_name(element_name).first.checked == enabled
|
||||
|
||||
|
||||
def _get_relay_ports(browser):
|
||||
"""Return the list of ports shown in the relay table."""
|
||||
functional.nav_to_module(browser, 'tor')
|
||||
return [
|
||||
port_name.text
|
||||
for port_name in browser.find_by_css('.tor-relay-port-name')
|
||||
]
|
||||
|
||||
|
||||
def _assert_hidden_services(browser):
|
||||
"""Assert that hidden service information is shown."""
|
||||
functional.nav_to_module(browser, 'tor')
|
||||
assert browser.find_by_css('.tor-hs .tor-hs-hostname')
|
||||
|
||||
BIN
plinth/modules/transmission/tests/data/sample.torrent
Normal file
BIN
plinth/modules/transmission/tests/data/sample.torrent
Normal file
Binary file not shown.
@ -3,6 +3,67 @@
|
||||
Functional, browser based tests for transmission app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
import os
|
||||
|
||||
from pytest_bdd import parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('transmission.feature')
|
||||
|
||||
|
||||
@when('all torrents are removed from transmission')
|
||||
def transmission_remove_all_torrents(session_browser):
|
||||
_remove_all_torrents(session_browser)
|
||||
|
||||
|
||||
@when('I upload a sample torrent to transmission')
|
||||
def transmission_upload_sample_torrent(session_browser):
|
||||
_upload_sample_torrent(session_browser)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'there should be {torrents_number:d} torrents listed in transmission'))
|
||||
def transmission_assert_number_of_torrents(session_browser, torrents_number):
|
||||
assert torrents_number == _get_number_of_torrents(session_browser)
|
||||
|
||||
|
||||
def _remove_all_torrents(browser):
|
||||
"""Remove all torrents from transmission."""
|
||||
functional.visit(browser, '/transmission')
|
||||
while True:
|
||||
torrents = browser.find_by_css('#torrent_list .torrent')
|
||||
if not torrents:
|
||||
break
|
||||
|
||||
torrents.first.click()
|
||||
functional.eventually(browser.is_element_not_present_by_css,
|
||||
args=['#toolbar-remove.disabled'])
|
||||
browser.click_link_by_id('toolbar-remove')
|
||||
functional.eventually(
|
||||
browser.is_element_not_present_by_css,
|
||||
args=['#dialog-container[style="display: none;"]'])
|
||||
browser.click_link_by_id('dialog_confirm_button')
|
||||
functional.eventually(browser.is_element_present_by_css,
|
||||
args=['#toolbar-remove.disabled'])
|
||||
|
||||
|
||||
def _upload_sample_torrent(browser):
|
||||
"""Upload a sample torrent into transmission."""
|
||||
functional.visit(browser, '/transmission')
|
||||
file_path = os.path.join(os.path.dirname(__file__), 'data',
|
||||
'sample.torrent')
|
||||
browser.click_link_by_id('toolbar-open')
|
||||
functional.eventually(browser.is_element_not_present_by_css,
|
||||
args=['#upload-container[style="display: none;"]'])
|
||||
browser.attach_file('torrent_files[]', [file_path])
|
||||
browser.click_link_by_id('upload_confirm_button')
|
||||
functional.eventually(browser.is_element_present_by_css,
|
||||
args=['#torrent_list .torrent'])
|
||||
|
||||
|
||||
def _get_number_of_torrents(browser):
|
||||
"""Return the number torrents currently in transmission."""
|
||||
functional.visit(browser, '/transmission')
|
||||
return len(browser.find_by_css('#torrent_list .torrent'))
|
||||
|
||||
@ -3,6 +3,73 @@
|
||||
Functional, browser based tests for ttrss app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('ttrss.feature')
|
||||
|
||||
|
||||
@given('I subscribe to a feed in ttrss')
|
||||
def ttrss_subscribe(session_browser):
|
||||
_subscribe(session_browser)
|
||||
|
||||
|
||||
@when('I unsubscribe from the feed in ttrss')
|
||||
def ttrss_unsubscribe(session_browser):
|
||||
_unsubscribe(session_browser)
|
||||
|
||||
|
||||
@then('I should be subscribed to the feed in ttrss')
|
||||
def ttrss_assert_subscribed(session_browser):
|
||||
assert _is_subscribed(session_browser)
|
||||
|
||||
|
||||
def _ttrss_load_main_interface(browser):
|
||||
"""Load the TT-RSS interface."""
|
||||
functional.access_url(browser, 'ttrss')
|
||||
overlay = browser.find_by_id('overlay')
|
||||
functional.eventually(lambda: not overlay.visible)
|
||||
|
||||
|
||||
def _is_feed_shown(browser, invert=False):
|
||||
return browser.is_text_present('Planet Debian') != invert
|
||||
|
||||
|
||||
def _subscribe(browser):
|
||||
"""Subscribe to a feed in TT-RSS."""
|
||||
_ttrss_load_main_interface(browser)
|
||||
browser.find_by_text('Actions...').click()
|
||||
browser.find_by_text('Subscribe to feed...').click()
|
||||
browser.find_by_id('feedDlg_feedUrl').fill(
|
||||
'https://planet.debian.org/atom.xml')
|
||||
browser.find_by_text('Subscribe').click()
|
||||
if browser.is_text_present('You are already subscribed to this feed.'):
|
||||
browser.find_by_text('Cancel').click()
|
||||
|
||||
expand = browser.find_by_css('span.dijitTreeExpandoClosed')
|
||||
if expand:
|
||||
expand.first.click()
|
||||
|
||||
assert functional.eventually(_is_feed_shown, [browser])
|
||||
|
||||
|
||||
def _unsubscribe(browser):
|
||||
"""Unsubscribe from a feed in TT-RSS."""
|
||||
_ttrss_load_main_interface(browser)
|
||||
expand = browser.find_by_css('span.dijitTreeExpandoClosed')
|
||||
if expand:
|
||||
expand.first.click()
|
||||
|
||||
browser.find_by_text('Planet Debian').click()
|
||||
browser.execute_script("quickMenuGo('qmcRemoveFeed')")
|
||||
prompt = browser.get_alert()
|
||||
prompt.accept()
|
||||
|
||||
assert functional.eventually(_is_feed_shown, [browser, True])
|
||||
|
||||
|
||||
def _is_subscribed(browser):
|
||||
"""Return whether subscribed to a feed in TT-RSS."""
|
||||
_ttrss_load_main_interface(browser)
|
||||
return browser.is_text_present('Planet Debian')
|
||||
|
||||
@ -3,6 +3,47 @@
|
||||
Functional, browser based tests for upgrades app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('upgrades.feature')
|
||||
|
||||
|
||||
@given(parsers.parse('automatic upgrades are {enabled:w}'))
|
||||
def upgrades_given_enable_automatic(session_browser, enabled):
|
||||
should_enable = (enabled == 'enabled')
|
||||
_enable_automatic(session_browser, should_enable)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable:w} automatic upgrades'))
|
||||
def upgrades_enable_automatic(session_browser, enable):
|
||||
should_enable = (enable == 'enable')
|
||||
_enable_automatic(session_browser, should_enable)
|
||||
|
||||
|
||||
@then(parsers.parse('automatic upgrades should be {enabled:w}'))
|
||||
def upgrades_assert_automatic(session_browser, enabled):
|
||||
should_be_enabled = (enabled == 'enabled')
|
||||
assert _get_automatic(session_browser) == should_be_enabled
|
||||
|
||||
|
||||
def _enable_automatic(browser, should_enable):
|
||||
"""Enable/disable automatic software upgrades."""
|
||||
functional.nav_to_module(browser, 'upgrades')
|
||||
checkbox_element = browser.find_by_name('auto_upgrades_enabled').first
|
||||
if should_enable == checkbox_element.checked:
|
||||
return
|
||||
|
||||
if should_enable:
|
||||
checkbox_element.check()
|
||||
else:
|
||||
checkbox_element.uncheck()
|
||||
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _get_automatic(browser):
|
||||
"""Return whether automatic software upgrades is enabled."""
|
||||
functional.nav_to_module(browser, 'upgrades')
|
||||
return browser.find_by_name('auto_upgrades_enabled').first.checked
|
||||
|
||||
@ -3,6 +3,140 @@
|
||||
Functional, browser based tests for users app.
|
||||
"""
|
||||
|
||||
from pytest_bdd import scenarios
|
||||
from pytest_bdd import given, parsers, scenarios, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
scenarios('users.feature')
|
||||
|
||||
_language_codes = {
|
||||
'Deutsch': 'de',
|
||||
'Nederlands': 'nl',
|
||||
'Português': 'pt',
|
||||
'Türkçe': 'tr',
|
||||
'dansk': 'da',
|
||||
'español': 'es',
|
||||
'français': 'fr',
|
||||
'norsk (bokmål)': 'nb',
|
||||
'polski': 'pl',
|
||||
'svenska': 'sv',
|
||||
'Русский': 'ru',
|
||||
'తెలుగు': 'te',
|
||||
'简体中文': 'zh-hans'
|
||||
}
|
||||
|
||||
_config_page_title_language_map = {
|
||||
'da': 'Generel Konfiguration',
|
||||
'de': 'Allgemeine Konfiguration',
|
||||
'es': 'Configuración general',
|
||||
'fr': 'Configuration générale',
|
||||
'nb': 'Generelt oppsett',
|
||||
'nl': 'Algemene Instellingen',
|
||||
'pl': 'Ustawienia główne',
|
||||
'pt': 'Configuração Geral',
|
||||
'ru': 'Общие настройки',
|
||||
'sv': 'Allmän Konfiguration',
|
||||
'te': 'సాధారణ ఆకృతీకరణ',
|
||||
'tr': 'Genel Yapılandırma',
|
||||
'zh-hans': '常规配置',
|
||||
}
|
||||
|
||||
|
||||
@given(parsers.parse("the user {name:w} doesn't exist"))
|
||||
def new_user_does_not_exist(session_browser, name):
|
||||
_delete_user(session_browser, name)
|
||||
|
||||
|
||||
@given(parsers.parse('the user {name:w} exists'))
|
||||
def test_user_exists(session_browser, name):
|
||||
functional.nav_to_module(session_browser, 'users')
|
||||
user_link = session_browser.find_link_by_href('/plinth/sys/users/' + name +
|
||||
'/edit/')
|
||||
if not user_link:
|
||||
create_user(session_browser, name, 'secret123')
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I create a user named {name:w} with password {password:w}'))
|
||||
def create_user(session_browser, name, password):
|
||||
_create_user(session_browser, name, password)
|
||||
|
||||
|
||||
@when(parsers.parse('I rename the user {old_name:w} to {new_name:w}'))
|
||||
def rename_user(session_browser, old_name, new_name):
|
||||
_rename_user(session_browser, old_name, new_name)
|
||||
|
||||
|
||||
@when(parsers.parse('I delete the user {name:w}'))
|
||||
def delete_user(session_browser, name):
|
||||
_delete_user(session_browser, name)
|
||||
|
||||
|
||||
@then(parsers.parse('{name:w} should be listed as a user'))
|
||||
def new_user_is_listed(session_browser, name):
|
||||
assert _is_user(session_browser, name)
|
||||
|
||||
|
||||
@then(parsers.parse('{name:w} should not be listed as a user'))
|
||||
def new_user_is_not_listed(session_browser, name):
|
||||
assert not _is_user(session_browser, name)
|
||||
|
||||
|
||||
@when('I change the language to <language>')
|
||||
def change_language(session_browser, language):
|
||||
_set_language(session_browser, _language_codes[language])
|
||||
|
||||
|
||||
@then('Plinth language should be <language>')
|
||||
def plinth_language_should_be(session_browser, language):
|
||||
assert _check_language(session_browser, _language_codes[language])
|
||||
|
||||
|
||||
def _create_user(browser, name, password):
|
||||
functional.nav_to_module(browser, 'users')
|
||||
with functional.wait_for_page_update(browser):
|
||||
browser.find_link_by_href('/plinth/sys/users/create/').first.click()
|
||||
browser.find_by_id('id_username').fill(name)
|
||||
browser.find_by_id('id_password1').fill(password)
|
||||
browser.find_by_id('id_password2').fill(password)
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _rename_user(browser, old_name, new_name):
|
||||
functional.nav_to_module(browser, 'users')
|
||||
with functional.wait_for_page_update(browser):
|
||||
browser.find_link_by_href('/plinth/sys/users/' + old_name +
|
||||
'/edit/').first.click()
|
||||
browser.find_by_id('id_username').fill(new_name)
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _delete_user(browser, name):
|
||||
functional.nav_to_module(browser, 'users')
|
||||
delete_link = browser.find_link_by_href('/plinth/sys/users/' + name +
|
||||
'/delete/')
|
||||
if delete_link:
|
||||
with functional.wait_for_page_update(browser):
|
||||
delete_link.first.click()
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _is_user(browser, name):
|
||||
functional.nav_to_module(browser, 'users')
|
||||
edit_link = browser.find_link_by_href('/plinth/sys/users/' + name +
|
||||
'/edit/')
|
||||
return bool(edit_link)
|
||||
|
||||
|
||||
def _set_language(browser, language_code):
|
||||
username = functional.config['DEFAULT']['username']
|
||||
functional.visit(browser, '/plinth/sys/users/{}/edit/'.format(username))
|
||||
browser.find_by_xpath('//select[@id="id_language"]//option[@value="' +
|
||||
language_code + '"]').first.click()
|
||||
functional.submit(browser)
|
||||
|
||||
|
||||
def _check_language(browser, language_code):
|
||||
functional.nav_to_module(browser, 'config')
|
||||
return browser.find_by_css('.app-titles').first.find_by_tag(
|
||||
'h2').first.value == _config_page_title_language_map[language_code]
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
# TODO Scenario: Make user active
|
||||
# TODO Scenario: Change user password
|
||||
|
||||
@system @essential @users_groups
|
||||
@system @essential @users
|
||||
Feature: Users and Groups
|
||||
Manage users and groups.
|
||||
|
||||
|
||||
@ -0,0 +1,448 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Utilities for functional testing.
|
||||
"""
|
||||
|
||||
import configparser
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import tempfile
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
|
||||
import requests
|
||||
from selenium.common.exceptions import StaleElementReferenceException
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(pathlib.Path(__file__).with_name('config.ini'))
|
||||
|
||||
config['DEFAULT']['url'] = os.environ.get('FREEDOMBOX_URL',
|
||||
config['DEFAULT']['url'])
|
||||
config['DEFAULT']['samba_port'] = os.environ.get(
|
||||
'FREEDOMBOX_SAMBA_PORT', config['DEFAULT']['samba_port'])
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
base_url = config['DEFAULT']['url']
|
||||
|
||||
_app_checkbox_id = {
|
||||
'tor': 'id_tor-enabled',
|
||||
'openvpn': 'id_openvpn-enabled',
|
||||
}
|
||||
|
||||
_apps_with_loaders = ['tor']
|
||||
|
||||
# unlisted sites just use '/' + site_name as url
|
||||
_site_url = {
|
||||
'wiki': '/ikiwiki',
|
||||
'jsxc': '/plinth/apps/jsxc/jsxc/',
|
||||
'cockpit': '/_cockpit/',
|
||||
'syncthing': '/syncthing/',
|
||||
}
|
||||
|
||||
_sys_modules = [
|
||||
'avahi', 'backups', 'bind', 'cockpit', 'config', 'datetime', 'diagnostics',
|
||||
'dynamicdns', 'firewall', 'letsencrypt', 'monkeysphere', 'names',
|
||||
'networks', 'pagekite', 'performance', 'power', 'security', 'snapshot',
|
||||
'ssh', 'storage', 'upgrades', 'users'
|
||||
]
|
||||
|
||||
|
||||
######################
|
||||
# Browser Extensions #
|
||||
######################
|
||||
def visit(browser, path):
|
||||
"""Visit a path assuming the base URL as configured."""
|
||||
browser.visit(config['DEFAULT']['url'] + path)
|
||||
|
||||
|
||||
def eventually(function, args=[], timeout=30):
|
||||
"""Execute a function returning a boolean expression till it returns
|
||||
True or a timeout is reached"""
|
||||
end_time = time.time() + timeout
|
||||
current_time = time.time()
|
||||
while current_time < end_time:
|
||||
if function(*args):
|
||||
return True
|
||||
|
||||
time.sleep(0.1)
|
||||
current_time = time.time()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class _PageLoaded():
|
||||
"""
|
||||
Wait until a page (re)loaded.
|
||||
|
||||
- element: Wait until this element gets stale
|
||||
- expected_url (optional): Wait for the URL to become <expected_url>.
|
||||
This can be necessary to wait for a redirect to finish.
|
||||
"""
|
||||
|
||||
def __init__(self, element, expected_url=None):
|
||||
self.element = element
|
||||
self.expected_url = expected_url
|
||||
|
||||
def __call__(self, driver):
|
||||
is_stale = False
|
||||
try:
|
||||
self.element.has_class('whatever_class')
|
||||
except StaleElementReferenceException:
|
||||
if self.expected_url is None:
|
||||
is_stale = True
|
||||
else:
|
||||
if driver.url.endswith(self.expected_url):
|
||||
is_stale = True
|
||||
return is_stale
|
||||
|
||||
|
||||
@contextmanager
|
||||
def wait_for_page_update(browser, timeout=300, expected_url=None):
|
||||
page_body = browser.find_by_tag('body').first
|
||||
yield
|
||||
WebDriverWait(browser, timeout).until(_PageLoaded(page_body, expected_url))
|
||||
|
||||
|
||||
def _get_site_url(site_name):
|
||||
if site_name.startswith('share'):
|
||||
site_name = site_name.replace('_', '/')
|
||||
url = '/' + site_name
|
||||
url = _site_url.get(site_name, url)
|
||||
return url
|
||||
|
||||
|
||||
def access_url(browser, site_name):
|
||||
browser.visit(config['DEFAULT']['url'] + _get_site_url(site_name))
|
||||
|
||||
|
||||
def is_available(browser, site_name):
|
||||
url_to_visit = config['DEFAULT']['url'] + _get_site_url(site_name)
|
||||
browser.visit(url_to_visit)
|
||||
time.sleep(3)
|
||||
browser.reload()
|
||||
not_404 = '404' not in browser.title
|
||||
# The site might have a default path after the sitename,
|
||||
# e.g /mediawiki/Main_Page
|
||||
no_redirect = browser.url.startswith(url_to_visit.strip('/'))
|
||||
return not_404 and no_redirect
|
||||
|
||||
|
||||
def download_file(browser, url):
|
||||
"""Return file contents after downloading a URL."""
|
||||
cookies = browser.cookies.all()
|
||||
response = requests.get(url, cookies=cookies, verify=False)
|
||||
if response.status_code != 200:
|
||||
raise Exception('URL download failed')
|
||||
|
||||
return response.content
|
||||
|
||||
|
||||
def download_file_outside_browser(url):
|
||||
"""Download a file to disk given a URL."""
|
||||
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
||||
logging.captureWarnings(True)
|
||||
request = requests.get(url, verify=False)
|
||||
logging.captureWarnings(False)
|
||||
temp_file.write(request.content)
|
||||
|
||||
return temp_file.name
|
||||
|
||||
|
||||
###########################
|
||||
# Form handling utilities #
|
||||
###########################
|
||||
def submit(browser, element=None, form_class=None, expected_url=None):
|
||||
with wait_for_page_update(browser, expected_url=expected_url):
|
||||
if element:
|
||||
element.click()
|
||||
elif form_class:
|
||||
browser.find_by_css(
|
||||
'.{} input[type=submit]'.format(form_class)).click()
|
||||
else:
|
||||
browser.find_by_css('input[type=submit]').click()
|
||||
|
||||
|
||||
def change_checkbox_status(browser, app_name, checkbox_id,
|
||||
change_status_to='enabled'):
|
||||
"""Change checkbox status."""
|
||||
checkbox = browser.find_by_id(checkbox_id)
|
||||
if change_status_to == 'enabled':
|
||||
checkbox.check()
|
||||
else:
|
||||
checkbox.uncheck()
|
||||
|
||||
submit(browser, form_class='form-configuration')
|
||||
|
||||
if app_name in _apps_with_loaders:
|
||||
wait_for_config_update(browser, app_name)
|
||||
|
||||
|
||||
def wait_for_config_update(browser, app_name):
|
||||
while browser.is_element_present_by_css('.running-status.loading'):
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
############################
|
||||
# Login handling utilities #
|
||||
############################
|
||||
def is_login_prompt(browser):
|
||||
return all(
|
||||
[browser.find_by_id('id_username'),
|
||||
browser.find_by_id('id_password')])
|
||||
|
||||
|
||||
def _create_admin_account(browser, username, password):
|
||||
browser.find_by_id('id_username').fill(username)
|
||||
browser.find_by_id('id_password1').fill(password)
|
||||
browser.find_by_id('id_password2').fill(password)
|
||||
submit(browser)
|
||||
|
||||
|
||||
def login(browser, url, username, password):
|
||||
|
||||
# XXX: Find a way to remove the hardcoded jsxc URL
|
||||
if '/plinth/' not in browser.url or '/jsxc/jsxc' in browser.url:
|
||||
browser.visit(url)
|
||||
|
||||
apps_link = browser.find_link_by_href('/plinth/apps/')
|
||||
if len(apps_link):
|
||||
return
|
||||
|
||||
login_button = browser.find_link_by_href('/plinth/accounts/login/')
|
||||
if login_button:
|
||||
login_button.first.click()
|
||||
if login_button:
|
||||
browser.fill('username', username)
|
||||
browser.fill('password', password)
|
||||
submit(browser)
|
||||
else:
|
||||
browser.visit(base_url + '/plinth/firstboot/welcome')
|
||||
submit(browser) # click the "Start Setup" button
|
||||
_create_admin_account(browser, username, password)
|
||||
if '/network-topology-first-boot' in browser.url:
|
||||
submit(browser, element=browser.find_by_name('skip')[0])
|
||||
|
||||
if '/internet-connection-type' in browser.url:
|
||||
submit(browser, element=browser.find_by_name('skip')[0])
|
||||
|
||||
|
||||
#################
|
||||
# App utilities #
|
||||
#################
|
||||
def nav_to_module(browser, module):
|
||||
sys_or_apps = 'sys' if module in _sys_modules else 'apps'
|
||||
required_url = base_url + f'/plinth/{sys_or_apps}/{module}/'
|
||||
if browser.url != required_url:
|
||||
browser.visit(required_url)
|
||||
|
||||
|
||||
def app_select_domain_name(browser, app_name, domain_name):
|
||||
browser.visit('{}/plinth/apps/{}/setup/'.format(base_url, app_name))
|
||||
drop_down = browser.find_by_id('id_domain_name')
|
||||
drop_down.select(domain_name)
|
||||
submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
#########################
|
||||
# App install utilities #
|
||||
#########################
|
||||
def _find_install_button(browser, app_name):
|
||||
nav_to_module(browser, app_name)
|
||||
return browser.find_by_css('.form-install input[type=submit]')
|
||||
|
||||
|
||||
def is_installed(browser, app_name):
|
||||
install_button = _find_install_button(browser, app_name)
|
||||
return not bool(install_button)
|
||||
|
||||
|
||||
def install(browser, app_name):
|
||||
install_button = _find_install_button(browser, app_name)
|
||||
|
||||
def install_in_progress():
|
||||
selectors = [
|
||||
'.install-state-' + state
|
||||
for state in ['pre', 'post', 'installing']
|
||||
]
|
||||
return any(
|
||||
browser.is_element_present_by_css(selector)
|
||||
for selector in selectors)
|
||||
|
||||
def is_server_restarting():
|
||||
return browser.is_element_present_by_css('.neterror')
|
||||
|
||||
def wait_for_install():
|
||||
if install_in_progress():
|
||||
time.sleep(1)
|
||||
elif is_server_restarting():
|
||||
time.sleep(1)
|
||||
browser.visit(browser.url)
|
||||
else:
|
||||
return
|
||||
wait_for_install()
|
||||
|
||||
if install_button:
|
||||
install_button.click()
|
||||
wait_for_install()
|
||||
# sleep(2) # XXX This shouldn't be required.
|
||||
|
||||
|
||||
################################
|
||||
# App enable/disable utilities #
|
||||
################################
|
||||
def _change_app_status(browser, app_name, change_status_to='enabled'):
|
||||
"""Enable or disable application."""
|
||||
button = browser.find_by_css('button[name="app_enable_disable_button"]')
|
||||
|
||||
if button:
|
||||
should_enable_field = browser.find_by_id('id_should_enable')
|
||||
if (should_enable_field.value == 'False'
|
||||
and change_status_to == 'disabled') or (
|
||||
should_enable_field.value == 'True'
|
||||
and change_status_to == 'enabled'):
|
||||
submit(browser, element=button)
|
||||
else:
|
||||
checkbox_id = _app_checkbox_id[app_name]
|
||||
change_checkbox_status(browser, app_name, checkbox_id,
|
||||
change_status_to)
|
||||
|
||||
if app_name in _apps_with_loaders:
|
||||
wait_for_config_update(browser, app_name)
|
||||
|
||||
|
||||
def app_enable(browser, app_name):
|
||||
nav_to_module(browser, app_name)
|
||||
_change_app_status(browser, app_name, 'enabled')
|
||||
|
||||
|
||||
def app_disable(browser, app_name):
|
||||
nav_to_module(browser, app_name)
|
||||
_change_app_status(browser, app_name, 'disabled')
|
||||
|
||||
|
||||
def app_can_be_disabled(browser, app_name):
|
||||
"""Return whether the application can be disabled."""
|
||||
nav_to_module(browser, app_name)
|
||||
button = browser.find_by_css('button[name="app_enable_disable_button"]')
|
||||
return bool(button)
|
||||
|
||||
|
||||
#########################
|
||||
# Domain name utilities #
|
||||
#########################
|
||||
def set_domain_name(browser, domain_name):
|
||||
nav_to_module(browser, 'config')
|
||||
browser.find_by_id('id_domainname').fill(domain_name)
|
||||
submit(browser)
|
||||
|
||||
|
||||
########################
|
||||
# Front page utilities #
|
||||
########################
|
||||
def find_on_front_page(browser, app_name):
|
||||
browser.visit(base_url)
|
||||
shortcuts = browser.find_link_by_href(f'/{app_name}/')
|
||||
return shortcuts
|
||||
|
||||
|
||||
####################
|
||||
# Daemon utilities #
|
||||
####################
|
||||
def service_is_running(browser, app_name):
|
||||
nav_to_module(browser, app_name)
|
||||
return len(browser.find_by_id('service-not-running')) == 0
|
||||
|
||||
|
||||
def service_is_not_running(browser, app_name):
|
||||
nav_to_module(browser, app_name)
|
||||
return len(browser.find_by_id('service-not-running')) != 0
|
||||
|
||||
|
||||
##############################
|
||||
# System -> Config utilities #
|
||||
##############################
|
||||
def set_advanced_mode(browser, mode):
|
||||
nav_to_module(browser, 'config')
|
||||
advanced_mode = browser.find_by_id('id_advanced_mode')
|
||||
if mode:
|
||||
advanced_mode.check()
|
||||
else:
|
||||
advanced_mode.uncheck()
|
||||
|
||||
submit(browser)
|
||||
|
||||
|
||||
####################
|
||||
# Backup utilities #
|
||||
####################
|
||||
def _click_button_and_confirm(browser, href):
|
||||
buttons = browser.find_link_by_href(href)
|
||||
if buttons:
|
||||
buttons.first.click()
|
||||
with wait_for_page_update(browser,
|
||||
expected_url='/plinth/sys/backups/'):
|
||||
submit(browser)
|
||||
|
||||
|
||||
def _backup_delete_archive_by_name(browser, archive_name):
|
||||
nav_to_module(browser, 'backups')
|
||||
href = f'/plinth/sys/backups/root/delete/{archive_name}/'
|
||||
_click_button_and_confirm(browser, href)
|
||||
|
||||
|
||||
def backup_create(browser, app_name, archive_name=None):
|
||||
install(browser, 'backups')
|
||||
if archive_name:
|
||||
_backup_delete_archive_by_name(browser, archive_name)
|
||||
|
||||
browser.find_link_by_href('/plinth/sys/backups/create/').first.click()
|
||||
browser.find_by_id('select-all').uncheck()
|
||||
if archive_name:
|
||||
browser.find_by_id('id_backups-name').fill(archive_name)
|
||||
|
||||
# ensure the checkbox is scrolled into view
|
||||
browser.execute_script('window.scrollTo(0, 0)')
|
||||
browser.find_by_value(app_name).first.check()
|
||||
submit(browser)
|
||||
|
||||
|
||||
def backup_restore(browser, app_name, archive_name=None):
|
||||
nav_to_module(browser, 'backups')
|
||||
href = f'/plinth/sys/backups/root/restore-archive/{archive_name}/'
|
||||
_click_button_and_confirm(browser, href)
|
||||
|
||||
|
||||
######################
|
||||
# Networks utilities #
|
||||
######################
|
||||
def networks_set_firewall_zone(browser, zone):
|
||||
""""Set the network device firewall zone as internal or external."""
|
||||
nav_to_module(browser, 'networks')
|
||||
device = browser.find_by_xpath(
|
||||
'//span[contains(@class, "label-success") '
|
||||
'and contains(@class, "connection-status-label")]/following::a').first
|
||||
network_id = device['href'].split('/')[-3]
|
||||
device.click()
|
||||
edit_url = "/plinth/sys/networks/{}/edit/".format(network_id)
|
||||
browser.find_link_by_href(edit_url).first.click()
|
||||
browser.select('zone', zone)
|
||||
browser.find_by_tag("form").first.find_by_tag('input')[-1].click()
|
||||
|
||||
|
||||
##################
|
||||
# Bind utilities #
|
||||
##################
|
||||
def set_forwarders(browser, forwarders):
|
||||
"""Set the forwarders list (space separated) in bind configuration."""
|
||||
nav_to_module(browser, 'bind')
|
||||
browser.fill('forwarders', forwarders)
|
||||
submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def get_forwarders(browser):
|
||||
"""Return the forwarders list (space separated) in bind configuration."""
|
||||
nav_to_module(browser, 'bind')
|
||||
return browser.find_by_name('forwarders').first.value
|
||||
163
plinth/tests/functional/step_definitions.py
Normal file
163
plinth/tests/functional/step_definitions.py
Normal file
@ -0,0 +1,163 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Step definitions used across apps.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from pytest_bdd import given, parsers, then, when
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
|
||||
@given("I'm a logged in user")
|
||||
def logged_in_user(session_browser):
|
||||
functional.login(session_browser, functional.base_url,
|
||||
functional.config['DEFAULT']['username'],
|
||||
functional.config['DEFAULT']['password'])
|
||||
|
||||
|
||||
@given("I'm a logged out user")
|
||||
def logged_out_user(session_browser):
|
||||
functional.visit(session_browser, '/plinth/accounts/logout/')
|
||||
|
||||
|
||||
@when("I log out")
|
||||
def log_out_user(session_browser):
|
||||
functional.visit(session_browser, '/plinth/accounts/logout/')
|
||||
|
||||
|
||||
@given(parsers.parse('the {app_name:w} application is installed'))
|
||||
def application_is_installed(session_browser, app_name):
|
||||
functional.install(session_browser, app_name)
|
||||
assert (functional.is_installed(session_browser, app_name))
|
||||
|
||||
|
||||
@given(parsers.parse('the {app_name:w} application is enabled'))
|
||||
def application_is_enabled(session_browser, app_name):
|
||||
functional.app_enable(session_browser, app_name)
|
||||
|
||||
|
||||
@given(parsers.parse('the {app_name:w} application is disabled'))
|
||||
def application_is_disabled(session_browser, app_name):
|
||||
functional.app_disable(session_browser, app_name)
|
||||
|
||||
|
||||
@when(parsers.parse('I enable the {app_name:w} application'))
|
||||
def enable_application(session_browser, app_name):
|
||||
functional.app_enable(session_browser, app_name)
|
||||
|
||||
|
||||
@when(parsers.parse('I disable the {app_name:w} application'))
|
||||
def disable_application(session_browser, app_name):
|
||||
functional.app_disable(session_browser, app_name)
|
||||
|
||||
|
||||
@given(parsers.parse('the {app_name:w} application can be disabled'))
|
||||
def app_can_be_disabled(session_browser, app_name):
|
||||
if not functional.app_can_be_disabled(session_browser, app_name):
|
||||
pytest.skip(f'network time application can\'t be disabled')
|
||||
|
||||
|
||||
@then(parsers.parse('the {service_name:w} service should be running'))
|
||||
def service_should_be_running(session_browser, service_name):
|
||||
assert functional.eventually(functional.service_is_running,
|
||||
args=[session_browser, service_name])
|
||||
|
||||
|
||||
@then(parsers.parse('the {service_name:w} service should not be running'))
|
||||
def service_should_not_be_running(session_browser, service_name):
|
||||
assert functional.eventually(functional.service_is_not_running,
|
||||
args=[session_browser, service_name])
|
||||
|
||||
|
||||
@then(parsers.parse('I should be prompted for login'))
|
||||
def prompted_for_login(session_browser):
|
||||
assert functional.is_login_prompt(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse('the domain name is set to {domain:S}'))
|
||||
def step_set_domain_name(session_browser, domain):
|
||||
functional.set_domain_name(session_browser, domain)
|
||||
|
||||
|
||||
@then(parsers.parse('the {site_name:w} site should be available'))
|
||||
def site_should_be_available(session_browser, site_name):
|
||||
assert functional.is_available(session_browser, site_name)
|
||||
|
||||
|
||||
@then(parsers.parse('the {site_name:w} site should not be available'))
|
||||
def site_should_not_be_available(session_browser, site_name):
|
||||
assert not functional.is_available(session_browser, site_name)
|
||||
|
||||
|
||||
@when(parsers.parse('I access {app_name:w} application'))
|
||||
def access_application(session_browser, app_name):
|
||||
functional.access_url(session_browser, app_name)
|
||||
|
||||
|
||||
@given('advanced mode is on')
|
||||
def advanced_mode_is_on(session_browser):
|
||||
functional.set_advanced_mode(session_browser, True)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I create a backup of the {app_name:w} app data with '
|
||||
'name {archive_name:w}'))
|
||||
def backup_create(session_browser, app_name, archive_name):
|
||||
functional.backup_create(session_browser, app_name, archive_name)
|
||||
|
||||
|
||||
@when(parsers.parse('I wait for {seconds} seconds'))
|
||||
def sleep_for(seconds):
|
||||
seconds = int(seconds)
|
||||
time.sleep(seconds)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse(
|
||||
'I restore the {app_name:w} app data backup with name {archive_name:w}'
|
||||
))
|
||||
def backup_restore(session_browser, app_name, archive_name):
|
||||
functional.backup_restore(session_browser, app_name, archive_name)
|
||||
|
||||
|
||||
@given(parsers.parse('the network device is in the {zone:w} firewall zone'))
|
||||
def networks_set_firewall_zone(session_browser, zone):
|
||||
functional.networks_set_firewall_zone(session_browser, zone)
|
||||
|
||||
|
||||
@given(
|
||||
parsers.parse('the domain name for {app_name:w} is set to {domain_name:S}')
|
||||
)
|
||||
def select_domain_name(session_browser, app_name, domain_name):
|
||||
functional.app_select_domain_name(session_browser, app_name, domain_name)
|
||||
|
||||
|
||||
@then(parsers.parse('{app_name:w} app should be visible on the front page'))
|
||||
def app_visible_on_front_page(session_browser, app_name):
|
||||
shortcuts = functional.find_on_front_page(session_browser, app_name)
|
||||
assert len(shortcuts) == 1
|
||||
|
||||
|
||||
@then(parsers.parse('{app_name:w} app should not be visible on the front page')
|
||||
)
|
||||
def app_not_visible_on_front_page(session_browser, app_name):
|
||||
shortcuts = functional.find_on_front_page(session_browser, app_name)
|
||||
assert len(shortcuts) == 0
|
||||
|
||||
|
||||
@given(parsers.parse('bind forwarders are set to {forwarders}'))
|
||||
def bind_given_set_forwarders(session_browser, forwarders):
|
||||
functional.set_forwarders(session_browser, forwarders)
|
||||
|
||||
|
||||
@when(parsers.parse('I set bind forwarders to {forwarders}'))
|
||||
def bind_set_forwarders(session_browser, forwarders):
|
||||
functional.set_forwarders(session_browser, forwarders)
|
||||
|
||||
|
||||
@then(parsers.parse('bind forwarders should be {forwarders}'))
|
||||
def bind_assert_forwarders(session_browser, forwarders):
|
||||
assert functional.get_forwarders(session_browser) == forwarders
|
||||
@ -1,639 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import pytest
|
||||
import splinter
|
||||
from pytest_bdd import given, parsers, then, when
|
||||
|
||||
from ..support import application
|
||||
|
||||
|
||||
@given(parsers.parse('the {app_name:w} application is installed'))
|
||||
def application_is_installed(session_browser, app_name):
|
||||
application.install(session_browser, app_name)
|
||||
assert (application.is_installed(session_browser, app_name))
|
||||
|
||||
|
||||
@given(parsers.parse('the {app_name:w} application is enabled'))
|
||||
def application_is_enabled(session_browser, app_name):
|
||||
application.enable(session_browser, app_name)
|
||||
|
||||
|
||||
@given(parsers.parse('the {app_name:w} application is disabled'))
|
||||
def application_is_disabled(session_browser, app_name):
|
||||
application.disable(session_browser, app_name)
|
||||
|
||||
|
||||
@given(parsers.parse('the network time application is enabled'))
|
||||
def ntp_is_enabled(session_browser):
|
||||
application.enable(session_browser, 'ntp')
|
||||
|
||||
|
||||
@given(parsers.parse('the network time application is disabled'))
|
||||
def ntp_is_disabled(session_browser):
|
||||
application.disable(session_browser, 'ntp')
|
||||
|
||||
|
||||
@given(parsers.parse('the network time application can be disabled'))
|
||||
def ntp_can_be_disabled(session_browser):
|
||||
if not application.can_be_disabled(session_browser, 'ntp'):
|
||||
pytest.skip(f'network time application can\'t be disabled')
|
||||
|
||||
|
||||
@when(parsers.parse('I set the time zone to {time_zone:S}'))
|
||||
def time_zone_set(session_browser, time_zone):
|
||||
application.time_zone_set(session_browser, time_zone)
|
||||
|
||||
|
||||
@then(parsers.parse('the time zone should be {time_zone:S}'))
|
||||
def time_zone_assert(session_browser, time_zone):
|
||||
assert time_zone == application.time_zone_get(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse('the service discovery application is enabled'))
|
||||
def avahi_is_enabled(session_browser):
|
||||
application.enable(session_browser, 'avahi')
|
||||
|
||||
|
||||
@given(parsers.parse('the service discovery application is disabled'))
|
||||
def avahi_is_disabled(session_browser):
|
||||
application.disable(session_browser, 'avahi')
|
||||
|
||||
|
||||
@when(parsers.parse('I enable the {app_name:w} application'))
|
||||
def enable_application(session_browser, app_name):
|
||||
application.enable(session_browser, app_name)
|
||||
|
||||
|
||||
@when(parsers.parse('I disable the {app_name:w} application'))
|
||||
def disable_application(session_browser, app_name):
|
||||
application.disable(session_browser, app_name)
|
||||
|
||||
|
||||
@when(parsers.parse('I enable the network time application'))
|
||||
def enable_ntp(session_browser):
|
||||
application.enable(session_browser, 'ntp')
|
||||
|
||||
|
||||
@when(parsers.parse('I disable the network time application'))
|
||||
def disable_ntp(session_browser):
|
||||
application.disable(session_browser, 'ntp')
|
||||
|
||||
|
||||
@when(parsers.parse('I enable the service discovery application'))
|
||||
def enable_avahi(session_browser):
|
||||
application.enable(session_browser, 'avahi')
|
||||
|
||||
|
||||
@when(parsers.parse('I disable the service discovery application'))
|
||||
def disable_avahi(session_browser):
|
||||
application.disable(session_browser, 'avahi')
|
||||
|
||||
|
||||
@given(
|
||||
parsers.parse('the domain name for {app_name:w} is set to {domain_name:S}')
|
||||
)
|
||||
def select_domain_name(session_browser, app_name, domain_name):
|
||||
application.select_domain_name(session_browser, app_name, domain_name)
|
||||
|
||||
|
||||
@given('the shadowsocks application is configured')
|
||||
def configure_shadowsocks(session_browser):
|
||||
application.configure_shadowsocks(session_browser, 'example.com',
|
||||
'fakepassword')
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I configure shadowsocks with server {server:S} and '
|
||||
'password {password:w}'))
|
||||
def configure_shadowsocks_with_details(session_browser, server, password):
|
||||
application.configure_shadowsocks(session_browser, server, password)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse('shadowsocks should be configured with server {server:S} '
|
||||
'and password {password:w}'))
|
||||
def assert_shadowsocks_configuration(session_browser, server, password):
|
||||
assert (
|
||||
server,
|
||||
password) == application.shadowsocks_get_configuration(session_browser)
|
||||
|
||||
|
||||
@when(parsers.parse('I modify the maximum file size of coquelicot to {size:d}')
|
||||
)
|
||||
def modify_max_file_size(session_browser, size):
|
||||
application.modify_max_file_size(session_browser, size)
|
||||
|
||||
|
||||
@then(parsers.parse('the maximum file size of coquelicot should be {size:d}'))
|
||||
def assert_max_file_size(session_browser, size):
|
||||
assert application.get_max_file_size(session_browser) == size
|
||||
|
||||
|
||||
@when(parsers.parse('I modify the coquelicot upload password to {password:w}'))
|
||||
def modify_upload_password(session_browser, password):
|
||||
application.modify_upload_password(session_browser, password)
|
||||
|
||||
|
||||
@given(parsers.parse('share {name:w} is not available'))
|
||||
def remove_share(session_browser, name):
|
||||
application.remove_share(session_browser, name)
|
||||
|
||||
|
||||
@when(parsers.parse('I add a share {name:w} from path {path} for {group:w}'))
|
||||
def add_share(session_browser, name, path, group):
|
||||
application.add_share(session_browser, name, path, group)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I edit share {old_name:w} to {new_name:w} from path {path} '
|
||||
'for {group:w}'))
|
||||
def edit_share(session_browser, old_name, new_name, path, group):
|
||||
application.edit_share(session_browser, old_name, new_name, path, group)
|
||||
|
||||
|
||||
@when(parsers.parse('I remove share {name:w}'))
|
||||
def remove_share2(session_browser, name):
|
||||
application.remove_share(session_browser, name)
|
||||
|
||||
|
||||
@when(parsers.parse('I edit share {name:w} to be public'))
|
||||
def edit_share_public_access(session_browser, name):
|
||||
application.make_share_public(session_browser, name)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'the share {name:w} should be listed from path {path} for {group:w}'))
|
||||
def verify_share(session_browser, name, path, group):
|
||||
application.verify_share(session_browser, name, path, group)
|
||||
|
||||
|
||||
@then(parsers.parse('the share {name:w} should not be listed'))
|
||||
def verify_invalid_share(session_browser, name):
|
||||
with pytest.raises(splinter.exceptions.ElementDoesNotExist):
|
||||
application.get_share(session_browser, name)
|
||||
|
||||
|
||||
@then(parsers.parse('the share {name:w} should be accessible'))
|
||||
def access_share(session_browser, name):
|
||||
application.access_share(session_browser, name)
|
||||
|
||||
|
||||
@then(parsers.parse('the share {name:w} should not exist'))
|
||||
def verify_nonexistant_share(session_browser, name):
|
||||
application.verify_nonexistant_share(session_browser, name)
|
||||
|
||||
|
||||
@then(parsers.parse('the share {name:w} should not be accessible'))
|
||||
def verify_inaccessible_share(session_browser, name):
|
||||
application.verify_inaccessible_share(session_browser, name)
|
||||
|
||||
|
||||
@when(parsers.parse('I enable mediawiki public registrations'))
|
||||
def enable_mediawiki_public_registrations(session_browser):
|
||||
application.enable_mediawiki_public_registrations(session_browser)
|
||||
|
||||
|
||||
@when(parsers.parse('I disable mediawiki public registrations'))
|
||||
def disable_mediawiki_public_registrations(session_browser):
|
||||
application.disable_mediawiki_public_registrations(session_browser)
|
||||
|
||||
|
||||
@when(parsers.parse('I enable mediawiki private mode'))
|
||||
def enable_mediawiki_private_mode(session_browser):
|
||||
application.enable_mediawiki_private_mode(session_browser)
|
||||
|
||||
|
||||
@when(parsers.parse('I disable mediawiki private mode'))
|
||||
def disable_mediawiki_private_mode(session_browser):
|
||||
application.disable_mediawiki_private_mode(session_browser)
|
||||
|
||||
|
||||
@when(parsers.parse('I set the mediawiki admin password to {password}'))
|
||||
def set_mediawiki_admin_password(session_browser, password):
|
||||
application.set_mediawiki_admin_password(session_browser, password)
|
||||
|
||||
|
||||
@when(parsers.parse('I enable message archive management'))
|
||||
def ejabberd_enable_archive_management(session_browser):
|
||||
application.enable_ejabberd_message_archive_management(session_browser)
|
||||
|
||||
|
||||
@when(parsers.parse('I disable message archive management'))
|
||||
def ejabberd_disable_archive_management(session_browser):
|
||||
application.disable_ejabberd_message_archive_management(session_browser)
|
||||
|
||||
|
||||
@when('there is an ikiwiki wiki')
|
||||
def ikiwiki_create_wiki_if_needed(session_browser):
|
||||
application.ikiwiki_create_wiki_if_needed(session_browser)
|
||||
|
||||
|
||||
@when('I delete the ikiwiki wiki')
|
||||
def ikiwiki_delete_wiki(session_browser):
|
||||
application.ikiwiki_delete_wiki(session_browser)
|
||||
|
||||
|
||||
@then('the ikiwiki wiki should be restored')
|
||||
def ikiwiki_should_exist(session_browser):
|
||||
assert application.ikiwiki_wiki_exists(session_browser)
|
||||
|
||||
|
||||
@given('I have added a contact to my roster')
|
||||
def ejabberd_add_contact(session_browser):
|
||||
application.ejabberd_add_contact(session_browser)
|
||||
|
||||
|
||||
@when('I delete the contact from my roster')
|
||||
def ejabberd_delete_contact(session_browser):
|
||||
application.ejabberd_delete_contact(session_browser)
|
||||
|
||||
|
||||
@then('I should have a contact on my roster')
|
||||
def ejabberd_should_have_contact(session_browser):
|
||||
assert application.ejabberd_has_contact(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse('tor relay is {enabled:w}'))
|
||||
def tor_given_relay_enable(session_browser, enabled):
|
||||
application.tor_feature_enable(session_browser, 'relay', enabled)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable:w} tor relay'))
|
||||
def tor_relay_enable(session_browser, enable):
|
||||
application.tor_feature_enable(session_browser, 'relay', enable)
|
||||
|
||||
|
||||
@then(parsers.parse('tor relay should be {enabled:w}'))
|
||||
def tor_assert_relay_enabled(session_browser, enabled):
|
||||
application.tor_assert_feature_enabled(session_browser, 'relay', enabled)
|
||||
|
||||
|
||||
@then(parsers.parse('tor {port_name:w} port should be displayed'))
|
||||
def tor_assert_port_displayed(session_browser, port_name):
|
||||
assert port_name in application.tor_get_relay_ports(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse('tor bridge relay is {enabled:w}'))
|
||||
def tor_given_bridge_relay_enable(session_browser, enabled):
|
||||
application.tor_feature_enable(session_browser, 'bridge-relay', enabled)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable:w} tor bridge relay'))
|
||||
def tor_bridge_relay_enable(session_browser, enable):
|
||||
application.tor_feature_enable(session_browser, 'bridge-relay', enable)
|
||||
|
||||
|
||||
@then(parsers.parse('tor bridge relay should be {enabled:w}'))
|
||||
def tor_assert_bridge_relay_enabled(session_browser, enabled):
|
||||
application.tor_assert_feature_enabled(session_browser, 'bridge-relay',
|
||||
enabled)
|
||||
|
||||
|
||||
@given(parsers.parse('tor hidden services are {enabled:w}'))
|
||||
def tor_given_hidden_services_enable(session_browser, enabled):
|
||||
application.tor_feature_enable(session_browser, 'hidden-services', enabled)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable:w} tor hidden services'))
|
||||
def tor_hidden_services_enable(session_browser, enable):
|
||||
application.tor_feature_enable(session_browser, 'hidden-services', enable)
|
||||
|
||||
|
||||
@then(parsers.parse('tor hidden services should be {enabled:w}'))
|
||||
def tor_assert_hidden_services_enabled(session_browser, enabled):
|
||||
application.tor_assert_feature_enabled(session_browser, 'hidden-services',
|
||||
enabled)
|
||||
|
||||
|
||||
@then(parsers.parse('tor hidden services information should be displayed'))
|
||||
def tor_assert_hidden_services(session_browser):
|
||||
application.tor_assert_hidden_services(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse('download software packages over tor is {enabled:w}'))
|
||||
def tor_given_download_software_over_tor_enable(session_browser, enabled):
|
||||
application.tor_feature_enable(session_browser, 'software', enabled)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable:w} download software packages over tor'))
|
||||
def tor_download_software_over_tor_enable(session_browser, enable):
|
||||
application.tor_feature_enable(session_browser, 'software', enable)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse('download software packages over tor should be {enabled:w}'))
|
||||
def tor_assert_download_software_over_tor(session_browser, enabled):
|
||||
application.tor_assert_feature_enabled(session_browser, 'software',
|
||||
enabled)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'{domain:S} should be a tahoe {introducer_type:w} introducer'))
|
||||
def tahoe_assert_introducer(session_browser, domain, introducer_type):
|
||||
assert application.tahoe_get_introducer(session_browser, domain,
|
||||
introducer_type)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'{domain:S} should not be a tahoe {introducer_type:w} introducer'))
|
||||
def tahoe_assert_not_introducer(session_browser, domain, introducer_type):
|
||||
assert not application.tahoe_get_introducer(session_browser, domain,
|
||||
introducer_type)
|
||||
|
||||
|
||||
@given(parsers.parse('{domain:S} is not a tahoe introducer'))
|
||||
def tahoe_given_remove_introducer(session_browser, domain):
|
||||
if application.tahoe_get_introducer(session_browser, domain, 'connected'):
|
||||
application.tahoe_remove_introducer(session_browser, domain)
|
||||
|
||||
|
||||
@when(parsers.parse('I add {domain:S} as a tahoe introducer'))
|
||||
def tahoe_add_introducer(session_browser, domain):
|
||||
application.tahoe_add_introducer(session_browser, domain)
|
||||
|
||||
|
||||
@given(parsers.parse('{domain:S} is a tahoe introducer'))
|
||||
def tahoe_given_add_introducer(session_browser, domain):
|
||||
if not application.tahoe_get_introducer(session_browser, domain,
|
||||
'connected'):
|
||||
application.tahoe_add_introducer(session_browser, domain)
|
||||
|
||||
|
||||
@when(parsers.parse('I remove {domain:S} as a tahoe introducer'))
|
||||
def tahoe_remove_introducer(session_browser, domain):
|
||||
application.tahoe_remove_introducer(session_browser, domain)
|
||||
|
||||
|
||||
@given('the access rights are set to "only the owner can view or make changes"'
|
||||
)
|
||||
def radicale_given_owner_only(session_browser):
|
||||
application.radicale_set_access_rights(session_browser, 'owner_only')
|
||||
|
||||
|
||||
@given('the access rights are set to "any user can view, but only the '
|
||||
'owner can make changes"')
|
||||
def radicale_given_owner_write(session_browser):
|
||||
application.radicale_set_access_rights(session_browser, 'owner_write')
|
||||
|
||||
|
||||
@given('the access rights are set to "any user can view or make changes"')
|
||||
def radicale_given_authenticated(session_browser):
|
||||
application.radicale_set_access_rights(session_browser, 'authenticated')
|
||||
|
||||
|
||||
@when('I change the access rights to "only the owner can view or make changes"'
|
||||
)
|
||||
def radicale_set_owner_only(session_browser):
|
||||
application.radicale_set_access_rights(session_browser, 'owner_only')
|
||||
|
||||
|
||||
@when('I change the access rights to "any user can view, but only the '
|
||||
'owner can make changes"')
|
||||
def radicale_set_owner_write(session_browser):
|
||||
application.radicale_set_access_rights(session_browser, 'owner_write')
|
||||
|
||||
|
||||
@when('I change the access rights to "any user can view or make changes"')
|
||||
def radicale_set_authenticated(session_browser):
|
||||
application.radicale_set_access_rights(session_browser, 'authenticated')
|
||||
|
||||
|
||||
@then('the access rights should be "only the owner can view or make changes"')
|
||||
def radicale_check_owner_only(session_browser):
|
||||
assert application.radicale_get_access_rights(
|
||||
session_browser) == 'owner_only'
|
||||
|
||||
|
||||
@then('the access rights should be "any user can view, but only the '
|
||||
'owner can make changes"')
|
||||
def radicale_check_owner_write(session_browser):
|
||||
assert application.radicale_get_access_rights(
|
||||
session_browser) == 'owner_write'
|
||||
|
||||
|
||||
@then('the access rights should be "any user can view or make changes"')
|
||||
def radicale_check_authenticated(session_browser):
|
||||
assert application.radicale_get_access_rights(
|
||||
session_browser) == 'authenticated'
|
||||
|
||||
|
||||
@given(parsers.parse('the openvpn application is setup'))
|
||||
def openvpn_setup(session_browser):
|
||||
application.openvpn_setup(session_browser)
|
||||
|
||||
|
||||
@given('I download openvpn profile')
|
||||
def openvpn_download_profile(session_browser):
|
||||
return application.openvpn_download_profile(session_browser)
|
||||
|
||||
|
||||
@then('the openvpn profile should be downloadable')
|
||||
def openvpn_profile_downloadable(session_browser):
|
||||
application.openvpn_download_profile(session_browser)
|
||||
|
||||
|
||||
@then('the openvpn profile downloaded should be same as before')
|
||||
def openvpn_profile_download_compare(session_browser,
|
||||
openvpn_download_profile):
|
||||
new_profile = application.openvpn_download_profile(session_browser)
|
||||
assert openvpn_download_profile == new_profile
|
||||
|
||||
|
||||
@given('public access is enabled in searx')
|
||||
def searx_public_access_enabled(session_browser):
|
||||
application.searx_enable_public_access(session_browser)
|
||||
|
||||
|
||||
@when('I enable public access in searx')
|
||||
def searx_enable_public_access(session_browser):
|
||||
application.searx_enable_public_access(session_browser)
|
||||
|
||||
|
||||
@when('I disable public access in searx')
|
||||
def searx_disable_public_access(session_browser):
|
||||
application.searx_disable_public_access(session_browser)
|
||||
|
||||
|
||||
@then(parsers.parse('{app_name:w} app should be visible on the front page'))
|
||||
def app_visible_on_front_page(session_browser, app_name):
|
||||
shortcuts = application.find_on_front_page(session_browser, app_name)
|
||||
assert len(shortcuts) == 1
|
||||
|
||||
|
||||
@then(parsers.parse('{app_name:w} app should not be visible on the front page')
|
||||
)
|
||||
def app_not_visible_on_front_page(session_browser, app_name):
|
||||
shortcuts = application.find_on_front_page(session_browser, app_name)
|
||||
assert len(shortcuts) == 0
|
||||
|
||||
|
||||
@given('a public repository')
|
||||
@given('a repository')
|
||||
@given('at least one repository exists')
|
||||
def gitweb_repo(session_browser):
|
||||
application.gitweb_create_repo(session_browser, 'Test-repo', 'public',
|
||||
True)
|
||||
|
||||
|
||||
@given('a private repository')
|
||||
def gitweb_private_repo(session_browser):
|
||||
application.gitweb_create_repo(session_browser, 'Test-repo', 'private',
|
||||
True)
|
||||
|
||||
|
||||
@given('both public and private repositories exist')
|
||||
def gitweb_public_and_private_repo(session_browser):
|
||||
application.gitweb_create_repo(session_browser, 'Test-repo', 'public',
|
||||
True)
|
||||
application.gitweb_create_repo(session_browser, 'Test-repo2', 'private',
|
||||
True)
|
||||
|
||||
|
||||
@given(parsers.parse("a {access:w} repository that doesn't exist"))
|
||||
def gitweb_nonexistent_repo(session_browser, access):
|
||||
application.gitweb_delete_repo(session_browser, 'Test-repo',
|
||||
ignore_missing=True)
|
||||
return dict(access=access)
|
||||
|
||||
|
||||
@given('all repositories are private')
|
||||
def gitweb_all_repositories_private(session_browser):
|
||||
application.gitweb_set_all_repos_private(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse('a repository metadata:\n{metadata}'))
|
||||
def gitweb_repo_metadata(session_browser, metadata):
|
||||
metadata_dict = {}
|
||||
for item in metadata.split('\n'):
|
||||
item = item.split(': ')
|
||||
metadata_dict[item[0]] = item[1]
|
||||
return metadata_dict
|
||||
|
||||
|
||||
@when('I create the repository')
|
||||
def gitweb_create_repo(session_browser, access):
|
||||
application.gitweb_create_repo(session_browser, 'Test-repo', access)
|
||||
|
||||
|
||||
@when('I delete the repository')
|
||||
def gitweb_delete_repo(session_browser):
|
||||
application.gitweb_delete_repo(session_browser, 'Test-repo')
|
||||
|
||||
|
||||
@when('I set the metadata of the repository')
|
||||
def gitweb_edit_repo_metadata(session_browser, gitweb_repo_metadata):
|
||||
application.gitweb_edit_repo_metadata(session_browser, 'Test-repo',
|
||||
gitweb_repo_metadata)
|
||||
|
||||
|
||||
@when('using a git client')
|
||||
def gitweb_using_git_client():
|
||||
pass
|
||||
|
||||
|
||||
@then('the repository should be restored')
|
||||
@then('the repository should be listed as a public')
|
||||
def gitweb_repo_should_exists(session_browser):
|
||||
assert application.gitweb_repo_exists(session_browser, 'Test-repo',
|
||||
access='public')
|
||||
|
||||
|
||||
@then('the repository should be listed as a private')
|
||||
def gitweb_private_repo_should_exists(session_browser):
|
||||
assert application.gitweb_repo_exists(session_browser, 'Test-repo',
|
||||
'private')
|
||||
|
||||
|
||||
@then('the repository should not be listed')
|
||||
def gitweb_repo_should_not_exist(session_browser, gitweb_repo):
|
||||
assert not application.gitweb_repo_exists(session_browser, gitweb_repo)
|
||||
|
||||
|
||||
@then('the public repository should be listed on gitweb')
|
||||
@then('the repository should be listed on gitweb')
|
||||
def gitweb_repo_should_exist_on_gitweb(session_browser):
|
||||
assert application.gitweb_site_repo_exists(session_browser, 'Test-repo')
|
||||
|
||||
|
||||
@then('the private repository should not be listed on gitweb')
|
||||
def gitweb_private_repo_should_exists_on_gitweb(session_browser):
|
||||
assert not application.gitweb_site_repo_exists(session_browser,
|
||||
'Test-repo2')
|
||||
|
||||
|
||||
@then('the metadata of the repository should be as set')
|
||||
def gitweb_repo_metadata_should_match(session_browser, gitweb_repo_metadata):
|
||||
actual_metadata = application.gitweb_get_repo_metadata(
|
||||
session_browser, 'Test-repo')
|
||||
assert all(item in actual_metadata.items()
|
||||
for item in gitweb_repo_metadata.items())
|
||||
|
||||
|
||||
@then('the repository should be publicly readable')
|
||||
def gitweb_repo_publicly_readable():
|
||||
assert application.gitweb_repo_is_readable('Test-repo')
|
||||
assert application.gitweb_repo_is_readable('Test-repo',
|
||||
url_git_extension=True)
|
||||
|
||||
|
||||
@then('the repository should not be publicly readable')
|
||||
def gitweb_repo_not_publicly_readable():
|
||||
assert not application.gitweb_repo_is_readable('Test-repo')
|
||||
assert not application.gitweb_repo_is_readable('Test-repo',
|
||||
url_git_extension=True)
|
||||
|
||||
|
||||
@then('the repository should not be publicly writable')
|
||||
def gitweb_repo_not_publicly_writable():
|
||||
assert not application.gitweb_repo_is_writable('Test-repo')
|
||||
assert not application.gitweb_repo_is_writable('Test-repo',
|
||||
url_git_extension=True)
|
||||
|
||||
|
||||
@then('the repository should be privately readable')
|
||||
def gitweb_repo_privately_readable():
|
||||
assert application.gitweb_repo_is_readable('Test-repo', with_auth=True)
|
||||
assert application.gitweb_repo_is_readable('Test-repo', with_auth=True,
|
||||
url_git_extension=True)
|
||||
|
||||
|
||||
@then('the repository should be privately writable')
|
||||
def gitweb_repo_privately_writable():
|
||||
assert application.gitweb_repo_is_writable('Test-repo', with_auth=True)
|
||||
assert application.gitweb_repo_is_writable('Test-repo', with_auth=True,
|
||||
url_git_extension=True)
|
||||
|
||||
|
||||
@when(parsers.parse('I {task:w} the {share_type:w} samba share'))
|
||||
def samba_enable_share(session_browser, task, share_type):
|
||||
if task == 'enable':
|
||||
application.samba_set_share(session_browser, share_type,
|
||||
status='enabled')
|
||||
elif task == 'disable':
|
||||
application.samba_set_share(session_browser, share_type,
|
||||
status='disabled')
|
||||
|
||||
|
||||
@then(parsers.parse('I can write to the {share_type:w} samba share'))
|
||||
def samba_share_should_be_writable(share_type):
|
||||
application.samba_assert_share_is_writable(share_type)
|
||||
|
||||
|
||||
@then(parsers.parse('a guest user can write to the {share_type:w} samba share')
|
||||
)
|
||||
def samba_share_should_be_writable_to_guest(share_type):
|
||||
application.samba_assert_share_is_writable(share_type, as_guest=True)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse('a guest user can\'t access the {share_type:w} samba share'))
|
||||
def samba_share_should_not_be_accessible_to_guest(share_type):
|
||||
application.samba_assert_share_is_not_accessible(share_type, as_guest=True)
|
||||
|
||||
|
||||
@then(parsers.parse('the {share_type:w} samba share should not be available'))
|
||||
def samba_share_should_not_be_available(share_type):
|
||||
application.samba_assert_share_is_not_available(share_type)
|
||||
@ -1,90 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
from pytest_bdd import given, parsers, then, when
|
||||
|
||||
from ..support import config, interface
|
||||
|
||||
default_url = config['DEFAULT']['url']
|
||||
|
||||
|
||||
@given("I'm a logged in user")
|
||||
def logged_in_user(session_browser):
|
||||
interface.login(session_browser, default_url,
|
||||
config['DEFAULT']['username'],
|
||||
config['DEFAULT']['password'])
|
||||
|
||||
|
||||
@given("I'm a logged out user")
|
||||
def logged_out_user(session_browser):
|
||||
session_browser.visit(default_url + '/plinth/accounts/logout/')
|
||||
|
||||
|
||||
@when("I log out")
|
||||
def log_out_user(session_browser):
|
||||
session_browser.visit(default_url + '/plinth/accounts/logout/')
|
||||
|
||||
|
||||
@then(parsers.parse('I should be prompted for login'))
|
||||
def prompted_for_login(session_browser):
|
||||
assert interface.is_login_prompt(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse("the user {name:w} doesn't exist"))
|
||||
def new_user_does_not_exist(session_browser, name):
|
||||
interface.delete_user(session_browser, name)
|
||||
|
||||
|
||||
@given(parsers.parse('the user {name:w} exists'))
|
||||
def test_user_exists(session_browser, name):
|
||||
interface.nav_to_module(session_browser, 'users')
|
||||
user_link = session_browser.find_link_by_href('/plinth/sys/users/' + name +
|
||||
'/edit/')
|
||||
if not user_link:
|
||||
create_user(session_browser, name, 'secret123')
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I create a user named {name:w} with password {password:w}'))
|
||||
def create_user(session_browser, name, password):
|
||||
interface.create_user(session_browser, name, password)
|
||||
|
||||
|
||||
@when(parsers.parse('I rename the user {old_name:w} to {new_name:w}'))
|
||||
def rename_user(session_browser, old_name, new_name):
|
||||
interface.rename_user(session_browser, old_name, new_name)
|
||||
|
||||
|
||||
@when(parsers.parse('I delete the user {name:w}'))
|
||||
def delete_user(session_browser, name):
|
||||
interface.delete_user(session_browser, name)
|
||||
|
||||
|
||||
@then(parsers.parse('{name:w} should be listed as a user'))
|
||||
def new_user_is_listed(session_browser, name):
|
||||
assert interface.is_user(session_browser, name)
|
||||
|
||||
|
||||
@then(parsers.parse('{name:w} should not be listed as a user'))
|
||||
def new_user_is_not_listed(session_browser, name):
|
||||
assert not interface.is_user(session_browser, name)
|
||||
|
||||
|
||||
@given('a sample local file')
|
||||
def sample_local_file():
|
||||
file_path, contents = interface.create_sample_local_file()
|
||||
return dict(file_path=file_path, contents=contents)
|
||||
|
||||
|
||||
@when('I go to the status logs page')
|
||||
def help_go_to_status_logs(session_browser):
|
||||
interface.go_to_status_logs(session_browser)
|
||||
|
||||
|
||||
@then('status logs should be shown')
|
||||
def help_status_logs_are_shown(session_browser):
|
||||
assert interface.are_status_logs_shown(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse("I'm on the {name:w} page"))
|
||||
def go_to_module(session_browser, name):
|
||||
interface.nav_to_module(session_browser, name)
|
||||
@ -1,37 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
from pytest_bdd import parsers, then
|
||||
|
||||
from ..support import service
|
||||
from ..support.service import eventually
|
||||
|
||||
|
||||
@then(parsers.parse('the {service_name:w} service should be running'))
|
||||
def service_should_be_running(session_browser, service_name):
|
||||
assert eventually(service.is_running, args=[session_browser, service_name])
|
||||
|
||||
|
||||
@then(parsers.parse('the {service_name:w} service should not be running'))
|
||||
def service_should_not_be_running(session_browser, service_name):
|
||||
assert eventually(service.is_not_running,
|
||||
args=[session_browser, service_name])
|
||||
|
||||
|
||||
@then(parsers.parse('the network time service should be running'))
|
||||
def ntp_should_be_running(session_browser):
|
||||
assert service.is_running(session_browser, 'ntp')
|
||||
|
||||
|
||||
@then(parsers.parse('the network time service should not be running'))
|
||||
def ntp_should_not_be_running(session_browser):
|
||||
assert not service.is_running(session_browser, 'ntp')
|
||||
|
||||
|
||||
@then(parsers.parse('the service discovery service should be running'))
|
||||
def avahi_should_be_running(session_browser):
|
||||
assert service.is_running(session_browser, 'avahi')
|
||||
|
||||
|
||||
@then(parsers.parse('the service discovery service should not be running'))
|
||||
def avahi_should_not_be_running(session_browser):
|
||||
assert not service.is_running(session_browser, 'avahi')
|
||||
@ -1,234 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
from pytest_bdd import given, parsers, then, when
|
||||
|
||||
from ..support import interface, site
|
||||
|
||||
|
||||
@then(parsers.parse('the {site_name:w} site should be available'))
|
||||
def site_should_be_available(session_browser, site_name):
|
||||
assert site.is_available(session_browser, site_name)
|
||||
|
||||
|
||||
@then(parsers.parse('the {site_name:w} site should not be available'))
|
||||
def site_should_not_be_available(session_browser, site_name):
|
||||
assert not site.is_available(session_browser, site_name)
|
||||
|
||||
|
||||
@when(parsers.parse('I access {app_name:w} application'))
|
||||
def access_application(session_browser, app_name):
|
||||
site.access_url(session_browser, app_name)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse(
|
||||
'I upload an image named {image:S} to mediawiki with credentials '
|
||||
'{username:w} and {password:w}'))
|
||||
def upload_image(session_browser, username, password, image):
|
||||
site.upload_image_mediawiki(session_browser, username, password, image)
|
||||
|
||||
|
||||
@then(parsers.parse('there should be {image:S} image'))
|
||||
def uploaded_image_should_be_available(session_browser, image):
|
||||
uploaded_image = site.get_uploaded_image_in_mediawiki(
|
||||
session_browser, image)
|
||||
assert image.lower() == uploaded_image.lower()
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'I should be able to login to coquelicot with password {password:w}'))
|
||||
def verify_upload_password(session_browser, password):
|
||||
site.verify_coquelicot_upload_password(session_browser, password)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I upload the sample local file to coquelicot with password '
|
||||
'{password:w}'))
|
||||
def coquelicot_upload_file(session_browser, sample_local_file, password):
|
||||
url = site.upload_file_to_coquelicot(session_browser,
|
||||
sample_local_file['file_path'],
|
||||
password)
|
||||
sample_local_file['upload_url'] = url
|
||||
|
||||
|
||||
@when('I download the uploaded file from coquelicot')
|
||||
def coquelicot_download_file(sample_local_file):
|
||||
file_path = interface.download_file(sample_local_file['upload_url'])
|
||||
sample_local_file['download_path'] = file_path
|
||||
|
||||
|
||||
@then('contents of downloaded sample file should be same as sample local file')
|
||||
def coquelicot_compare_upload_download_files(sample_local_file):
|
||||
interface.compare_files(sample_local_file['file_path'],
|
||||
sample_local_file['download_path'])
|
||||
|
||||
|
||||
@then(parsers.parse('the mediawiki site should allow creating accounts'))
|
||||
def mediawiki_allows_creating_accounts(session_browser):
|
||||
site.verify_mediawiki_create_account_link(session_browser)
|
||||
|
||||
|
||||
@then(parsers.parse('the mediawiki site should not allow creating accounts'))
|
||||
def mediawiki_does_not_allow_creating_accounts(session_browser):
|
||||
site.verify_mediawiki_no_create_account_link(session_browser)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse('the mediawiki site should allow anonymous reads and writes')
|
||||
)
|
||||
def mediawiki_allows_anonymous_reads_edits(session_browser):
|
||||
site.verify_mediawiki_anonymous_reads_edits_link(session_browser)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'the mediawiki site should not allow anonymous reads and writes'))
|
||||
def mediawiki_does_not_allow__account_creation_anonymous_reads_edits(
|
||||
session_browser):
|
||||
site.verify_mediawiki_no_anonymous_reads_edits_link(session_browser)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'I should see the Upload File option in the side pane when logged in '
|
||||
'with credentials {username:w} and {password:w}'))
|
||||
def login_to_mediawiki_with_credentials(session_browser, username, password):
|
||||
site.login_to_mediawiki_with_credentials(session_browser, username,
|
||||
password)
|
||||
|
||||
|
||||
@when('I delete the mediawiki main page')
|
||||
def mediawiki_delete_main_page(session_browser):
|
||||
site.mediawiki_delete_main_page(session_browser)
|
||||
|
||||
|
||||
@then('the mediawiki main page should be restored')
|
||||
def mediawiki_verify_text(session_browser):
|
||||
assert site.mediawiki_has_main_page(session_browser)
|
||||
|
||||
|
||||
@when('all ed2k files are removed from mldonkey')
|
||||
def mldonkey_remove_all_ed2k_files(session_browser):
|
||||
site.mldonkey_remove_all_ed2k_files(session_browser)
|
||||
|
||||
|
||||
@when('I upload a sample ed2k file to mldonkey')
|
||||
def mldonkey_upload_sample_ed2k_file(session_browser):
|
||||
site.mldonkey_upload_sample_ed2k_file(session_browser)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'there should be {ed2k_files_number:d} ed2k files listed in mldonkey'))
|
||||
def mldonkey_assert_number_of_ed2k_files(session_browser, ed2k_files_number):
|
||||
assert ed2k_files_number == site.mldonkey_get_number_of_ed2k_files(
|
||||
session_browser)
|
||||
|
||||
|
||||
@when('all torrents are removed from transmission')
|
||||
def transmission_remove_all_torrents(session_browser):
|
||||
site.transmission_remove_all_torrents(session_browser)
|
||||
|
||||
|
||||
@when('I upload a sample torrent to transmission')
|
||||
def transmission_upload_sample_torrent(session_browser):
|
||||
site.transmission_upload_sample_torrent(session_browser)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'there should be {torrents_number:d} torrents listed in transmission'))
|
||||
def transmission_assert_number_of_torrents(session_browser, torrents_number):
|
||||
assert torrents_number == site.transmission_get_number_of_torrents(
|
||||
session_browser)
|
||||
|
||||
|
||||
@when('all torrents are removed from deluge')
|
||||
def deluge_remove_all_torrents(session_browser):
|
||||
site.deluge_remove_all_torrents(session_browser)
|
||||
|
||||
|
||||
@when('I upload a sample torrent to deluge')
|
||||
def deluge_upload_sample_torrent(session_browser):
|
||||
site.deluge_upload_sample_torrent(session_browser)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'there should be {torrents_number:d} torrents listed in deluge'))
|
||||
def deluge_assert_number_of_torrents(session_browser, torrents_number):
|
||||
assert torrents_number == site.deluge_get_number_of_torrents(
|
||||
session_browser)
|
||||
|
||||
|
||||
@then('the calendar should be available')
|
||||
def assert_calendar_is_available(session_browser):
|
||||
assert site.calendar_is_available(session_browser)
|
||||
|
||||
|
||||
@then('the calendar should not be available')
|
||||
def assert_calendar_is_not_available(session_browser):
|
||||
assert not site.calendar_is_available(session_browser)
|
||||
|
||||
|
||||
@then('the addressbook should be available')
|
||||
def assert_addressbook_is_available(session_browser):
|
||||
assert site.addressbook_is_available(session_browser)
|
||||
|
||||
|
||||
@then('the addressbook should not be available')
|
||||
def assert_addressbook_is_not_available(session_browser):
|
||||
assert not site.addressbook_is_available(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse('syncthing folder {folder_name:w} is not present'))
|
||||
def syncthing_folder_not_present(session_browser, folder_name):
|
||||
if site.syncthing_folder_is_present(session_browser, folder_name):
|
||||
site.syncthing_remove_folder(session_browser, folder_name)
|
||||
|
||||
|
||||
@given(
|
||||
parsers.parse(
|
||||
'folder {folder_path:S} is present as syncthing folder {folder_name:w}'
|
||||
))
|
||||
def syncthing_folder_present(session_browser, folder_name, folder_path):
|
||||
if not site.syncthing_folder_is_present(session_browser, folder_name):
|
||||
site.syncthing_add_folder(session_browser, folder_name, folder_path)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse(
|
||||
'I add a folder {folder_path:S} as syncthing folder {folder_name:w}'))
|
||||
def syncthing_add_folder(session_browser, folder_name, folder_path):
|
||||
site.syncthing_add_folder(session_browser, folder_name, folder_path)
|
||||
|
||||
|
||||
@when(parsers.parse('I remove syncthing folder {folder_name:w}'))
|
||||
def syncthing_remove_folder(session_browser, folder_name):
|
||||
site.syncthing_remove_folder(session_browser, folder_name)
|
||||
|
||||
|
||||
@then(parsers.parse('syncthing folder {folder_name:w} should be present'))
|
||||
def syncthing_assert_folder_present(session_browser, folder_name):
|
||||
assert site.syncthing_folder_is_present(session_browser, folder_name)
|
||||
|
||||
|
||||
@then(parsers.parse('syncthing folder {folder_name:w} should not be present'))
|
||||
def syncthing_assert_folder_not_present(session_browser, folder_name):
|
||||
assert not site.syncthing_folder_is_present(session_browser, folder_name)
|
||||
|
||||
|
||||
@given('I subscribe to a feed in ttrss')
|
||||
def ttrss_subscribe(session_browser):
|
||||
site.ttrss_subscribe(session_browser)
|
||||
|
||||
|
||||
@when('I unsubscribe from the feed in ttrss')
|
||||
def ttrss_unsubscribe(session_browser):
|
||||
site.ttrss_unsubscribe(session_browser)
|
||||
|
||||
|
||||
@then('I should be subscribed to the feed in ttrss')
|
||||
def ttrss_assert_subscribed(session_browser):
|
||||
assert site.ttrss_is_subscribed(session_browser)
|
||||
@ -1,341 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from pytest import fixture
|
||||
from pytest_bdd import given, parsers, then, when
|
||||
|
||||
from ..support import system
|
||||
|
||||
language_codes = {
|
||||
'Deutsch': 'de',
|
||||
'Nederlands': 'nl',
|
||||
'Português': 'pt',
|
||||
'Türkçe': 'tr',
|
||||
'dansk': 'da',
|
||||
'español': 'es',
|
||||
'français': 'fr',
|
||||
'norsk (bokmål)': 'nb',
|
||||
'polski': 'pl',
|
||||
'svenska': 'sv',
|
||||
'Русский': 'ru',
|
||||
'తెలుగు': 'te',
|
||||
'简体中文': 'zh-hans'
|
||||
}
|
||||
|
||||
|
||||
@fixture(scope='session')
|
||||
def downloaded_file_info():
|
||||
return dict()
|
||||
|
||||
|
||||
@given(parsers.parse('the home page is {app_name:w}'))
|
||||
def set_home_page(session_browser, app_name):
|
||||
system.set_home_page(session_browser, app_name)
|
||||
|
||||
|
||||
@given(parsers.parse('the domain name is set to {domain:S}'))
|
||||
def set_domain_name(session_browser, domain):
|
||||
system.set_domain_name(session_browser, domain)
|
||||
|
||||
|
||||
@given('advanced mode is on')
|
||||
def advanced_mode_is_on(session_browser):
|
||||
system.set_advanced_mode(session_browser, True)
|
||||
|
||||
|
||||
@when(parsers.parse('I change the hostname to {hostname:w}'))
|
||||
def change_hostname_to(session_browser, hostname):
|
||||
system.set_hostname(session_browser, hostname)
|
||||
|
||||
|
||||
@when(parsers.parse('I change the domain name to {domain:S}'))
|
||||
def change_domain_name_to(session_browser, domain):
|
||||
system.set_domain_name(session_browser, domain)
|
||||
|
||||
|
||||
@when(parsers.parse('I change the home page to {app_name:w}'))
|
||||
def change_home_page_to(session_browser, app_name):
|
||||
system.set_home_page(session_browser, app_name)
|
||||
|
||||
|
||||
@when('I change the language to <language>')
|
||||
def change_language(session_browser, language):
|
||||
system.set_language(session_browser, language_codes[language])
|
||||
|
||||
|
||||
@then(parsers.parse('the hostname should be {hostname:w}'))
|
||||
def hostname_should_be(session_browser, hostname):
|
||||
assert system.get_hostname(session_browser) == hostname
|
||||
|
||||
|
||||
@then(parsers.parse('the domain name should be {domain:S}'))
|
||||
def domain_name_should_be(session_browser, domain):
|
||||
assert system.get_domain_name(session_browser) == domain
|
||||
|
||||
|
||||
@then('Plinth language should be <language>')
|
||||
def plinth_language_should_be(session_browser, language):
|
||||
assert system.check_language(session_browser, language_codes[language])
|
||||
|
||||
|
||||
@given('the list of snapshots is empty')
|
||||
def empty_snapshots_list(session_browser):
|
||||
system.delete_all_snapshots(session_browser)
|
||||
|
||||
|
||||
@when('I manually create a snapshot')
|
||||
def create_snapshot(session_browser):
|
||||
system.create_snapshot(session_browser)
|
||||
|
||||
|
||||
@then(parsers.parse('there should be {count:d} snapshot in the list'))
|
||||
def verify_snapshot_count(session_browser, count):
|
||||
num_snapshots = system.get_snapshot_count(session_browser)
|
||||
assert num_snapshots == count
|
||||
|
||||
|
||||
@given(
|
||||
parsers.parse(
|
||||
'snapshots are configured with free space {free_space:d}, timeline '
|
||||
'snapshots {timeline_enabled:w}, software snapshots '
|
||||
'{software_enabled:w}, hourly limit {hourly:d}, daily limit {daily:d}'
|
||||
', weekly limit {weekly:d}, monthly limit {monthly:d}, yearly limit '
|
||||
'{yearly:d}'))
|
||||
def snapshot_given_set_configuration(session_browser, free_space,
|
||||
timeline_enabled, software_enabled,
|
||||
hourly, daily, weekly, monthly, yearly):
|
||||
timeline_enabled = (timeline_enabled == 'enabled')
|
||||
software_enabled = (software_enabled == 'enabled')
|
||||
system.snapshot_set_configuration(session_browser, free_space,
|
||||
timeline_enabled, software_enabled,
|
||||
hourly, daily, weekly, monthly, yearly)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse(
|
||||
'I configure snapshots with free space {free_space:d}, '
|
||||
'timeline snapshots {timeline_enabled:w}, '
|
||||
'software snapshots {software_enabled:w}, hourly limit {hourly:d}, '
|
||||
'daily limit {daily:d}, weekly limit {weekly:d}, monthly limit '
|
||||
'{monthly:d}, yearly limit {yearly:d}'))
|
||||
def snapshot_set_configuration(session_browser, free_space, timeline_enabled,
|
||||
software_enabled, hourly, daily, weekly,
|
||||
monthly, yearly):
|
||||
timeline_enabled = (timeline_enabled == 'enabled')
|
||||
software_enabled = (software_enabled == 'enabled')
|
||||
system.snapshot_set_configuration(session_browser, free_space,
|
||||
timeline_enabled, software_enabled,
|
||||
hourly, daily, weekly, monthly, yearly)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'snapshots should be configured with free space {free_space:d}, '
|
||||
'timeline snapshots {timeline_enabled:w}, software snapshots '
|
||||
'{software_enabled:w}, hourly limit {hourly:d}, daily limit '
|
||||
'{daily:d}, weekly limit {weekly:d}, monthly limit {monthly:d}, '
|
||||
'yearly limit {yearly:d}'))
|
||||
def snapshot_assert_configuration(session_browser, free_space,
|
||||
timeline_enabled, software_enabled, hourly,
|
||||
daily, weekly, monthly, yearly):
|
||||
timeline_enabled = (timeline_enabled == 'enabled')
|
||||
software_enabled = (software_enabled == 'enabled')
|
||||
assert (free_space, timeline_enabled, software_enabled, hourly, daily,
|
||||
weekly, monthly,
|
||||
yearly) == system.snapshot_get_configuration(session_browser)
|
||||
|
||||
|
||||
@then(parsers.parse('the home page should be {app_name:w}'))
|
||||
def home_page_should_be(session_browser, app_name):
|
||||
assert system.check_home_page_redirect(session_browser, app_name)
|
||||
|
||||
|
||||
@given('dynamicdns is configured')
|
||||
def dynamicdns_configure(session_browser):
|
||||
system.dynamicdns_configure(session_browser)
|
||||
|
||||
|
||||
@when('I change the dynamicdns configuration')
|
||||
def dynamicdns_change_config(session_browser):
|
||||
system.dynamicdns_change_config(session_browser)
|
||||
|
||||
|
||||
@then('dynamicdns should have the original configuration')
|
||||
def dynamicdns_has_original_config(session_browser):
|
||||
assert system.dynamicdns_has_original_config(session_browser)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I create a backup of the {app_name:w} app data with '
|
||||
'name {archive_name:w}'))
|
||||
def backup_create(session_browser, app_name, archive_name):
|
||||
system.backup_create(session_browser, app_name, archive_name)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I download the app data backup with name {archive_name:w}'))
|
||||
def backup_download(session_browser, downloaded_file_info, archive_name):
|
||||
file_path = system.download_backup(session_browser, archive_name)
|
||||
downloaded_file_info['path'] = file_path
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse(
|
||||
'I restore the {app_name:w} app data backup with name {archive_name:w}'
|
||||
))
|
||||
def backup_restore(session_browser, app_name, archive_name):
|
||||
system.backup_restore(session_browser, app_name, archive_name)
|
||||
|
||||
|
||||
@when(parsers.parse('I restore the downloaded app data backup'))
|
||||
def backup_restore_from_upload(session_browser, app_name,
|
||||
downloaded_file_info):
|
||||
path = downloaded_file_info["path"]
|
||||
try:
|
||||
system.backup_upload_and_restore(session_browser, app_name, path)
|
||||
except Exception as err:
|
||||
raise err
|
||||
finally:
|
||||
os.remove(path)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I configure pagekite with host {host:S}, port {port:d}, '
|
||||
'kite name {kite_name:S} and kite secret {kite_secret:w}'))
|
||||
def pagekite_configure(session_browser, host, port, kite_name, kite_secret):
|
||||
system.pagekite_configure(session_browser, host, port, kite_name,
|
||||
kite_secret)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'pagekite should be configured with host {host:S}, port {port:d}, '
|
||||
'kite name {kite_name:S} and kite secret {kite_secret:w}'))
|
||||
def pagekite_assert_configured(session_browser, host, port, kite_name,
|
||||
kite_secret):
|
||||
assert (host, port, kite_name,
|
||||
kite_secret) == system.pagekite_get_configuration(session_browser)
|
||||
|
||||
|
||||
@given(parsers.parse('bind forwarders are set to {forwarders}'))
|
||||
def bind_given_set_forwarders(session_browser, forwarders):
|
||||
system.bind_set_forwarders(session_browser, forwarders)
|
||||
|
||||
|
||||
@when(parsers.parse('I set bind forwarders to {forwarders}'))
|
||||
def bind_set_forwarders(session_browser, forwarders):
|
||||
system.bind_set_forwarders(session_browser, forwarders)
|
||||
|
||||
|
||||
@then(parsers.parse('bind forwarders should be {forwarders}'))
|
||||
def bind_assert_forwarders(session_browser, forwarders):
|
||||
assert system.bind_get_forwarders(session_browser) == forwarders
|
||||
|
||||
|
||||
@given(parsers.parse('bind DNSSEC is {enable:w}'))
|
||||
def bind_given_enable_dnssec(session_browser, enable):
|
||||
should_enable = (enable == 'enabled')
|
||||
system.bind_enable_dnssec(session_browser, should_enable)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable:w} bind DNSSEC'))
|
||||
def bind_enable_dnssec(session_browser, enable):
|
||||
should_enable = (enable == 'enable')
|
||||
system.bind_enable_dnssec(session_browser, should_enable)
|
||||
|
||||
|
||||
@then(parsers.parse('bind DNSSEC should be {enabled:w}'))
|
||||
def bind_assert_dnssec(session_browser, enabled):
|
||||
assert system.bind_get_dnssec(session_browser) == (enabled == 'enabled')
|
||||
|
||||
|
||||
@given(parsers.parse('restricted console logins are {enabled}'))
|
||||
def security_given_enable_restricted_logins(session_browser, enabled):
|
||||
should_enable = (enabled == 'enabled')
|
||||
system.security_enable_restricted_logins(session_browser, should_enable)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable} restricted console logins'))
|
||||
def security_enable_restricted_logins(session_browser, enable):
|
||||
should_enable = (enable == 'enable')
|
||||
system.security_enable_restricted_logins(session_browser, should_enable)
|
||||
|
||||
|
||||
@then(parsers.parse('restricted console logins should be {enabled}'))
|
||||
def security_assert_restricted_logins(session_browser, enabled):
|
||||
enabled = (enabled == 'enabled')
|
||||
assert system.security_get_restricted_logins(session_browser) == enabled
|
||||
|
||||
|
||||
@given(parsers.parse('automatic upgrades are {enabled:w}'))
|
||||
def upgrades_given_enable_automatic(session_browser, enabled):
|
||||
should_enable = (enabled == 'enabled')
|
||||
system.upgrades_enable_automatic(session_browser, should_enable)
|
||||
|
||||
|
||||
@when(parsers.parse('I {enable:w} automatic upgrades'))
|
||||
def upgrades_enable_automatic(session_browser, enable):
|
||||
should_enable = (enable == 'enable')
|
||||
system.upgrades_enable_automatic(session_browser, should_enable)
|
||||
|
||||
|
||||
@then(parsers.parse('automatic upgrades should be {enabled:w}'))
|
||||
def upgrades_assert_automatic(session_browser, enabled):
|
||||
should_be_enabled = (enabled == 'enabled')
|
||||
assert system.upgrades_get_automatic(session_browser) == should_be_enabled
|
||||
|
||||
|
||||
@given(
|
||||
parsers.parse(
|
||||
'the {key_type:w} key for {domain:S} is imported in monkeysphere'))
|
||||
def monkeysphere_given_import_key(session_browser, key_type, domain):
|
||||
system.monkeysphere_import_key(session_browser, key_type.lower(), domain)
|
||||
|
||||
|
||||
@when(parsers.parse('I import {key_type:w} key for {domain:S} in monkeysphere')
|
||||
)
|
||||
def monkeysphere_import_key(session_browser, key_type, domain):
|
||||
system.monkeysphere_import_key(session_browser, key_type.lower(), domain)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse(
|
||||
'the {key_type:w} key should imported for {domain:S} in monkeysphere'))
|
||||
def monkeysphere_assert_imported_key(session_browser, key_type, domain):
|
||||
system.monkeysphere_assert_imported_key(session_browser, key_type.lower(),
|
||||
domain)
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse('I should be able to publish {key_type:w} key for '
|
||||
'{domain:S} in monkeysphere'))
|
||||
def monkeysphere_publish_key(session_browser, key_type, domain):
|
||||
system.monkeysphere_publish_key(session_browser, key_type.lower(), domain)
|
||||
|
||||
|
||||
@when(parsers.parse('I wait for {seconds} seconds'))
|
||||
def sleep_for(seconds):
|
||||
seconds = int(seconds)
|
||||
time.sleep(seconds)
|
||||
|
||||
|
||||
@when(parsers.parse('I open the main page'))
|
||||
def open_main_page(session_browser):
|
||||
system.open_main_page(session_browser)
|
||||
|
||||
|
||||
@then(parsers.parse('the main page should be shown'))
|
||||
def main_page_is_shown(session_browser):
|
||||
assert (session_browser.url.endswith('/plinth/'))
|
||||
|
||||
|
||||
@given(parsers.parse('the network device is in the {zone:w} firewall zone'))
|
||||
def networks_set_firewall_zone(session_browser, zone):
|
||||
system.networks_set_firewall_zone(session_browser, zone)
|
||||
|
||||
|
||||
@then('the root disk should be shown')
|
||||
def storage_root_disk_is_shown(session_browser):
|
||||
assert system.storage_is_root_disk_shown(session_browser)
|
||||
@ -1,13 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import configparser
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(pathlib.Path(__file__).parent.with_name('config.ini'))
|
||||
|
||||
config['DEFAULT']['url'] = os.environ.get('FREEDOMBOX_URL',
|
||||
config['DEFAULT']['url'])
|
||||
config['DEFAULT']['samba_port'] = os.environ.get(
|
||||
'FREEDOMBOX_SAMBA_PORT', config['DEFAULT']['samba_port'])
|
||||
@ -1,752 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import string
|
||||
import subprocess
|
||||
import tempfile
|
||||
import urllib
|
||||
from time import sleep
|
||||
|
||||
import requests
|
||||
import splinter
|
||||
|
||||
from . import config, interface, site
|
||||
from .interface import submit
|
||||
from .service import eventually, wait_for_page_update
|
||||
|
||||
# unlisted apps just use the app_name as module name
|
||||
app_module = {
|
||||
'ntp': 'datetime',
|
||||
'wiki': 'ikiwiki',
|
||||
'tt-rss': 'ttrss',
|
||||
}
|
||||
|
||||
app_checkbox_id = {
|
||||
'tor': 'id_tor-enabled',
|
||||
}
|
||||
|
||||
default_url = config['DEFAULT']['url']
|
||||
|
||||
apps_with_loaders = ['tor']
|
||||
|
||||
|
||||
def get_app_module(app_name):
|
||||
module = app_name
|
||||
if app_name in app_module:
|
||||
module = app_module[app_name]
|
||||
return module
|
||||
|
||||
|
||||
def _find_install_button(browser, app_name):
|
||||
interface.nav_to_module(browser, get_app_module(app_name))
|
||||
return browser.find_by_css('.form-install input[type=submit]')
|
||||
|
||||
|
||||
def install(browser, app_name):
|
||||
install_button = _find_install_button(browser, app_name)
|
||||
|
||||
def install_in_progress():
|
||||
selectors = [
|
||||
'.install-state-' + state
|
||||
for state in ['pre', 'post', 'installing']
|
||||
]
|
||||
return any(
|
||||
browser.is_element_present_by_css(selector)
|
||||
for selector in selectors)
|
||||
|
||||
def is_server_restarting():
|
||||
return browser.is_element_present_by_css('.neterror')
|
||||
|
||||
def wait_for_install():
|
||||
if install_in_progress():
|
||||
sleep(1)
|
||||
elif is_server_restarting():
|
||||
sleep(1)
|
||||
browser.visit(browser.url)
|
||||
else:
|
||||
return
|
||||
wait_for_install()
|
||||
|
||||
if install_button:
|
||||
install_button.click()
|
||||
wait_for_install()
|
||||
# sleep(2) # XXX This shouldn't be required.
|
||||
|
||||
|
||||
def is_installed(browser, app_name):
|
||||
install_button = _find_install_button(browser, app_name)
|
||||
return not bool(install_button)
|
||||
|
||||
|
||||
def _change_app_status(browser, app_name, change_status_to='enabled'):
|
||||
"""Enable or disable application."""
|
||||
button = browser.find_by_css('button[name="app_enable_disable_button"]')
|
||||
|
||||
if button:
|
||||
should_enable_field = browser.find_by_id('id_should_enable')
|
||||
if (should_enable_field.value == 'False'
|
||||
and change_status_to == 'disabled') or (
|
||||
should_enable_field.value == 'True'
|
||||
and change_status_to == 'enabled'):
|
||||
interface.submit(browser, element=button)
|
||||
else:
|
||||
checkbox_id = app_checkbox_id[app_name]
|
||||
_change_status(browser, app_name, checkbox_id, change_status_to)
|
||||
|
||||
if app_name in apps_with_loaders:
|
||||
wait_for_config_update(browser, app_name)
|
||||
|
||||
|
||||
def _change_status(browser, app_name, checkbox_id, change_status_to='enabled'):
|
||||
"""Change checkbox status."""
|
||||
checkbox = browser.find_by_id(checkbox_id)
|
||||
checkbox.check() if change_status_to == 'enabled' else checkbox.uncheck()
|
||||
interface.submit(browser, form_class='form-configuration')
|
||||
|
||||
if app_name in apps_with_loaders:
|
||||
wait_for_config_update(browser, app_name)
|
||||
|
||||
|
||||
def enable(browser, app_name):
|
||||
interface.nav_to_module(browser, get_app_module(app_name))
|
||||
_change_app_status(browser, app_name, 'enabled')
|
||||
|
||||
|
||||
def disable(browser, app_name):
|
||||
interface.nav_to_module(browser, get_app_module(app_name))
|
||||
_change_app_status(browser, app_name, 'disabled')
|
||||
|
||||
|
||||
def can_be_disabled(browser, app_name):
|
||||
"""Return whether the application can be disabled."""
|
||||
interface.nav_to_module(browser, get_app_module(app_name))
|
||||
button = browser.find_by_css('button[name="app_enable_disable_button"]')
|
||||
return bool(button)
|
||||
|
||||
|
||||
def wait_for_config_update(browser, app_name):
|
||||
while browser.is_element_present_by_css('.running-status.loading'):
|
||||
sleep(0.1)
|
||||
|
||||
|
||||
def _download_file(browser, url):
|
||||
"""Return file contents after downloading a URL."""
|
||||
cookies = browser.cookies.all()
|
||||
response = requests.get(url, cookies=cookies, verify=False)
|
||||
if response.status_code != 200:
|
||||
raise Exception('URL download failed')
|
||||
|
||||
return response.content
|
||||
|
||||
|
||||
def select_domain_name(browser, app_name, domain_name):
|
||||
browser.visit('{}/plinth/apps/{}/setup/'.format(default_url, app_name))
|
||||
drop_down = browser.find_by_id('id_domain_name')
|
||||
drop_down.select(domain_name)
|
||||
interface.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def configure_shadowsocks(browser, server, password):
|
||||
"""Configure shadowsocks client with given server details."""
|
||||
browser.visit('{}/plinth/apps/shadowsocks/'.format(default_url))
|
||||
browser.find_by_id('id_server').fill(server)
|
||||
browser.find_by_id('id_password').fill(password)
|
||||
interface.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def shadowsocks_get_configuration(browser):
|
||||
"""Return the server and password currently configured in shadowsocks."""
|
||||
browser.visit('{}/plinth/apps/shadowsocks/'.format(default_url))
|
||||
server = browser.find_by_id('id_server').value
|
||||
password = browser.find_by_id('id_password').value
|
||||
return server, password
|
||||
|
||||
|
||||
def modify_max_file_size(browser, size):
|
||||
"""Change the maximum file size of coquelicot to the given value"""
|
||||
browser.visit('{}/plinth/apps/coquelicot/'.format(default_url))
|
||||
browser.find_by_id('id_max_file_size').fill(size)
|
||||
interface.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def get_max_file_size(browser):
|
||||
"""Get the maximum file size of coquelicot"""
|
||||
browser.visit('{}/plinth/apps/coquelicot/'.format(default_url))
|
||||
return int(browser.find_by_id('id_max_file_size').value)
|
||||
|
||||
|
||||
def modify_upload_password(browser, password):
|
||||
"""Change the upload password for coquelicot to the given value"""
|
||||
browser.visit('{}/plinth/apps/coquelicot/'.format(default_url))
|
||||
browser.find_by_id('id_upload_password').fill(password)
|
||||
interface.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
# Sharing app helper functions
|
||||
|
||||
|
||||
def remove_share(browser, name):
|
||||
"""Remove a share in sharing app."""
|
||||
try:
|
||||
share_row = get_share(browser, name)
|
||||
except splinter.exceptions.ElementDoesNotExist:
|
||||
pass
|
||||
else:
|
||||
share_row.find_by_css('.share-remove')[0].click()
|
||||
|
||||
|
||||
def add_share(browser, name, path, group):
|
||||
"""Add a share in sharing app."""
|
||||
browser.visit('{}/plinth/apps/sharing/add/'.format(default_url))
|
||||
browser.fill('sharing-name', name)
|
||||
browser.fill('sharing-path', path)
|
||||
browser.find_by_css(
|
||||
'#id_sharing-groups input[value="{}"]'.format(group)).check()
|
||||
submit(browser)
|
||||
|
||||
|
||||
def edit_share(browser, old_name, new_name, path, group):
|
||||
"""Edit a share in sharing app."""
|
||||
row = get_share(browser, old_name)
|
||||
with wait_for_page_update(browser):
|
||||
row.find_by_css('.share-edit')[0].click()
|
||||
browser.fill('sharing-name', new_name)
|
||||
browser.fill('sharing-path', path)
|
||||
browser.find_by_css('#id_sharing-groups input').uncheck()
|
||||
browser.find_by_css(
|
||||
'#id_sharing-groups input[value="{}"]'.format(group)).check()
|
||||
submit(browser)
|
||||
|
||||
|
||||
def get_share(browser, name):
|
||||
"""Return the row for a given share."""
|
||||
browser.visit('{}/plinth/apps/sharing/'.format(default_url))
|
||||
return browser.find_by_id('share-{}'.format(name))[0]
|
||||
|
||||
|
||||
def verify_share(browser, name, path, group):
|
||||
"""Verfiy that a share exists in list of shares."""
|
||||
href = '{}/share/{}'.format(default_url, name)
|
||||
url = '/share/{}'.format(name)
|
||||
row = get_share(browser, name)
|
||||
assert row.find_by_css('.share-name')[0].text == name
|
||||
assert row.find_by_css('.share-path')[0].text == path
|
||||
assert row.find_by_css('.share-url a')[0]['href'] == href
|
||||
assert row.find_by_css('.share-url a')[0].text == url
|
||||
assert row.find_by_css('.share-groups')[0].text == group
|
||||
|
||||
|
||||
def access_share(browser, name):
|
||||
"""Visit a share and see if it is accessible."""
|
||||
row = get_share(browser, name)
|
||||
url = row.find_by_css('.share-url a')[0]['href']
|
||||
browser.visit(url)
|
||||
assert '/share/{}'.format(name) in browser.title
|
||||
|
||||
|
||||
def make_share_public(browser, name):
|
||||
"""Make share publicly accessible."""
|
||||
row = get_share(browser, name)
|
||||
with wait_for_page_update(browser):
|
||||
row.find_by_css('.share-edit')[0].click()
|
||||
browser.find_by_id('id_sharing-is_public').check()
|
||||
interface.submit(browser)
|
||||
|
||||
|
||||
def verify_nonexistant_share(browser, name):
|
||||
"""Verify that given URL for a given share name is a 404."""
|
||||
url = '{}/share/{}'.format(default_url, name)
|
||||
browser.visit(url)
|
||||
assert '404' in browser.title
|
||||
|
||||
|
||||
def verify_inaccessible_share(browser, name):
|
||||
"""Verify that given URL for a given share name denies permission."""
|
||||
url = '{}/share/{}'.format(default_url, name)
|
||||
browser.visit(url)
|
||||
eventually(lambda: '/plinth' in browser.url, args=[])
|
||||
|
||||
|
||||
def enable_mediawiki_public_registrations(browser):
|
||||
"""Enable public registrations in MediaWiki."""
|
||||
interface.nav_to_module(browser, 'mediawiki')
|
||||
_change_status(browser, 'mediawiki', 'id_enable_public_registrations',
|
||||
'enabled')
|
||||
|
||||
|
||||
def disable_mediawiki_public_registrations(browser):
|
||||
"""Enable public registrations in MediaWiki."""
|
||||
interface.nav_to_module(browser, 'mediawiki')
|
||||
_change_status(browser, 'mediawiki', 'id_enable_public_registrations',
|
||||
'disabled')
|
||||
|
||||
|
||||
def enable_mediawiki_private_mode(browser):
|
||||
"""Enable public registrations in MediaWiki."""
|
||||
interface.nav_to_module(browser, 'mediawiki')
|
||||
_change_status(browser, 'mediawiki', 'id_enable_private_mode', 'enabled')
|
||||
|
||||
|
||||
def disable_mediawiki_private_mode(browser):
|
||||
"""Enable public registrations in MediaWiki."""
|
||||
interface.nav_to_module(browser, 'mediawiki')
|
||||
_change_status(browser, 'mediawiki', 'id_enable_private_mode', 'disabled')
|
||||
|
||||
|
||||
def set_mediawiki_admin_password(browser, password):
|
||||
"""Set a password for the MediaWiki user called admin."""
|
||||
interface.nav_to_module(browser, 'mediawiki')
|
||||
browser.find_by_id('id_password').fill(password)
|
||||
interface.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def enable_ejabberd_message_archive_management(browser):
|
||||
"""Enable Message Archive Management in Ejabberd."""
|
||||
interface.nav_to_module(browser, 'ejabberd')
|
||||
_change_status(browser, 'ejabberd', 'id_MAM_enabled', 'enabled')
|
||||
|
||||
|
||||
def disable_ejabberd_message_archive_management(browser):
|
||||
"""Enable Message Archive Management in Ejabberd."""
|
||||
interface.nav_to_module(browser, 'ejabberd')
|
||||
_change_status(browser, 'ejabberd', 'id_MAM_enabled', 'disabled')
|
||||
|
||||
|
||||
def ejabberd_add_contact(browser):
|
||||
"""Add a contact to Ejabberd user's roster."""
|
||||
site.jsxc_add_contact(browser)
|
||||
|
||||
|
||||
def ejabberd_delete_contact(browser):
|
||||
"""Delete the contact from Ejabberd user's roster."""
|
||||
site.jsxc_delete_contact(browser)
|
||||
|
||||
|
||||
def ejabberd_has_contact(browser):
|
||||
"""Check whether the contact is in Ejabberd user's roster."""
|
||||
return eventually(site.jsxc_has_contact, [browser])
|
||||
|
||||
|
||||
def gitweb_create_repo(browser, repo, access=None, ok_if_exists=False):
|
||||
"""Create repository."""
|
||||
if not gitweb_repo_exists(browser, repo, access):
|
||||
gitweb_delete_repo(browser, repo, ignore_missing=True)
|
||||
browser.find_link_by_href('/plinth/apps/gitweb/create/').first.click()
|
||||
browser.find_by_id('id_gitweb-name').fill(repo)
|
||||
if access == 'private':
|
||||
browser.find_by_id('id_gitweb-is_private').check()
|
||||
elif access == 'public':
|
||||
browser.find_by_id('id_gitweb-is_private').uncheck()
|
||||
submit(browser)
|
||||
elif not ok_if_exists:
|
||||
assert False, 'Repo already exists.'
|
||||
|
||||
|
||||
def gitweb_delete_repo(browser, repo, ignore_missing=False):
|
||||
"""Delete repository."""
|
||||
interface.nav_to_module(browser, 'gitweb')
|
||||
delete_link = browser.find_link_by_href(
|
||||
'/plinth/apps/gitweb/{}/delete/'.format(repo))
|
||||
if delete_link or not ignore_missing:
|
||||
delete_link.first.click()
|
||||
submit(browser)
|
||||
|
||||
|
||||
def gitweb_edit_repo_metadata(browser, repo, metadata):
|
||||
"""Set repository metadata."""
|
||||
interface.nav_to_module(browser, 'gitweb')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/apps/gitweb/{}/edit/'.format(repo)).first.click()
|
||||
if 'name' in metadata:
|
||||
browser.find_by_id('id_gitweb-name').fill(metadata['name'])
|
||||
if 'description' in metadata:
|
||||
browser.find_by_id('id_gitweb-description').fill(
|
||||
metadata['description'])
|
||||
if 'owner' in metadata:
|
||||
browser.find_by_id('id_gitweb-owner').fill(metadata['owner'])
|
||||
if 'access' in metadata:
|
||||
if metadata['access'] == 'private':
|
||||
browser.find_by_id('id_gitweb-is_private').check()
|
||||
else:
|
||||
browser.find_by_id('id_gitweb-is_private').uncheck()
|
||||
submit(browser)
|
||||
|
||||
|
||||
def gitweb_get_repo_metadata(browser, repo):
|
||||
"""Get repository metadata."""
|
||||
interface.nav_to_module(browser, 'gitweb')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/apps/gitweb/{}/edit/'.format(repo)).first.click()
|
||||
metadata = {}
|
||||
for item in ['name', 'description', 'owner']:
|
||||
metadata[item] = browser.find_by_id('id_gitweb-' + item).value
|
||||
if browser.find_by_id('id_gitweb-is_private').value:
|
||||
metadata['access'] = 'private'
|
||||
else:
|
||||
metadata['access'] = 'public'
|
||||
return metadata
|
||||
|
||||
|
||||
def _gitweb_get_repo_url(repo, with_auth):
|
||||
""""Get repository URL"""
|
||||
scheme = 'http'
|
||||
if default_url.startswith('https://'):
|
||||
scheme = 'https'
|
||||
url = default_url.split('://')[1] if '://' in default_url else default_url
|
||||
password = 'gitweb_wrong_password'
|
||||
if with_auth:
|
||||
password = config['DEFAULT']['password']
|
||||
|
||||
return '{0}://{1}:{2}@{3}/gitweb/{4}'.format(scheme,
|
||||
config['DEFAULT']['username'],
|
||||
password, url, repo)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _gitweb_temp_directory():
|
||||
"""Create temporary directory"""
|
||||
name = tempfile.mkdtemp(prefix='plinth_test_gitweb_')
|
||||
yield name
|
||||
shutil.rmtree(name)
|
||||
|
||||
|
||||
def _gitweb_git_command_is_successful(command, cwd):
|
||||
"""Check if a command runs successfully or gives authentication error"""
|
||||
process = subprocess.run(command, capture_output=True, cwd=cwd)
|
||||
if process.returncode != 0:
|
||||
if 'Authentication failed' in process.stderr.decode():
|
||||
return False
|
||||
print(process.stdout.decode())
|
||||
# raise exception
|
||||
process.check_returncode()
|
||||
return True
|
||||
|
||||
|
||||
def gitweb_repo_exists(browser, repo, access=None):
|
||||
"""Check whether the repository exists."""
|
||||
interface.nav_to_module(browser, 'gitweb')
|
||||
links_found = browser.find_link_by_href('/gitweb/{}.git'.format(repo))
|
||||
access_matches = True
|
||||
if links_found and access:
|
||||
parent = links_found.first.find_by_xpath('..').first
|
||||
private_icon = parent.find_by_css('.repo-private-icon')
|
||||
if access == 'private':
|
||||
access_matches = True if private_icon else False
|
||||
if access == 'public':
|
||||
access_matches = True if not private_icon else False
|
||||
return bool(links_found) and access_matches
|
||||
|
||||
|
||||
def gitweb_repo_is_readable(repo, with_auth=False, url_git_extension=False):
|
||||
"""Check if a git repo is readable with git client."""
|
||||
url = _gitweb_get_repo_url(repo, with_auth)
|
||||
if url_git_extension:
|
||||
url = url + '.git'
|
||||
git_command = ['git', 'clone', '-c', 'http.sslverify=false', url]
|
||||
with _gitweb_temp_directory() as cwd:
|
||||
return _gitweb_git_command_is_successful(git_command, cwd)
|
||||
|
||||
|
||||
def gitweb_repo_is_writable(repo, with_auth=False, url_git_extension=False):
|
||||
"""Check if a git repo is writable with git client."""
|
||||
url = _gitweb_get_repo_url(repo, with_auth)
|
||||
if url_git_extension:
|
||||
url = url + '.git'
|
||||
|
||||
with _gitweb_temp_directory() as cwd:
|
||||
subprocess.run(['mkdir', 'test-project'], check=True, cwd=cwd)
|
||||
cwd = os.path.join(cwd, 'test-project')
|
||||
prepare_git_repo_commands = [
|
||||
'git init -q', 'git config http.sslVerify false',
|
||||
'git -c "user.name=Tester" -c "user.email=tester" '
|
||||
'commit -q --allow-empty -m "test"'
|
||||
]
|
||||
for command in prepare_git_repo_commands:
|
||||
subprocess.run(command, shell=True, check=True, cwd=cwd)
|
||||
git_push_command = ['git', 'push', '-qf', url, 'master']
|
||||
|
||||
return _gitweb_git_command_is_successful(git_push_command, cwd)
|
||||
|
||||
|
||||
def gitweb_set_repo_access(browser, repo, access):
|
||||
"""Set repository as public or private."""
|
||||
interface.nav_to_module(browser, 'gitweb')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/apps/gitweb/{}/edit/'.format(repo)).first.click()
|
||||
if access == 'private':
|
||||
browser.find_by_id('id_gitweb-is_private').check()
|
||||
else:
|
||||
browser.find_by_id('id_gitweb-is_private').uncheck()
|
||||
submit(browser)
|
||||
|
||||
|
||||
def gitweb_set_all_repos_private(browser):
|
||||
"""Set all repositories private"""
|
||||
interface.nav_to_module(browser, 'gitweb')
|
||||
public_repos = []
|
||||
for element in browser.find_by_css('#gitweb-repo-list .list-group-item'):
|
||||
if not element.find_by_css('.repo-private-icon'):
|
||||
repo = element.find_by_css('.repo-label').first.text
|
||||
public_repos.append(repo)
|
||||
for repo in public_repos:
|
||||
gitweb_set_repo_access(browser, repo, 'private')
|
||||
|
||||
|
||||
def gitweb_site_repo_exists(browser, repo):
|
||||
"""Check whether the repository exists on Gitweb site."""
|
||||
browser.visit('{}/gitweb'.format(default_url))
|
||||
return browser.find_by_css('a[href="/gitweb/{0}.git"]'.format(repo))
|
||||
|
||||
|
||||
def ikiwiki_create_wiki_if_needed(browser):
|
||||
"""Create wiki if it does not exist."""
|
||||
interface.nav_to_module(browser, 'ikiwiki')
|
||||
wiki = browser.find_link_by_href('/ikiwiki/wiki')
|
||||
if not wiki:
|
||||
browser.find_link_by_href('/plinth/apps/ikiwiki/create/').first.click()
|
||||
browser.find_by_id('id_ikiwiki-name').fill('wiki')
|
||||
browser.find_by_id('id_ikiwiki-admin_name').fill(
|
||||
config['DEFAULT']['username'])
|
||||
browser.find_by_id('id_ikiwiki-admin_password').fill(
|
||||
config['DEFAULT']['password'])
|
||||
submit(browser)
|
||||
|
||||
|
||||
def ikiwiki_delete_wiki(browser):
|
||||
"""Delete wiki."""
|
||||
interface.nav_to_module(browser, 'ikiwiki')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/apps/ikiwiki/wiki/delete/').first.click()
|
||||
submit(browser)
|
||||
|
||||
|
||||
def ikiwiki_wiki_exists(browser):
|
||||
"""Check whether the wiki exists."""
|
||||
interface.nav_to_module(browser, 'ikiwiki')
|
||||
wiki = browser.find_link_by_href('/ikiwiki/wiki')
|
||||
return bool(wiki)
|
||||
|
||||
|
||||
def time_zone_set(browser, time_zone):
|
||||
"""Set the system time zone."""
|
||||
interface.nav_to_module(browser, 'datetime')
|
||||
browser.select('time_zone', time_zone)
|
||||
interface.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def time_zone_get(browser):
|
||||
"""Set the system time zone."""
|
||||
interface.nav_to_module(browser, 'datetime')
|
||||
return browser.find_by_name('time_zone').first.value
|
||||
|
||||
|
||||
_TOR_FEATURE_TO_ELEMENT = {
|
||||
'relay': 'tor-relay_enabled',
|
||||
'bridge-relay': 'tor-bridge_relay_enabled',
|
||||
'hidden-services': 'tor-hs_enabled',
|
||||
'software': 'tor-apt_transport_tor_enabled'
|
||||
}
|
||||
|
||||
|
||||
def tor_feature_enable(browser, feature, should_enable):
|
||||
"""Enable/disable a Tor feature."""
|
||||
if not isinstance(should_enable, bool):
|
||||
should_enable = should_enable in ('enable', 'enabled')
|
||||
|
||||
element_name = _TOR_FEATURE_TO_ELEMENT[feature]
|
||||
interface.nav_to_module(browser, 'tor')
|
||||
checkbox_element = browser.find_by_name(element_name).first
|
||||
if should_enable == checkbox_element.checked:
|
||||
return
|
||||
|
||||
if should_enable:
|
||||
if feature == 'bridge-relay':
|
||||
browser.find_by_name('tor-relay_enabled').first.check()
|
||||
|
||||
checkbox_element.check()
|
||||
else:
|
||||
checkbox_element.uncheck()
|
||||
|
||||
interface.submit(browser, form_class='form-configuration')
|
||||
wait_for_config_update(browser, 'tor')
|
||||
|
||||
|
||||
def tor_assert_feature_enabled(browser, feature, enabled):
|
||||
"""Assert whether Tor relay is enabled or disabled."""
|
||||
if not isinstance(enabled, bool):
|
||||
enabled = enabled in ('enable', 'enabled')
|
||||
|
||||
element_name = _TOR_FEATURE_TO_ELEMENT[feature]
|
||||
interface.nav_to_module(browser, 'tor')
|
||||
assert browser.find_by_name(element_name).first.checked == enabled
|
||||
|
||||
|
||||
def tor_get_relay_ports(browser):
|
||||
"""Return the list of ports shown in the relay table."""
|
||||
interface.nav_to_module(browser, 'tor')
|
||||
return [
|
||||
port_name.text
|
||||
for port_name in browser.find_by_css('.tor-relay-port-name')
|
||||
]
|
||||
|
||||
|
||||
def tor_assert_hidden_services(browser):
|
||||
"""Assert that hidden service information is shown."""
|
||||
interface.nav_to_module(browser, 'tor')
|
||||
assert browser.find_by_css('.tor-hs .tor-hs-hostname')
|
||||
|
||||
|
||||
def tahoe_get_introducer(browser, domain, introducer_type):
|
||||
"""Return an introducer element with a given type from tahoe-lafs."""
|
||||
interface.nav_to_module(browser, 'tahoe')
|
||||
css_class = '.{}-introducers .introducer-furl'.format(introducer_type)
|
||||
for furl in browser.find_by_css(css_class):
|
||||
if domain in furl.text:
|
||||
return furl.parent
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def tahoe_add_introducer(browser, domain):
|
||||
"""Add a new introducer into tahoe-lafs."""
|
||||
interface.nav_to_module(browser, 'tahoe')
|
||||
|
||||
furl = 'pb://ewe4zdz6kxn7xhuvc7izj2da2gpbgeir@tcp:{}:3456/' \
|
||||
'fko4ivfwgqvybppwar3uehkx6spaaou7'.format(domain)
|
||||
browser.fill('pet_name', 'testintroducer')
|
||||
browser.fill('furl', furl)
|
||||
submit(browser, form_class='form-add-introducer')
|
||||
|
||||
|
||||
def tahoe_remove_introducer(browser, domain):
|
||||
"""Remove an introducer from tahoe-lafs."""
|
||||
introducer = tahoe_get_introducer(browser, domain, 'connected')
|
||||
submit(browser, element=introducer.find_by_css('.form-remove'))
|
||||
|
||||
|
||||
def radicale_get_access_rights(browser):
|
||||
access_rights_types = ['owner_only', 'owner_write', 'authenticated']
|
||||
interface.nav_to_module(browser, 'radicale')
|
||||
for access_rights_type in access_rights_types:
|
||||
if browser.find_by_value(access_rights_type).checked:
|
||||
return access_rights_type
|
||||
|
||||
|
||||
def radicale_set_access_rights(browser, access_rights_type):
|
||||
interface.nav_to_module(browser, 'radicale')
|
||||
browser.choose('access_rights', access_rights_type)
|
||||
interface.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def openvpn_setup(browser):
|
||||
"""Setup the OpenVPN application after installation."""
|
||||
interface.nav_to_module(browser, 'openvpn')
|
||||
setup_form = browser.find_by_css('.form-setup')
|
||||
if not setup_form:
|
||||
return
|
||||
|
||||
submit(browser, form_class='form-setup')
|
||||
wait_for_config_update(browser, 'openvpn')
|
||||
|
||||
|
||||
def openvpn_download_profile(browser):
|
||||
"""Download the current user's profile into a file and return path."""
|
||||
interface.nav_to_module(browser, 'openvpn')
|
||||
url = browser.find_by_css('.form-profile')['action']
|
||||
return _download_file(browser, url)
|
||||
|
||||
|
||||
def samba_set_share(browser, share_type, status='enabled'):
|
||||
"""Enable or disable samba share."""
|
||||
disk_name = 'disk'
|
||||
share_type_name = '{0}_share'.format(share_type)
|
||||
interface.nav_to_module(browser, 'samba')
|
||||
for elem in browser.find_by_tag('td'):
|
||||
if elem.text == disk_name:
|
||||
share_form = elem.find_by_xpath('(..//*)[2]/form').first
|
||||
share_btn = share_form.find_by_name(share_type_name).first
|
||||
if status == 'enabled' and share_btn['value'] == 'enable':
|
||||
share_btn.click()
|
||||
elif status == 'disabled' and share_btn['value'] == 'disable':
|
||||
share_btn.click()
|
||||
break
|
||||
|
||||
|
||||
def _samba_write_to_share(share_type, as_guest=False):
|
||||
"""Write to the samba share, return output messages as string."""
|
||||
disk_name = 'disk'
|
||||
if share_type == 'open':
|
||||
share_name = disk_name
|
||||
else:
|
||||
share_name = '{0}_{1}'.format(disk_name, share_type)
|
||||
hostname = urllib.parse.urlparse(default_url).hostname
|
||||
servicename = '\\\\{0}\\{1}'.format(hostname, share_name)
|
||||
directory = '_plinth-test_{0}'.format(''.join(
|
||||
random.SystemRandom().choices(string.ascii_letters, k=8)))
|
||||
port = config['DEFAULT']['samba_port']
|
||||
|
||||
smb_command = ['smbclient', '-W', 'WORKGROUP', '-p', port]
|
||||
if as_guest:
|
||||
smb_command += ['-N']
|
||||
else:
|
||||
smb_command += [
|
||||
'-U', '{0}%{1}'.format(config['DEFAULT']['username'],
|
||||
config['DEFAULT']['password'])
|
||||
]
|
||||
smb_command += [
|
||||
servicename, '-c', 'mkdir {0}; rmdir {0}'.format(directory)
|
||||
]
|
||||
|
||||
return subprocess.check_output(smb_command).decode()
|
||||
|
||||
|
||||
def samba_assert_share_is_writable(share_type, as_guest=False):
|
||||
"""Assert that samba share is writable."""
|
||||
output = _samba_write_to_share(share_type, as_guest=False)
|
||||
|
||||
assert not output, output
|
||||
|
||||
|
||||
def samba_assert_share_is_not_accessible(share_type, as_guest=False):
|
||||
"""Assert that samba share is not accessible."""
|
||||
try:
|
||||
_samba_write_to_share(share_type, as_guest)
|
||||
except subprocess.CalledProcessError as err:
|
||||
err_output = err.output.decode()
|
||||
assert 'NT_STATUS_ACCESS_DENIED' in err_output, err_output
|
||||
else:
|
||||
assert False, 'Can access the share.'
|
||||
|
||||
|
||||
def samba_assert_share_is_not_available(share_type):
|
||||
"""Assert that samba share is not accessible."""
|
||||
try:
|
||||
_samba_write_to_share(share_type)
|
||||
except subprocess.CalledProcessError as err:
|
||||
err_output = err.output.decode()
|
||||
assert 'NT_STATUS_BAD_NETWORK_NAME' in err_output, err_output
|
||||
else:
|
||||
assert False, 'Can access the share.'
|
||||
|
||||
|
||||
def searx_enable_public_access(browser):
|
||||
"""Enable Public Access in SearX"""
|
||||
interface.nav_to_module(browser, 'searx')
|
||||
browser.find_by_id('id_public_access').check()
|
||||
interface.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def searx_disable_public_access(browser):
|
||||
"""Enable Public Access in SearX"""
|
||||
interface.nav_to_module(browser, 'searx')
|
||||
browser.find_by_id('id_public_access').uncheck()
|
||||
interface.submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def find_on_front_page(browser, app_name):
|
||||
browser.visit(default_url)
|
||||
shortcuts = browser.find_link_by_href(f'/{app_name}/')
|
||||
return shortcuts
|
||||
@ -1,150 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import logging
|
||||
import random
|
||||
import tempfile
|
||||
|
||||
import requests
|
||||
|
||||
from . import config
|
||||
from .service import wait_for_page_update
|
||||
|
||||
sys_modules = [
|
||||
'avahi', 'backups', 'bind', 'cockpit', 'config', 'datetime', 'diagnostics',
|
||||
'dynamicdns', 'firewall', 'letsencrypt', 'monkeysphere', 'names',
|
||||
'networks', 'pagekite', 'performance', 'power', 'security', 'snapshot',
|
||||
'ssh', 'storage', 'upgrades', 'users'
|
||||
]
|
||||
|
||||
default_url = config['DEFAULT']['url']
|
||||
|
||||
|
||||
def login(browser, url, username, password):
|
||||
|
||||
# XXX: Find a way to remove the hardcoded jsxc URL
|
||||
if '/plinth/' not in browser.url or '/jsxc/jsxc' in browser.url:
|
||||
browser.visit(url)
|
||||
|
||||
apps_link = browser.find_link_by_href('/plinth/apps/')
|
||||
if len(apps_link):
|
||||
return
|
||||
|
||||
login_button = browser.find_link_by_href('/plinth/accounts/login/')
|
||||
if login_button:
|
||||
login_button.first.click()
|
||||
if login_button:
|
||||
browser.fill('username', username)
|
||||
browser.fill('password', password)
|
||||
submit(browser)
|
||||
else:
|
||||
browser.visit(default_url + '/plinth/firstboot/welcome')
|
||||
submit(browser) # click the "Start Setup" button
|
||||
create_admin_account(browser, username, password)
|
||||
if '/network-topology-first-boot' in browser.url:
|
||||
submit(browser, element=browser.find_by_name('skip')[0])
|
||||
|
||||
if '/internet-connection-type' in browser.url:
|
||||
submit(browser, element=browser.find_by_name('skip')[0])
|
||||
|
||||
|
||||
def is_login_prompt(browser):
|
||||
return all(
|
||||
[browser.find_by_id('id_username'),
|
||||
browser.find_by_id('id_password')])
|
||||
|
||||
|
||||
def nav_to_module(browser, module):
|
||||
sys_or_apps = 'sys' if module in sys_modules else 'apps'
|
||||
required_url = default_url + f'/plinth/{sys_or_apps}/{module}/'
|
||||
if browser.url != required_url:
|
||||
browser.visit(required_url)
|
||||
|
||||
|
||||
def create_user(browser, name, password):
|
||||
nav_to_module(browser, 'users')
|
||||
with wait_for_page_update(browser):
|
||||
browser.find_link_by_href('/plinth/sys/users/create/').first.click()
|
||||
browser.find_by_id('id_username').fill(name)
|
||||
browser.find_by_id('id_password1').fill(password)
|
||||
browser.find_by_id('id_password2').fill(password)
|
||||
submit(browser)
|
||||
|
||||
|
||||
def rename_user(browser, old_name, new_name):
|
||||
nav_to_module(browser, 'users')
|
||||
with wait_for_page_update(browser):
|
||||
browser.find_link_by_href('/plinth/sys/users/' + old_name +
|
||||
'/edit/').first.click()
|
||||
browser.find_by_id('id_username').fill(new_name)
|
||||
submit(browser)
|
||||
|
||||
|
||||
def delete_user(browser, name):
|
||||
nav_to_module(browser, 'users')
|
||||
delete_link = browser.find_link_by_href('/plinth/sys/users/' + name +
|
||||
'/delete/')
|
||||
if delete_link:
|
||||
with wait_for_page_update(browser):
|
||||
delete_link.first.click()
|
||||
submit(browser)
|
||||
|
||||
|
||||
def is_user(browser, name):
|
||||
nav_to_module(browser, 'users')
|
||||
edit_link = browser.find_link_by_href('/plinth/sys/users/' + name +
|
||||
'/edit/')
|
||||
return bool(edit_link)
|
||||
|
||||
|
||||
def create_admin_account(browser, username, password):
|
||||
browser.find_by_id('id_username').fill(username)
|
||||
browser.find_by_id('id_password1').fill(password)
|
||||
browser.find_by_id('id_password2').fill(password)
|
||||
submit(browser)
|
||||
|
||||
|
||||
def submit(browser, element=None, form_class=None, expected_url=None):
|
||||
with wait_for_page_update(browser, expected_url=expected_url):
|
||||
if element:
|
||||
element.click()
|
||||
elif form_class:
|
||||
browser.find_by_css(
|
||||
'.{} input[type=submit]'.format(form_class)).click()
|
||||
else:
|
||||
browser.find_by_css('input[type=submit]').click()
|
||||
|
||||
|
||||
def create_sample_local_file():
|
||||
"""Create a sample file for upload using browser."""
|
||||
contents = bytearray(random.getrandbits(8) for _ in range(64))
|
||||
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
||||
temp_file.write(contents)
|
||||
|
||||
return temp_file.name, contents
|
||||
|
||||
|
||||
def download_file(url):
|
||||
"""Download a file to disk given a URL."""
|
||||
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
||||
logging.captureWarnings(True)
|
||||
request = requests.get(url, verify=False)
|
||||
logging.captureWarnings(False)
|
||||
temp_file.write(request.content)
|
||||
|
||||
return temp_file.name
|
||||
|
||||
|
||||
def compare_files(file1, file2):
|
||||
"""Assert that the contents of two files are the same."""
|
||||
file1_contents = open(file1, 'rb').read()
|
||||
file2_contents = open(file2, 'rb').read()
|
||||
|
||||
assert file1_contents == file2_contents
|
||||
|
||||
|
||||
def go_to_status_logs(browser):
|
||||
browser.visit(default_url + '/plinth/help/status-log/')
|
||||
|
||||
|
||||
def are_status_logs_shown(browser):
|
||||
return browser.is_text_present('Logs begin')
|
||||
@ -1,79 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
|
||||
from selenium.common.exceptions import StaleElementReferenceException
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
|
||||
from . import interface
|
||||
|
||||
# unlisted services just use the service_name as module name
|
||||
service_module = {
|
||||
'ntp': 'datetime',
|
||||
}
|
||||
|
||||
|
||||
def get_service_module(service_name):
|
||||
module = service_name
|
||||
if service_name in service_module:
|
||||
module = service_module[service_name]
|
||||
return module
|
||||
|
||||
|
||||
def is_running(browser, service_name):
|
||||
interface.nav_to_module(browser, get_service_module(service_name))
|
||||
return len(browser.find_by_id('service-not-running')) == 0
|
||||
|
||||
|
||||
def is_not_running(browser, service_name):
|
||||
interface.nav_to_module(browser, get_service_module(service_name))
|
||||
return len(browser.find_by_id('service-not-running')) != 0
|
||||
|
||||
|
||||
def eventually(function, args=[], timeout=30):
|
||||
"""Execute a function returning a boolean expression till it returns
|
||||
True or a timeout is reached"""
|
||||
end_time = time.time() + timeout
|
||||
current_time = time.time()
|
||||
while current_time < end_time:
|
||||
if function(*args):
|
||||
return True
|
||||
|
||||
time.sleep(0.1)
|
||||
current_time = time.time()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@contextmanager
|
||||
def wait_for_page_update(browser, timeout=300, expected_url=None):
|
||||
page_body = browser.find_by_tag('body').first
|
||||
yield
|
||||
WebDriverWait(browser, timeout).until(page_loaded(page_body, expected_url))
|
||||
|
||||
|
||||
class page_loaded():
|
||||
"""
|
||||
Wait until a page (re)loaded.
|
||||
|
||||
- element: Wait until this element gets stale
|
||||
- expected_url (optional): Wait for the URL to become <expected_url>.
|
||||
This can be necessary to wait for a redirect to finish.
|
||||
"""
|
||||
|
||||
def __init__(self, element, expected_url=None):
|
||||
self.element = element
|
||||
self.expected_url = expected_url
|
||||
|
||||
def __call__(self, driver):
|
||||
is_stale = False
|
||||
try:
|
||||
self.element.has_class('whatever_class')
|
||||
except StaleElementReferenceException:
|
||||
if self.expected_url is None:
|
||||
is_stale = True
|
||||
else:
|
||||
if driver.url.endswith(self.expected_url):
|
||||
is_stale = True
|
||||
return is_stale
|
||||
@ -1,589 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import time
|
||||
|
||||
import requests
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
from . import application, config, interface, system
|
||||
from .service import eventually, wait_for_page_update
|
||||
|
||||
# unlisted sites just use '/' + site_name as url
|
||||
site_url = {
|
||||
'wiki': '/ikiwiki',
|
||||
'jsxc': '/plinth/apps/jsxc/jsxc/',
|
||||
'cockpit': '/_cockpit/',
|
||||
'syncthing': '/syncthing/',
|
||||
}
|
||||
|
||||
|
||||
def get_site_url(site_name):
|
||||
if site_name.startswith('share'):
|
||||
site_name = site_name.replace('_', '/')
|
||||
url = '/' + site_name
|
||||
if site_name in site_url:
|
||||
url = site_url[site_name]
|
||||
return url
|
||||
|
||||
|
||||
def is_available(browser, site_name):
|
||||
url_to_visit = config['DEFAULT']['url'] + get_site_url(site_name)
|
||||
browser.visit(url_to_visit)
|
||||
time.sleep(3)
|
||||
browser.reload()
|
||||
not_404 = '404' not in browser.title
|
||||
# The site might have a default path after the sitename,
|
||||
# e.g /mediawiki/Main_Page
|
||||
no_redirect = browser.url.startswith(url_to_visit.strip('/'))
|
||||
return not_404 and no_redirect
|
||||
|
||||
|
||||
def access_url(browser, site_name):
|
||||
browser.visit(config['DEFAULT']['url'] + get_site_url(site_name))
|
||||
|
||||
|
||||
def verify_coquelicot_upload_password(browser, password):
|
||||
browser.visit(config['DEFAULT']['url'] + '/coquelicot')
|
||||
# ensure the password form is scrolled into view
|
||||
browser.execute_script('window.scrollTo(100, 0)')
|
||||
browser.find_by_id('upload_password').fill(password)
|
||||
actions = ActionChains(browser.driver)
|
||||
actions.send_keys(Keys.RETURN)
|
||||
actions.perform()
|
||||
assert eventually(browser.is_element_present_by_css,
|
||||
args=['div[style*="display: none;"]'])
|
||||
|
||||
|
||||
def upload_file_to_coquelicot(browser, file_path, password):
|
||||
"""Upload a local file from disk to coquelicot."""
|
||||
verify_coquelicot_upload_password(browser, password)
|
||||
browser.attach_file('file', file_path)
|
||||
interface.submit(browser)
|
||||
assert eventually(browser.is_element_present_by_css,
|
||||
args=['#content .url'])
|
||||
url_textarea = browser.find_by_css('#content .url textarea').first
|
||||
return url_textarea.value
|
||||
|
||||
|
||||
def verify_mediawiki_create_account_link(browser):
|
||||
browser.visit(config['DEFAULT']['url'] +
|
||||
'/mediawiki/index.php/Special:CreateAccount')
|
||||
assert eventually(browser.is_element_present_by_id,
|
||||
args=['wpCreateaccount'])
|
||||
|
||||
|
||||
def verify_mediawiki_no_create_account_link(browser):
|
||||
browser.visit(config['DEFAULT']['url'] +
|
||||
'/mediawiki/index.php/Special:CreateAccount')
|
||||
assert eventually(browser.is_element_not_present_by_id,
|
||||
args=['wpCreateaccount'])
|
||||
|
||||
|
||||
def verify_mediawiki_anonymous_reads_edits_link(browser):
|
||||
browser.visit(config['DEFAULT']['url'] + '/mediawiki')
|
||||
assert eventually(browser.is_element_present_by_id, args=['ca-nstab-main'])
|
||||
|
||||
|
||||
def verify_mediawiki_no_anonymous_reads_edits_link(browser):
|
||||
browser.visit(config['DEFAULT']['url'] + '/mediawiki')
|
||||
assert eventually(browser.is_element_not_present_by_id,
|
||||
args=['ca-nstab-main'])
|
||||
assert eventually(browser.is_element_present_by_id,
|
||||
args=['ca-nstab-special'])
|
||||
|
||||
|
||||
def _login_to_mediawiki(browser, username, password):
|
||||
browser.visit(config['DEFAULT']['url'] +
|
||||
'/mediawiki/index.php?title=Special:Login')
|
||||
browser.find_by_id('wpName1').fill(username)
|
||||
browser.find_by_id('wpPassword1').fill(password)
|
||||
with wait_for_page_update(browser):
|
||||
browser.find_by_id('wpLoginAttempt').click()
|
||||
|
||||
|
||||
def login_to_mediawiki_with_credentials(browser, username, password):
|
||||
_login_to_mediawiki(browser, username, password)
|
||||
# Had to put it in the same step because sessions don't
|
||||
# persist between steps
|
||||
assert eventually(browser.is_element_present_by_id, args=['t-upload'])
|
||||
|
||||
|
||||
def upload_image_mediawiki(browser, username, password, image):
|
||||
"""Upload an image to MediaWiki. Idempotent."""
|
||||
browser.visit(config['DEFAULT']['url'] + '/mediawiki')
|
||||
_login_to_mediawiki(browser, username, password)
|
||||
|
||||
# Upload file
|
||||
browser.visit(config['DEFAULT']['url'] + '/mediawiki/Special:Upload')
|
||||
file_path = pathlib.Path(__file__).parent
|
||||
file_path /= '../../static/themes/default/img/' + image
|
||||
browser.attach_file('wpUploadFile', str(file_path.resolve()))
|
||||
interface.submit(browser, element=browser.find_by_name('wpUpload')[0])
|
||||
|
||||
|
||||
def get_number_of_uploaded_images_in_mediawiki(browser):
|
||||
browser.visit(config['DEFAULT']['url'] + '/mediawiki/Special:ListFiles')
|
||||
return len(browser.find_by_css('.TablePager_col_img_timestamp'))
|
||||
|
||||
|
||||
def get_uploaded_image_in_mediawiki(browser, image):
|
||||
browser.visit(config['DEFAULT']['url'] + '/mediawiki/Special:ListFiles')
|
||||
elements = browser.find_link_by_partial_href(image)
|
||||
return elements[0].value
|
||||
|
||||
|
||||
def mediawiki_delete_main_page(browser):
|
||||
"""Delete the mediawiki main page."""
|
||||
_login_to_mediawiki(browser, 'admin', 'whatever123')
|
||||
browser.visit(
|
||||
'{}/mediawiki/index.php?title=Main_Page&action=delete'.format(
|
||||
interface.default_url))
|
||||
with wait_for_page_update(browser):
|
||||
browser.find_by_id('wpConfirmB').first.click()
|
||||
|
||||
|
||||
def mediawiki_has_main_page(browser):
|
||||
"""Check if mediawiki main page exists."""
|
||||
return eventually(_mediawiki_has_main_page, [browser])
|
||||
|
||||
|
||||
def _mediawiki_has_main_page(browser):
|
||||
"""Check if mediawiki main page exists."""
|
||||
browser.visit('{}/mediawiki/Main_Page'.format(interface.default_url))
|
||||
content = browser.find_by_id('mw-content-text').first
|
||||
return 'This page has been deleted.' not in content.text
|
||||
|
||||
|
||||
def jsxc_login(browser):
|
||||
"""Login to JSXC."""
|
||||
access_url(browser, 'jsxc')
|
||||
browser.find_by_id('jsxc-username').fill(config['DEFAULT']['username'])
|
||||
browser.find_by_id('jsxc-password').fill(config['DEFAULT']['password'])
|
||||
browser.find_by_id('jsxc-submit').click()
|
||||
relogin = browser.find_by_text('relogin')
|
||||
if relogin:
|
||||
relogin.first.click()
|
||||
browser.find_by_id('jsxc_username').fill(config['DEFAULT']['username'])
|
||||
browser.find_by_id('jsxc_password').fill(config['DEFAULT']['password'])
|
||||
browser.find_by_text('Connect').first.click()
|
||||
|
||||
|
||||
def jsxc_add_contact(browser):
|
||||
"""Add a contact to JSXC user's roster."""
|
||||
system.set_domain_name(browser, 'localhost')
|
||||
application.install(browser, 'jsxc')
|
||||
jsxc_login(browser)
|
||||
new = browser.find_by_text('new contact')
|
||||
if new: # roster is empty
|
||||
new.first.click()
|
||||
browser.find_by_id('jsxc_username').fill('alice@localhost')
|
||||
browser.find_by_text('Add').first.click()
|
||||
|
||||
|
||||
def jsxc_delete_contact(browser):
|
||||
"""Delete the contact from JSXC user's roster."""
|
||||
jsxc_login(browser)
|
||||
browser.find_by_css('div.jsxc_more').first.click()
|
||||
browser.find_by_text('delete contact').first.click()
|
||||
browser.find_by_text('Remove').first.click()
|
||||
|
||||
|
||||
def jsxc_has_contact(browser):
|
||||
"""Check whether the contact is in JSXC user's roster."""
|
||||
jsxc_login(browser)
|
||||
contact = browser.find_by_text('alice@localhost')
|
||||
return bool(contact)
|
||||
|
||||
|
||||
def _mldonkey_submit_command(browser, command):
|
||||
"""Submit a command to mldonkey."""
|
||||
with browser.get_iframe('commands') as commands_frame:
|
||||
commands_frame.find_by_css('.txt2').fill(command)
|
||||
commands_frame.find_by_css('.but2').click()
|
||||
|
||||
|
||||
def mldonkey_remove_all_ed2k_files(browser):
|
||||
"""Remove all ed2k files from mldonkey."""
|
||||
browser.visit(config['DEFAULT']['url'] + '/mldonkey/')
|
||||
_mldonkey_submit_command(browser, 'cancel all')
|
||||
_mldonkey_submit_command(browser, 'confirm yes')
|
||||
|
||||
|
||||
def mldonkey_upload_sample_ed2k_file(browser):
|
||||
"""Upload a sample ed2k file into mldonkey."""
|
||||
browser.visit(config['DEFAULT']['url'] + '/mldonkey/')
|
||||
dllink_command = 'dllink ed2k://|file|foo.bar|123|' \
|
||||
'0123456789ABCDEF0123456789ABCDEF|/'
|
||||
_mldonkey_submit_command(browser, dllink_command)
|
||||
|
||||
|
||||
def mldonkey_get_number_of_ed2k_files(browser):
|
||||
"""Return the number of ed2k files currently in mldonkey."""
|
||||
browser.visit(config['DEFAULT']['url'] + '/mldonkey/')
|
||||
|
||||
with browser.get_iframe('commands') as commands_frame:
|
||||
commands_frame.find_by_xpath(
|
||||
'//tr//td[contains(text(), "Transfers")]').click()
|
||||
|
||||
with browser.get_iframe('output') as output_frame:
|
||||
return len(output_frame.find_by_css('.dl-1')) + len(
|
||||
output_frame.find_by_css('.dl-2'))
|
||||
|
||||
|
||||
def transmission_remove_all_torrents(browser):
|
||||
"""Remove all torrents from transmission."""
|
||||
browser.visit(config['DEFAULT']['url'] + '/transmission')
|
||||
while True:
|
||||
torrents = browser.find_by_css('#torrent_list .torrent')
|
||||
if not torrents:
|
||||
break
|
||||
|
||||
torrents.first.click()
|
||||
eventually(browser.is_element_not_present_by_css,
|
||||
args=['#toolbar-remove.disabled'])
|
||||
browser.click_link_by_id('toolbar-remove')
|
||||
eventually(browser.is_element_not_present_by_css,
|
||||
args=['#dialog-container[style="display: none;"]'])
|
||||
browser.click_link_by_id('dialog_confirm_button')
|
||||
eventually(browser.is_element_present_by_css,
|
||||
args=['#toolbar-remove.disabled'])
|
||||
|
||||
|
||||
def transmission_upload_sample_torrent(browser):
|
||||
"""Upload a sample torrent into transmission."""
|
||||
browser.visit(config['DEFAULT']['url'] + '/transmission')
|
||||
file_path = os.path.join(os.path.dirname(__file__), '..', 'data',
|
||||
'sample.torrent')
|
||||
browser.click_link_by_id('toolbar-open')
|
||||
eventually(browser.is_element_not_present_by_css,
|
||||
args=['#upload-container[style="display: none;"]'])
|
||||
browser.attach_file('torrent_files[]', [file_path])
|
||||
browser.click_link_by_id('upload_confirm_button')
|
||||
eventually(browser.is_element_present_by_css,
|
||||
args=['#torrent_list .torrent'])
|
||||
|
||||
|
||||
def transmission_get_number_of_torrents(browser):
|
||||
"""Return the number torrents currently in transmission."""
|
||||
browser.visit(config['DEFAULT']['url'] + '/transmission')
|
||||
return len(browser.find_by_css('#torrent_list .torrent'))
|
||||
|
||||
|
||||
def _deluge_get_active_window_title(browser):
|
||||
"""Return the title of the currently active window in Deluge."""
|
||||
return browser.evaluate_script(
|
||||
'Ext.WindowMgr.getActive() ? Ext.WindowMgr.getActive().title : null')
|
||||
|
||||
|
||||
def _deluge_ensure_logged_in(browser):
|
||||
"""Ensure that password dialog is answered and we can interact."""
|
||||
url = config['DEFAULT']['url'] + '/deluge'
|
||||
|
||||
def service_is_available():
|
||||
if browser.is_element_present_by_xpath(
|
||||
'//h1[text()="Service Unavailable"]'):
|
||||
access_url(browser, 'deluge')
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
if browser.url != url:
|
||||
browser.visit(url)
|
||||
# After a backup restore, service may not be available immediately
|
||||
eventually(service_is_available)
|
||||
|
||||
time.sleep(1) # Wait for Ext.js application in initialize
|
||||
|
||||
if _deluge_get_active_window_title(browser) != 'Login':
|
||||
return
|
||||
|
||||
browser.find_by_id('_password').first.fill('deluge')
|
||||
_deluge_click_active_window_button(browser, 'Login')
|
||||
|
||||
assert eventually(
|
||||
lambda: _deluge_get_active_window_title(browser) != 'Login')
|
||||
eventually(browser.is_element_not_present_by_css,
|
||||
args=['#add.x-item-disabled'], timeout=0.3)
|
||||
|
||||
|
||||
def _deluge_open_connection_manager(browser):
|
||||
"""Open the connection manager dialog if not already open."""
|
||||
title = 'Connection Manager'
|
||||
if _deluge_get_active_window_title(browser) == title:
|
||||
return
|
||||
|
||||
browser.find_by_css('button.x-deluge-connection-manager').first.click()
|
||||
eventually(lambda: _deluge_get_active_window_title(browser) == title)
|
||||
|
||||
|
||||
def _deluge_ensure_connected(browser):
|
||||
"""Type the connection password if required and start Deluge daemon."""
|
||||
_deluge_ensure_logged_in(browser)
|
||||
|
||||
# Change Default Password window appears once.
|
||||
if _deluge_get_active_window_title(browser) == 'Change Default Password':
|
||||
_deluge_click_active_window_button(browser, 'No')
|
||||
|
||||
assert eventually(browser.is_element_not_present_by_css,
|
||||
args=['#add.x-item-disabled'])
|
||||
|
||||
|
||||
def deluge_remove_all_torrents(browser):
|
||||
"""Remove all torrents from deluge."""
|
||||
_deluge_ensure_connected(browser)
|
||||
|
||||
while browser.find_by_css('#torrentGrid .torrent-name'):
|
||||
browser.find_by_css('#torrentGrid .torrent-name').first.click()
|
||||
|
||||
# Click remove toolbar button
|
||||
browser.find_by_id('remove').first.click()
|
||||
|
||||
# Remove window shows up
|
||||
assert eventually(lambda: _deluge_get_active_window_title(browser) ==
|
||||
'Remove Torrent')
|
||||
|
||||
_deluge_click_active_window_button(browser, 'Remove With Data')
|
||||
|
||||
# Remove window disappears
|
||||
assert eventually(lambda: not _deluge_get_active_window_title(browser))
|
||||
|
||||
|
||||
def _deluge_get_active_window_id(browser):
|
||||
"""Return the ID of the currently active window."""
|
||||
return browser.evaluate_script('Ext.WindowMgr.getActive().id')
|
||||
|
||||
|
||||
def _deluge_click_active_window_button(browser, button_text):
|
||||
"""Click an action button in the active window."""
|
||||
browser.execute_script('''
|
||||
active_window = Ext.WindowMgr.getActive();
|
||||
active_window.buttons.forEach(function (button) {{
|
||||
if (button.text == "{button_text}")
|
||||
button.btnEl.dom.click()
|
||||
}})'''.format(button_text=button_text))
|
||||
|
||||
|
||||
def deluge_upload_sample_torrent(browser):
|
||||
"""Upload a sample torrent into deluge."""
|
||||
_deluge_ensure_connected(browser)
|
||||
|
||||
number_of_torrents = _deluge_get_number_of_torrents(browser)
|
||||
|
||||
# Click add toolbar button
|
||||
browser.find_by_id('add').first.click()
|
||||
|
||||
# Add window appears
|
||||
eventually(
|
||||
lambda: _deluge_get_active_window_title(browser) == 'Add Torrents')
|
||||
|
||||
file_path = os.path.join(os.path.dirname(__file__), '..', 'data',
|
||||
'sample.torrent')
|
||||
|
||||
if browser.find_by_id('fileUploadForm'): # deluge-web 2.x
|
||||
browser.attach_file('file', file_path)
|
||||
else: # deluge-web 1.x
|
||||
browser.find_by_css('button.x-deluge-add-file').first.click()
|
||||
|
||||
# Add from file window appears
|
||||
eventually(lambda: _deluge_get_active_window_title(browser) ==
|
||||
'Add from File')
|
||||
|
||||
# Attach file
|
||||
browser.attach_file('file', file_path)
|
||||
|
||||
# Click Add
|
||||
_deluge_click_active_window_button(browser, 'Add')
|
||||
|
||||
eventually(
|
||||
lambda: _deluge_get_active_window_title(browser) == 'Add Torrents')
|
||||
|
||||
# Click Add
|
||||
time.sleep(1)
|
||||
_deluge_click_active_window_button(browser, 'Add')
|
||||
|
||||
eventually(
|
||||
lambda: _deluge_get_number_of_torrents(browser) > number_of_torrents)
|
||||
|
||||
|
||||
def _deluge_get_number_of_torrents(browser):
|
||||
"""Return the number torrents currently in deluge."""
|
||||
return len(browser.find_by_css('#torrentGrid .torrent-name'))
|
||||
|
||||
|
||||
def deluge_get_number_of_torrents(browser):
|
||||
"""Return the number torrents currently in deluge."""
|
||||
_deluge_ensure_connected(browser)
|
||||
|
||||
return _deluge_get_number_of_torrents(browser)
|
||||
|
||||
|
||||
def calendar_is_available(browser):
|
||||
"""Return whether calendar is available at well-known URL."""
|
||||
conf = config['DEFAULT']
|
||||
url = conf['url'] + '/.well-known/caldav'
|
||||
logging.captureWarnings(True)
|
||||
request = requests.get(url, auth=(conf['username'], conf['password']),
|
||||
verify=False)
|
||||
logging.captureWarnings(False)
|
||||
return request.status_code != 404
|
||||
|
||||
|
||||
def addressbook_is_available(browser):
|
||||
"""Return whether addressbook is available at well-known URL."""
|
||||
conf = config['DEFAULT']
|
||||
url = conf['url'] + '/.well-known/carddav'
|
||||
logging.captureWarnings(True)
|
||||
request = requests.get(url, auth=(conf['username'], conf['password']),
|
||||
verify=False)
|
||||
logging.captureWarnings(False)
|
||||
return request.status_code != 404
|
||||
|
||||
|
||||
def _syncthing_load_main_interface(browser):
|
||||
"""Close the dialog boxes that many popup after visiting the URL."""
|
||||
access_url(browser, 'syncthing')
|
||||
|
||||
def service_is_available():
|
||||
if browser.is_element_present_by_xpath(
|
||||
'//h1[text()="Service Unavailable"]'):
|
||||
access_url(browser, 'syncthing')
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# After a backup restore, service may not be available immediately
|
||||
eventually(service_is_available)
|
||||
|
||||
# Wait for javascript loading process to complete
|
||||
browser.execute_script('''
|
||||
document.is_ui_online = false;
|
||||
var old_console_log = console.log;
|
||||
console.log = function(message) {
|
||||
old_console_log.apply(null, arguments);
|
||||
if (message == 'UIOnline') {
|
||||
document.is_ui_online = true;
|
||||
console.log = old_console_log;
|
||||
}
|
||||
};
|
||||
''')
|
||||
eventually(lambda: browser.evaluate_script('document.is_ui_online'),
|
||||
timeout=5)
|
||||
|
||||
# Dismiss the Usage Reporting consent dialog
|
||||
usage_reporting = browser.find_by_id('ur').first
|
||||
eventually(lambda: usage_reporting.visible, timeout=2)
|
||||
if usage_reporting.visible:
|
||||
yes_xpath = './/button[contains(@ng-click, "declineUR")]'
|
||||
usage_reporting.find_by_xpath(yes_xpath).first.click()
|
||||
eventually(lambda: not usage_reporting.visible)
|
||||
|
||||
|
||||
def syncthing_folder_is_present(browser, folder_name):
|
||||
"""Return whether a folder is present in Syncthing."""
|
||||
_syncthing_load_main_interface(browser)
|
||||
folder_names = browser.find_by_css('#folders .panel-title-text span')
|
||||
folder_names = [folder_name.text for folder_name in folder_names]
|
||||
return folder_name in folder_names
|
||||
|
||||
|
||||
def syncthing_add_folder(browser, folder_name, folder_path):
|
||||
"""Add a new folder to Synthing."""
|
||||
_syncthing_load_main_interface(browser)
|
||||
add_folder_xpath = '//button[contains(@ng-click, "addFolder")]'
|
||||
browser.find_by_xpath(add_folder_xpath).click()
|
||||
|
||||
folder_dialog = browser.find_by_id('editFolder').first
|
||||
eventually(lambda: folder_dialog.visible)
|
||||
browser.find_by_id('folderLabel').fill(folder_name)
|
||||
browser.find_by_id('folderPath').fill(folder_path)
|
||||
save_folder_xpath = './/button[contains(@ng-click, "saveFolder")]'
|
||||
folder_dialog.find_by_xpath(save_folder_xpath).first.click()
|
||||
eventually(lambda: not folder_dialog.visible)
|
||||
|
||||
|
||||
def syncthing_remove_folder(browser, folder_name):
|
||||
"""Remove a folder from Synthing."""
|
||||
_syncthing_load_main_interface(browser)
|
||||
|
||||
# Find folder
|
||||
folder = None
|
||||
for current_folder in browser.find_by_css('#folders > .panel'):
|
||||
name = current_folder.find_by_css('.panel-title-text span').first.text
|
||||
if name == folder_name:
|
||||
folder = current_folder
|
||||
break
|
||||
|
||||
# Edit folder button
|
||||
folder.find_by_css('button.panel-heading').first.click()
|
||||
eventually(lambda: folder.find_by_css('div.collapse.in'))
|
||||
edit_folder_xpath = './/button[contains(@ng-click, "editFolder")]'
|
||||
edit_folder_button = folder.find_by_xpath(edit_folder_xpath).first
|
||||
edit_folder_button.click()
|
||||
|
||||
# Edit folder dialog
|
||||
folder_dialog = browser.find_by_id('editFolder').first
|
||||
eventually(lambda: folder_dialog.visible)
|
||||
remove_button_xpath = './/button[contains(@data-target, "remove-folder")]'
|
||||
folder_dialog.find_by_xpath(remove_button_xpath).first.click()
|
||||
|
||||
# Remove confirmation dialog
|
||||
remove_folder_dialog = browser.find_by_id('remove-folder-confirmation')
|
||||
eventually(lambda: remove_folder_dialog.visible)
|
||||
remove_button_xpath = './/button[contains(@ng-click, "deleteFolder")]'
|
||||
remove_folder_dialog.find_by_xpath(remove_button_xpath).first.click()
|
||||
|
||||
eventually(lambda: not folder_dialog.visible)
|
||||
|
||||
|
||||
def _ttrss_load_main_interface(browser):
|
||||
"""Load the TT-RSS interface."""
|
||||
access_url(browser, 'tt-rss')
|
||||
overlay = browser.find_by_id('overlay')
|
||||
eventually(lambda: not overlay.visible)
|
||||
|
||||
|
||||
def _ttrss_is_feed_shown(browser, invert=False):
|
||||
return browser.is_text_present('Planet Debian') != invert
|
||||
|
||||
|
||||
def ttrss_subscribe(browser):
|
||||
"""Subscribe to a feed in TT-RSS."""
|
||||
_ttrss_load_main_interface(browser)
|
||||
browser.find_by_text('Actions...').click()
|
||||
browser.find_by_text('Subscribe to feed...').click()
|
||||
browser.find_by_id('feedDlg_feedUrl').fill(
|
||||
'https://planet.debian.org/atom.xml')
|
||||
browser.find_by_text('Subscribe').click()
|
||||
if browser.is_text_present('You are already subscribed to this feed.'):
|
||||
browser.find_by_text('Cancel').click()
|
||||
|
||||
expand = browser.find_by_css('span.dijitTreeExpandoClosed')
|
||||
if expand:
|
||||
expand.first.click()
|
||||
|
||||
assert eventually(_ttrss_is_feed_shown, [browser])
|
||||
|
||||
|
||||
def ttrss_unsubscribe(browser):
|
||||
"""Unsubscribe from a feed in TT-RSS."""
|
||||
_ttrss_load_main_interface(browser)
|
||||
expand = browser.find_by_css('span.dijitTreeExpandoClosed')
|
||||
if expand:
|
||||
expand.first.click()
|
||||
|
||||
browser.find_by_text('Planet Debian').click()
|
||||
browser.execute_script("quickMenuGo('qmcRemoveFeed')")
|
||||
prompt = browser.get_alert()
|
||||
prompt.accept()
|
||||
|
||||
assert eventually(_ttrss_is_feed_shown, [browser, True])
|
||||
|
||||
|
||||
def ttrss_is_subscribed(browser):
|
||||
"""Return whether subscribed to a feed in TT-RSS."""
|
||||
_ttrss_load_main_interface(browser)
|
||||
return browser.is_text_present('Planet Debian')
|
||||
@ -1,417 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import tempfile
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import requests
|
||||
|
||||
from . import application, config
|
||||
from .interface import nav_to_module, submit
|
||||
from .service import wait_for_page_update
|
||||
|
||||
config_page_title_language_map = {
|
||||
'da': 'Generel Konfiguration',
|
||||
'de': 'Allgemeine Konfiguration',
|
||||
'es': 'Configuración general',
|
||||
'fr': 'Configuration générale',
|
||||
'nb': 'Generelt oppsett',
|
||||
'nl': 'Algemene Instellingen',
|
||||
'pl': 'Ustawienia główne',
|
||||
'pt': 'Configuração Geral',
|
||||
'ru': 'Общие настройки',
|
||||
'sv': 'Allmän Konfiguration',
|
||||
'te': 'సాధారణ ఆకృతీకరణ',
|
||||
'tr': 'Genel Yapılandırma',
|
||||
'zh-hans': '常规配置',
|
||||
}
|
||||
|
||||
|
||||
def get_hostname(browser):
|
||||
nav_to_module(browser, 'config')
|
||||
return browser.find_by_id('id_hostname').value
|
||||
|
||||
|
||||
def set_hostname(browser, hostname):
|
||||
nav_to_module(browser, 'config')
|
||||
browser.find_by_id('id_hostname').fill(hostname)
|
||||
submit(browser)
|
||||
|
||||
|
||||
def get_domain_name(browser):
|
||||
nav_to_module(browser, 'config')
|
||||
return browser.find_by_id('id_domainname').value
|
||||
|
||||
|
||||
def set_domain_name(browser, domain_name):
|
||||
nav_to_module(browser, 'config')
|
||||
browser.find_by_id('id_domainname').fill(domain_name)
|
||||
submit(browser)
|
||||
|
||||
|
||||
def set_home_page(browser, home_page):
|
||||
if 'plinth' not in home_page and 'apache' not in home_page:
|
||||
home_page = 'shortcut-' + home_page
|
||||
|
||||
nav_to_module(browser, 'config')
|
||||
drop_down = browser.find_by_id('id_homepage')
|
||||
drop_down.select(home_page)
|
||||
submit(browser)
|
||||
|
||||
|
||||
def set_advanced_mode(browser, mode):
|
||||
nav_to_module(browser, 'config')
|
||||
advanced_mode = browser.find_by_id('id_advanced_mode')
|
||||
if mode:
|
||||
advanced_mode.check()
|
||||
else:
|
||||
advanced_mode.uncheck()
|
||||
|
||||
submit(browser)
|
||||
|
||||
|
||||
def set_language(browser, language_code):
|
||||
username = config['DEFAULT']['username']
|
||||
browser.visit(config['DEFAULT']['url'] +
|
||||
'/plinth/sys/users/{}/edit/'.format(username))
|
||||
browser.find_by_xpath('//select[@id="id_language"]//option[@value="' +
|
||||
language_code + '"]').first.click()
|
||||
submit(browser)
|
||||
|
||||
|
||||
def check_language(browser, language_code):
|
||||
nav_to_module(browser, 'config')
|
||||
return browser.find_by_css('.app-titles').first.find_by_tag(
|
||||
'h2').first.value == config_page_title_language_map[language_code]
|
||||
|
||||
|
||||
def delete_all_snapshots(browser):
|
||||
if get_snapshot_count(browser):
|
||||
browser.find_by_id('select-all').check()
|
||||
submit(browser, browser.find_by_name('delete_selected'))
|
||||
|
||||
confirm_button = browser.find_by_name('delete_confirm')
|
||||
if confirm_button: # Only if redirected to confirm page
|
||||
submit(browser, confirm_button)
|
||||
|
||||
|
||||
def create_snapshot(browser):
|
||||
browser.visit(config['DEFAULT']['url'] + '/plinth/sys/snapshot/manage/')
|
||||
submit(browser) # Click on 'Create Snapshot'
|
||||
|
||||
|
||||
def get_snapshot_count(browser):
|
||||
browser.visit(config['DEFAULT']['url'] + '/plinth/sys/snapshot/manage/')
|
||||
# Subtract 1 for table header
|
||||
return len(browser.find_by_xpath('//tr')) - 1
|
||||
|
||||
|
||||
def snapshot_set_configuration(browser, free_space, timeline_enabled,
|
||||
software_enabled, hourly, daily, weekly,
|
||||
monthly, yearly):
|
||||
"""Set the configuration for snapshots."""
|
||||
nav_to_module(browser, 'snapshot')
|
||||
browser.find_by_name('free_space').select(free_space / 100)
|
||||
browser.find_by_name('enable_timeline_snapshots').select(
|
||||
'yes' if timeline_enabled else 'no')
|
||||
browser.find_by_name('enable_software_snapshots').select(
|
||||
'yes' if software_enabled else 'no')
|
||||
browser.find_by_name('hourly_limit').fill(hourly)
|
||||
browser.find_by_name('daily_limit').fill(daily)
|
||||
browser.find_by_name('weekly_limit').fill(weekly)
|
||||
browser.find_by_name('monthly_limit').fill(monthly)
|
||||
browser.find_by_name('yearly_limit').fill(yearly)
|
||||
submit(browser)
|
||||
|
||||
|
||||
def snapshot_get_configuration(browser):
|
||||
"""Return the current configuration for snapshots."""
|
||||
nav_to_module(browser, 'snapshot')
|
||||
return (int(float(browser.find_by_name('free_space').value) * 100),
|
||||
browser.find_by_name('enable_timeline_snapshots').value == 'yes',
|
||||
browser.find_by_name('enable_software_snapshots').value == 'yes',
|
||||
int(browser.find_by_name('hourly_limit').value),
|
||||
int(browser.find_by_name('daily_limit').value),
|
||||
int(browser.find_by_name('weekly_limit').value),
|
||||
int(browser.find_by_name('monthly_limit').value),
|
||||
int(browser.find_by_name('yearly_limit').value))
|
||||
|
||||
|
||||
def check_home_page_redirect(browser, app_name):
|
||||
browser.visit(config['DEFAULT']['url'])
|
||||
return browser.find_by_xpath(
|
||||
"//a[contains(@href, '/plinth/') and @title='FreedomBox']")
|
||||
|
||||
|
||||
def dynamicdns_configure(browser):
|
||||
nav_to_module(browser, 'dynamicdns')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/sys/dynamicdns/configure/').first.click()
|
||||
browser.find_by_id('id_enabled').check()
|
||||
browser.find_by_id('id_service_type').select('GnuDIP')
|
||||
browser.find_by_id('id_dynamicdns_server').fill('example.com')
|
||||
browser.find_by_id('id_dynamicdns_domain').fill('freedombox.example.com')
|
||||
browser.find_by_id('id_dynamicdns_user').fill('tester')
|
||||
browser.find_by_id('id_dynamicdns_secret').fill('testingtesting')
|
||||
browser.find_by_id('id_dynamicdns_ipurl').fill(
|
||||
'http://myip.datasystems24.de')
|
||||
submit(browser)
|
||||
|
||||
|
||||
def dynamicdns_has_original_config(browser):
|
||||
nav_to_module(browser, 'dynamicdns')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/sys/dynamicdns/configure/').first.click()
|
||||
enabled = browser.find_by_id('id_enabled').value
|
||||
service_type = browser.find_by_id('id_service_type').value
|
||||
server = browser.find_by_id('id_dynamicdns_server').value
|
||||
domain = browser.find_by_id('id_dynamicdns_domain').value
|
||||
user = browser.find_by_id('id_dynamicdns_user').value
|
||||
ipurl = browser.find_by_id('id_dynamicdns_ipurl').value
|
||||
if enabled and service_type == 'GnuDIP' and server == 'example.com' \
|
||||
and domain == 'freedombox.example.com' and user == 'tester' \
|
||||
and ipurl == 'http://myip.datasystems24.de':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def dynamicdns_change_config(browser):
|
||||
nav_to_module(browser, 'dynamicdns')
|
||||
browser.find_link_by_href(
|
||||
'/plinth/sys/dynamicdns/configure/').first.click()
|
||||
browser.find_by_id('id_enabled').check()
|
||||
browser.find_by_id('id_service_type').select('GnuDIP')
|
||||
browser.find_by_id('id_dynamicdns_server').fill('2.example.com')
|
||||
browser.find_by_id('id_dynamicdns_domain').fill('freedombox2.example.com')
|
||||
browser.find_by_id('id_dynamicdns_user').fill('tester2')
|
||||
browser.find_by_id('id_dynamicdns_secret').fill('testingtesting2')
|
||||
browser.find_by_id('id_dynamicdns_ipurl').fill(
|
||||
'http://myip2.datasystems24.de')
|
||||
submit(browser)
|
||||
|
||||
|
||||
def _click_button_and_confirm(browser, href):
|
||||
buttons = browser.find_link_by_href(href)
|
||||
if buttons:
|
||||
buttons.first.click()
|
||||
with wait_for_page_update(browser,
|
||||
expected_url='/plinth/sys/backups/'):
|
||||
submit(browser)
|
||||
|
||||
|
||||
def backup_delete_archive_by_name(browser, archive_name):
|
||||
nav_to_module(browser, 'backups')
|
||||
href = f'/plinth/sys/backups/root/delete/{archive_name}/'
|
||||
_click_button_and_confirm(browser, href)
|
||||
|
||||
|
||||
def backup_create(browser, app_name, archive_name=None):
|
||||
application.install(browser, 'backups')
|
||||
if archive_name:
|
||||
backup_delete_archive_by_name(browser, archive_name)
|
||||
|
||||
browser.find_link_by_href('/plinth/sys/backups/create/').first.click()
|
||||
browser.find_by_id('select-all').uncheck()
|
||||
if archive_name:
|
||||
browser.find_by_id('id_backups-name').fill(archive_name)
|
||||
|
||||
# ensure the checkbox is scrolled into view
|
||||
browser.execute_script('window.scrollTo(0, 0)')
|
||||
browser.find_by_value(app_name).first.check()
|
||||
submit(browser)
|
||||
|
||||
|
||||
def backup_restore(browser, app_name, archive_name=None):
|
||||
nav_to_module(browser, 'backups')
|
||||
href = f'/plinth/sys/backups/root/restore-archive/{archive_name}/'
|
||||
_click_button_and_confirm(browser, href)
|
||||
|
||||
|
||||
def backup_upload_and_restore(browser, app_name, downloaded_file_path):
|
||||
nav_to_module(browser, 'backups')
|
||||
browser.find_link_by_href('/plinth/sys/backups/upload/').first.click()
|
||||
fileinput = browser.driver.find_element_by_id('id_backups-file')
|
||||
fileinput.send_keys(downloaded_file_path)
|
||||
# submit upload form
|
||||
submit(browser)
|
||||
# submit restore form
|
||||
with wait_for_page_update(browser, expected_url='/plinth/sys/backups/'):
|
||||
submit(browser)
|
||||
|
||||
|
||||
def download_backup(browser, archive_name=None):
|
||||
nav_to_module(browser, 'backups')
|
||||
href = f'/plinth/sys/backups/root/download/{archive_name}/'
|
||||
url = config['DEFAULT']['url'] + href
|
||||
file_path = download_file_logged_in(browser, url, suffix='.tar.gz')
|
||||
return file_path
|
||||
|
||||
|
||||
def download_file_logged_in(browser, url, suffix=''):
|
||||
"""Download a file from Plinth, pretend being logged in via cookies"""
|
||||
if not url.startswith("http"):
|
||||
current_url = urlparse(browser.url)
|
||||
url = "%s://%s%s" % (current_url.scheme, current_url.netloc, url)
|
||||
cookies = browser.driver.get_cookies()
|
||||
cookies = {cookie["name"]: cookie["value"] for cookie in cookies}
|
||||
response = requests.get(url, verify=False, cookies=cookies)
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
|
||||
for chunk in response.iter_content(chunk_size=128):
|
||||
temp_file.write(chunk)
|
||||
return temp_file.name
|
||||
|
||||
|
||||
def pagekite_configure(browser, host, port, kite_name, kite_secret):
|
||||
"""Configure pagekite basic parameters."""
|
||||
nav_to_module(browser, 'pagekite')
|
||||
# time.sleep(0.250) # Wait for 200ms show animation to complete
|
||||
browser.fill('pagekite-server_domain', host)
|
||||
browser.fill('pagekite-server_port', str(port))
|
||||
browser.fill('pagekite-kite_name', kite_name)
|
||||
browser.fill('pagekite-kite_secret', kite_secret)
|
||||
submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def pagekite_get_configuration(browser):
|
||||
"""Return pagekite basic parameters."""
|
||||
nav_to_module(browser, 'pagekite')
|
||||
return (browser.find_by_name('pagekite-server_domain').value,
|
||||
int(browser.find_by_name('pagekite-server_port').value),
|
||||
browser.find_by_name('pagekite-kite_name').value,
|
||||
browser.find_by_name('pagekite-kite_secret').value)
|
||||
|
||||
|
||||
def bind_set_forwarders(browser, forwarders):
|
||||
"""Set the forwarders list (space separated) in bind configuration."""
|
||||
nav_to_module(browser, 'bind')
|
||||
browser.fill('forwarders', forwarders)
|
||||
submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def bind_get_forwarders(browser):
|
||||
"""Return the forwarders list (space separated) in bind configuration."""
|
||||
nav_to_module(browser, 'bind')
|
||||
return browser.find_by_name('forwarders').first.value
|
||||
|
||||
|
||||
def bind_enable_dnssec(browser, enable):
|
||||
"""Enable/disable DNSSEC in bind configuration."""
|
||||
nav_to_module(browser, 'bind')
|
||||
if enable:
|
||||
browser.check('enable_dnssec')
|
||||
else:
|
||||
browser.uncheck('enable_dnssec')
|
||||
|
||||
submit(browser, form_class='form-configuration')
|
||||
|
||||
|
||||
def bind_get_dnssec(browser):
|
||||
"""Return whether DNSSEC is enabled/disabled in bind configuration."""
|
||||
nav_to_module(browser, 'bind')
|
||||
return browser.find_by_name('enable_dnssec').first.checked
|
||||
|
||||
|
||||
def security_enable_restricted_logins(browser, should_enable):
|
||||
"""Enable/disable restricted logins in security module."""
|
||||
nav_to_module(browser, 'security')
|
||||
if should_enable:
|
||||
browser.check('security-restricted_access')
|
||||
else:
|
||||
browser.uncheck('security-restricted_access')
|
||||
|
||||
submit(browser)
|
||||
|
||||
|
||||
def security_get_restricted_logins(browser):
|
||||
"""Return whether restricted console logins is enabled."""
|
||||
nav_to_module(browser, 'security')
|
||||
return browser.find_by_name('security-restricted_access').first.checked
|
||||
|
||||
|
||||
def upgrades_enable_automatic(browser, should_enable):
|
||||
"""Enable/disable automatic software upgrades."""
|
||||
nav_to_module(browser, 'upgrades')
|
||||
checkbox_element = browser.find_by_name('auto_upgrades_enabled').first
|
||||
if should_enable == checkbox_element.checked:
|
||||
return
|
||||
|
||||
if should_enable:
|
||||
checkbox_element.check()
|
||||
else:
|
||||
checkbox_element.uncheck()
|
||||
|
||||
submit(browser)
|
||||
|
||||
|
||||
def upgrades_get_automatic(browser):
|
||||
"""Return whether automatic software upgrades is enabled."""
|
||||
nav_to_module(browser, 'upgrades')
|
||||
return browser.find_by_name('auto_upgrades_enabled').first.checked
|
||||
|
||||
|
||||
def _monkeysphere_find_domain(browser, key_type, domain_type, domain):
|
||||
"""Iterate every domain of a given type which given key type."""
|
||||
keys_of_type = browser.find_by_css(
|
||||
'.monkeysphere-service-{}'.format(key_type))
|
||||
for key_of_type in keys_of_type:
|
||||
search_domains = key_of_type.find_by_css(
|
||||
'.monkeysphere-{}-domain'.format(domain_type))
|
||||
for search_domain in search_domains:
|
||||
if search_domain.text == domain:
|
||||
return key_of_type, search_domain
|
||||
|
||||
raise IndexError('Domain not found')
|
||||
|
||||
|
||||
def monkeysphere_import_key(browser, key_type, domain):
|
||||
"""Import a key of specified type for given domain into monkeysphere."""
|
||||
try:
|
||||
monkeysphere_assert_imported_key(browser, key_type, domain)
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
|
||||
key, _ = _monkeysphere_find_domain(browser, key_type, 'importable', domain)
|
||||
with wait_for_page_update(browser):
|
||||
key.find_by_css('.button-import').click()
|
||||
|
||||
|
||||
def monkeysphere_assert_imported_key(browser, key_type, domain):
|
||||
"""Assert that a key of specified type for given domain was imported.."""
|
||||
nav_to_module(browser, 'monkeysphere')
|
||||
return _monkeysphere_find_domain(browser, key_type, 'imported', domain)
|
||||
|
||||
|
||||
def monkeysphere_publish_key(browser, key_type, domain):
|
||||
"""Publish a key of specified type for given domain from monkeysphere."""
|
||||
nav_to_module(browser, 'monkeysphere')
|
||||
key, _ = _monkeysphere_find_domain(browser, key_type, 'imported', domain)
|
||||
with wait_for_page_update(browser):
|
||||
key.find_by_css('.button-publish').click()
|
||||
|
||||
application.wait_for_config_update(browser, 'monkeysphere')
|
||||
|
||||
|
||||
def open_main_page(browser):
|
||||
with wait_for_page_update(browser):
|
||||
browser.find_link_by_href('/plinth/').first.click()
|
||||
|
||||
|
||||
def networks_set_firewall_zone(browser, zone):
|
||||
""""Set the network device firewall zone as internal or external."""
|
||||
nav_to_module(browser, 'networks')
|
||||
device = browser.find_by_xpath(
|
||||
'//span[contains(@class, "label-success") '
|
||||
'and contains(@class, "connection-status-label")]/following::a').first
|
||||
network_id = device['href'].split('/')[-3]
|
||||
device.click()
|
||||
edit_url = "/plinth/sys/networks/{}/edit/".format(network_id)
|
||||
browser.find_link_by_href(edit_url).first.click()
|
||||
browser.select('zone', zone)
|
||||
browser.find_by_tag("form").first.find_by_tag('input')[-1].click()
|
||||
|
||||
|
||||
def storage_is_root_disk_shown(browser):
|
||||
table_cells = browser.find_by_tag('td')
|
||||
return any(cell.text == '/' for cell in table_cells)
|
||||
Loading…
x
Reference in New Issue
Block a user