Fioddor Superconcentrado aef0dcd381
test: help: Add help view tests
Signed-off-by: Fioddor Superconcentrado <fioddor@gmail.com>
[sunil: Yapf and isort, flak8 warnings, spelling]
[sunil: Drop debugging code]
[sunil: Use pytest parametrize, skip marks]
[sunil: Minor test improvements]
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
2021-09-27 15:10:07 -07:00

276 lines
10 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""Tests for help views.
Design: - Make tests independent from URL policy by using Django names instead
of URLs to call the help module. For this, some additional fixture
work is needed: pytestmark and fixture_app_urls().
Pending: - status log
"""
import pathlib
import subprocess
from unittest.mock import patch
import pytest
from django import urls
from django.conf import settings
from django.http import Http404
from plinth import module_loader
from plinth.modules.help import views
# For all tests, use plinth.urls instead of urls configured for testing
pytestmark = pytest.mark.urls('plinth.urls')
def _is_page(response):
"""Minimal check on help views."""
return (response.status_code == 200 and 'title' in response.context_data
and response['content-type'] == 'text/html; charset=utf-8')
@pytest.fixture(autouse=True, scope='module')
def fixture_app_urls():
"""Make sure app's URLs are part of plinth.urls."""
with patch('plinth.module_loader._modules_to_load', new=[]) as modules, \
patch('plinth.urls.urlpatterns', new=[]):
modules.append('plinth.modules.help')
module_loader.include_urls()
yield
@pytest.mark.parametrize("view_name, view", (
('contribute', views.contribute),
('feedback', views.feedback),
('support', views.support),
('index', views.index),
))
def test_simple_help_pages(rf, view_name, view):
"""Simple common test for certain help views."""
response = view(rf.get(urls.reverse('help:' + view_name)))
assert _is_page(response)
def test_about(rf):
"""Test some expected items in about view."""
manual_url = urls.reverse('help:manual')
response = views.about(rf.get(manual_url))
assert _is_page(response)
for item in ('version', 'new_version', 'os_release'):
assert item in response.context_data
# ---------------------------------------------------------------------------
# Tests for serving the offline user guide ( the "manual")
#
# The manual can be requested:
# - Either complete on a single page or page by page.
# - Specifying (or not) the language.
# - The complete manual can be requested in HTML or PDF formats.
#
# Expected Behaviour Rules:
# - If the page isn't specified, the help module returns the full manual in
# one single page.
# - The help module tries first to return the page in the specified
# language. If not found (either that page doesn't exist or the language
# wasn't secified) it falls back to its twin in the fallback language. If
# it is neither available, it shows a proper error message.
#
# Design Decisions:
# - The PDF manual has a separate function to serve it.
# - The 'Manual' page doesn't exist as such. However there are files named
# 'Manual' containing the complete full manual in one single page.
# - In order to avoid loops, the fallback language is intercepted and
# treated specifically.
# - Problem: Requesting a missing page in a language that happens to be
# the fallback one, missing it would cause the help module to
# redirect to the same page, closing thereby a neverending loop.
# The web served would probably break that loop, but it would
# cause confusion to the user.
# - CI environments don't setup FreedomBox completely. A regular setup run is
# impractically slow (10-15 mins), if even posible. Compiling and deploying
# the manual is just 3 extra lines in .gitlab-ci.yml file:
# - make -C doc
# - mkdir -p /usr/share/freedombox/manual
# - cp -r doc/manual /usr/share/freedombox/manual
# But again, this causes the 4 minutes of test preparation to bump to 6.
# It's not worth for just testing the offline manual, so the tests guess if
# they are running in a restricted environment and skip.
canary = pathlib.Path('doc/manual/en/Coturn.part.html')
TRANSLATIONS = ('es', )
MANUAL_PAGES = ('Apache_userdir', 'APU', 'Backups', 'BananaPro', 'BeagleBone',
'bepasty', 'Bind', 'Calibre', 'Cockpit', 'Configure',
'Contribute', 'Coturn', 'Cubieboard2', 'Cubietruck',
'DateTime', 'Debian', 'Deluge', 'Developer', 'Diagnostics',
'Download', 'DynamicDNS', 'ejabberd', 'Firewall',
'freedombox-manual', 'GettingHelp', 'GitWeb', 'Hardware',
'I2P', 'Ikiwiki', 'Infinoted', 'Introduction', 'JSXC',
'LetsEncrypt', 'Maker', 'MatrixSynapse', 'MediaWiki',
'Minetest', 'MiniDLNA', 'MLDonkey', 'Monkeysphere', 'Mumble',
'NameServices', 'Networks', 'OpenVPN', 'OrangePiZero',
'PageKite', 'pcDuino3', 'Performance', 'PineA64+',
'PioneerEdition', 'Plinth', 'Power', 'Privoxy', 'Quassel',
'QuickStart', 'Radicale', 'RaspberryPi2', 'RaspberryPi3B+',
'RaspberryPi3B', 'RaspberryPi4B', 'ReleaseNotes', 'Rock64',
'RockPro64', 'Roundcube', 'Samba', 'Searx', 'SecureShell',
'Security', 'ServiceDiscovery', 'Shadowsocks', 'Sharing',
'Snapshots', 'Storage', 'Syncthing', 'TinyTinyRSS', 'Tor',
'Transmission', 'Upgrades', 'USBWiFi', 'Users', 'VirtualBox',
'WireGuard')
_restricted_reason = ('Needs installed manual. '
'CI speed-optimized workspace does not provide it.')
not_restricted_environment = pytest.mark.skipif(not canary.exists(),
reason=_restricted_reason)
@pytest.mark.parametrize('lang', (None, '-'))
def test_full_default_manual(rf, lang):
"""Test request for the full default manual.
Expected: Redirect to the full manual in the fallback language.
"""
manual_url = urls.reverse('help:manual')
response = views.manual(rf.get(manual_url), lang=lang)
assert response.status_code == 302
assert response.url == '/help/manual/en/'
# With a language cookie set
request = rf.get(manual_url)
request.COOKIES[settings.LANGUAGE_COOKIE_NAME] = TRANSLATIONS[0]
response = views.manual(request, lang=lang)
assert response.status_code == 302
assert response.url == f'/help/manual/{TRANSLATIONS[0]}/'
@pytest.mark.parametrize('lang', (None, '-'))
def test_default_manual_by_pages(rf, lang):
"""Test page-specific requests for the (default) manual.
Expected: Redirect to their respective twins in the fallback language.
Pending.: Redirect pages with plus-sign '+' in their name.
"""
manual_url = urls.reverse('help:manual')
for page in MANUAL_PAGES:
if '+' in page or 'Manual' in page: # Pine64+ & RaspberryPi3B+
continue
response = views.manual(rf.get(manual_url), lang=lang, page=page)
assert response.status_code == 302
assert response.url == '/help/manual/en/' + page
# With a language cookie set
request = rf.get(manual_url)
request.COOKIES[settings.LANGUAGE_COOKIE_NAME] = TRANSLATIONS[0]
response = views.manual(request, lang=lang, page=page)
assert response.status_code == 302
assert response.url == f'/help/manual/{TRANSLATIONS[0]}/{page}'
@not_restricted_environment
def test_specific_full_manual_translation(rf):
"""Test request for specific translated manuals.
Expected: All return a page.
"""
manual_url = urls.reverse('help:manual')
for lang in ('es', 'en'):
response = views.manual(rf.get(manual_url), lang=lang)
assert _is_page(response)
@not_restricted_environment
def test_specific_manual_translation_by_pages(rf):
"""Test that translated-page-specific requests.
Expected: All known page names return pages.
"""
manual_url = urls.reverse('help:manual')
for lang in ('es', 'en'):
for page in MANUAL_PAGES:
response = views.manual(rf.get(manual_url), page=page, lang=lang)
assert _is_page(response)
@not_restricted_environment
def test_full_manual_requested_by_page_name(rf):
"""Test requests for 'Manual'.
Note: 'Manual' is a file, not a manual page.
Expected: Return a proper not found message (HTTP 404)
Currently: Non fallback languages return a page.
This is wrong, but doesn't cause any harm.
"""
manual_url = urls.reverse('help:manual')
page = 'Manual'
for lang in TRANSLATIONS:
response = views.manual(rf.get(manual_url), page=page, lang=lang)
assert _is_page(response)
with pytest.raises(Http404):
views.manual(rf.get(manual_url), page=page, lang='en')
def test_missing_page(rf):
"""Test requests for missing pages.
Expected:
- Unspecified language: Fall back to its fallback twin.
- Translated languages: Fall back to its fallback twin.
- Fallback language...: Return a proper not found message (HTTP 404)
- Unknown languages...: Fall back to its fallback twin.
"""
manual_url = urls.reverse('help:manual')
page = 'unknown'
for lang in TRANSLATIONS + ('unknown', None):
response = views.manual(rf.get(manual_url), page=page, lang=lang)
assert response.status_code == 302
assert response.url == '/help/manual/en/unknown'
with pytest.raises(Http404):
views.manual(rf.get(manual_url), page=page, lang='en')
@not_restricted_environment
def test_download_full_manual_file(rf, tmp_path):
"""Test download of manual.
Design: - Downloads the default manual, a translated one and the
fallback translation. None should fail. Then compares
them.
- Call diff command for fast comparision. Comparing the
over 10MB bytestrings in python is insanely slow.
"""
def _diff(file_name_a, file_name_b, same):
file_a = tmp_path / file_name_a
file_b = tmp_path / file_name_b
process = subprocess.run(
['diff', '-q', str(file_a), str(file_b)], check=False)
assert bool(process.returncode) != same
url = urls.reverse('help:manual')
manuals = {
'unspecified': rf.get(url),
'translated': rf.get(url, HTTP_ACCEPT_LANGUAGE='es'),
'fallback': rf.get(url, HTTP_ACCEPT_LANGUAGE='en')
}
for name, request in manuals.items():
response = views.download_manual(request)
assert response.status_code == 200
file = tmp_path / (name + '.pdf')
file.write_bytes(response.content)
_diff('fallback.pdf', 'unspecified.pdf', same=True)
_diff('fallback.pdf', 'translated.pdf', same=False)