static site generator part 6 --- start testing stuff
Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
This commit is contained in:
parent
746314f4ed
commit
76b1800155
@ -8,16 +8,10 @@ import logging
|
||||
import os
|
||||
from logging.config import dictConfig
|
||||
|
||||
from jinja2 import Environment, PackageLoader, select_autoescape
|
||||
from termcolor import cprint
|
||||
|
||||
from incorporealcms.config import Config
|
||||
|
||||
env = Environment(
|
||||
loader=PackageLoader('incorporealcms'),
|
||||
autoescape=select_autoescape(),
|
||||
)
|
||||
|
||||
|
||||
def init_instance(instance_path: str, extra_config: dict = None):
|
||||
"""Create the instance context, with allowances for customizing path and test settings."""
|
||||
@ -30,6 +24,7 @@ def init_instance(instance_path: str, extra_config: dict = None):
|
||||
Config.update(config_dict)
|
||||
|
||||
if extra_config:
|
||||
cprint(f"splicing {extra_config} into the config", 'yellow')
|
||||
Config.update(extra_config)
|
||||
|
||||
# stash some stuff
|
||||
|
@ -67,7 +67,7 @@ class Config(object):
|
||||
# feed settings
|
||||
AUTHOR = {'name': 'Test Name', 'email': 'admin@example.com'}
|
||||
|
||||
# specify FAVICON in your instance config.py to override the provided icon
|
||||
FAVICON = '/static/img/favicon.png'
|
||||
|
||||
@classmethod
|
||||
def update(cls, config: dict):
|
||||
|
@ -9,13 +9,18 @@ import os
|
||||
import re
|
||||
|
||||
import markdown
|
||||
from jinja2 import Environment, PackageLoader, select_autoescape
|
||||
from markupsafe import Markup
|
||||
|
||||
from incorporealcms import env
|
||||
from incorporealcms.config import Config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
jinja_env = Environment(
|
||||
loader=PackageLoader('incorporealcms'),
|
||||
autoescape=select_autoescape(),
|
||||
)
|
||||
|
||||
|
||||
def get_meta_str(md, key):
|
||||
"""Provide the page's (parsed in Markup obj md) metadata for the specified key, or '' if unset."""
|
||||
@ -96,15 +101,12 @@ def handle_markdown_file_path(path: str) -> str:
|
||||
if redirect_url:
|
||||
raise ValueError("redirects in markdown are unsupported!")
|
||||
|
||||
template = env.get_template(template_name)
|
||||
template = jinja_env.get_template(template_name)
|
||||
return template.render(title=page_title,
|
||||
config=Config,
|
||||
description=get_meta_str(md, 'description'),
|
||||
image=get_meta_str(md, 'image'),
|
||||
content=content,
|
||||
stylesheet=(Config.DEFAULT_PAGE_STYLE,
|
||||
Config.PAGE_STYLES.get(Config.DEFAULT_PAGE_STYLE)),
|
||||
page_styles=Config.PAGE_STYLES,
|
||||
base_url=Config.BASE_HOST + instance_resource_path_to_request_path(path),
|
||||
navs=parent_navs,
|
||||
mtime=mtime.strftime('%Y-%m-%d %H:%M:%S %Z'),
|
||||
|
@ -13,11 +13,11 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
{% if image %}<meta property="og:image" content="{{ image }}">{% endif %}
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" title="{{ stylesheet.0 }}" href="{{ stylesheet.1 }}">
|
||||
{% for style, stylesheet in page_styles.items() %}
|
||||
<link rel="alternate stylesheet" type="text/css" title="{{ style }}" href="{{ stylesheet }}" />
|
||||
<link rel="stylesheet" type="text/css" title="{{ config.DEFAULT_PAGE_STYLE }}" href="{{ config.PAGE_STYLES[config.DEFAULT_PAGE_STYLE] }}">
|
||||
{% for style, stylesheet in config.PAGE_STYLES.items() %}
|
||||
<link rel="alternate stylesheet" type="text/css" title="{{ style }}" href="{{ stylesheet }}">
|
||||
{% endfor %}
|
||||
<link rel="icon" href="{% if config.FAVICON %}{{ config.FAVICON }}{% else %}/static/img/favicon.png{% endif %}">
|
||||
<link rel="icon" href="{{ config.FAVICON }}">
|
||||
<link rel="alternate" type="application/atom+xml" href="/feed/atom">
|
||||
<link rel="alternate" type="application/rss+xml" href="/feed/rss">
|
||||
<script type="text/javascript">
|
||||
|
@ -1,26 +0,0 @@
|
||||
"""Create the test app and other fixtures.
|
||||
|
||||
SPDX-FileCopyrightText: © 2020 Brian S. Stephan <bss@incorporeal.org>
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from incorporealcms import create_app
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
"""Create the Flask application, with test settings."""
|
||||
app = create_app(instance_path=os.path.join(HERE, 'instance'))
|
||||
|
||||
yield app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(app):
|
||||
"""Create a test client based on the test app."""
|
||||
return app.test_client()
|
@ -1,244 +0,0 @@
|
||||
"""Test page requests.
|
||||
|
||||
SPDX-FileCopyrightText: © 2020 Brian S. Stephan <bss@incorporeal.org>
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
import re
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
def test_page_that_exists(client):
|
||||
"""Test that the app can serve a basic file at the index."""
|
||||
response = client.get('/')
|
||||
assert response.status_code == 200
|
||||
assert b'<h1 id="test-index">test index</h1>' in response.data
|
||||
|
||||
|
||||
def test_direct_file_that_exists(client):
|
||||
"""Test that the app can serve a basic file at the index."""
|
||||
response = client.get('/foo.txt')
|
||||
assert response.status_code == 200
|
||||
assert b'test file' in response.data
|
||||
|
||||
|
||||
def test_page_that_doesnt_exist(client):
|
||||
"""Test that the app returns 404 for nonsense requests and they use my error page."""
|
||||
response = client.get('/ohuesthaoeusth')
|
||||
assert response.status_code == 404
|
||||
assert b'<b><tt>/ohuesthaoeusth</tt></b> does not seem to exist' in response.data
|
||||
# test the contact email config
|
||||
assert b'admin@example.com' in response.data
|
||||
|
||||
|
||||
def test_files_outside_pages_do_not_get_served(client):
|
||||
"""Test that page pathing doesn't break out of the instance/pages/ dir, and the error uses my error page."""
|
||||
response = client.get('/../unreachable')
|
||||
assert response.status_code == 400
|
||||
assert b'You\'re doing something you\'re not supposed to. Stop it?' in response.data
|
||||
|
||||
|
||||
def test_internal_server_error_serves_error_page(client):
|
||||
"""Test that various exceptions serve up the 500 page."""
|
||||
response = client.get('/actually-a-png')
|
||||
assert response.status_code == 500
|
||||
assert b'INTERNAL SERVER ERROR' in response.data
|
||||
# test the contact email config
|
||||
assert b'admin@example.com' in response.data
|
||||
|
||||
|
||||
def test_oserror_is_500(client, app):
|
||||
"""Test that an OSError raises as a 500."""
|
||||
with app.test_request_context():
|
||||
with patch('flask.current_app.open_instance_resource', side_effect=OSError):
|
||||
response = client.get('/')
|
||||
assert response.status_code == 500
|
||||
assert b'INTERNAL SERVER ERROR' in response.data
|
||||
|
||||
|
||||
def test_unsupported_file_type_is_500(client, app):
|
||||
"""Test a coding condition mishap raises as a 500."""
|
||||
with app.test_request_context():
|
||||
with patch('incorporealcms.pages.request_path_to_instance_resource_path', return_value=('foo', 'bar')):
|
||||
response = client.get('/')
|
||||
assert response.status_code == 500
|
||||
assert b'INTERNAL SERVER ERROR' in response.data
|
||||
|
||||
|
||||
def test_weird_paths_do_not_get_served(client):
|
||||
"""Test that we clean up requests as desired."""
|
||||
response = client.get('/../../')
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_page_with_title_metadata(client):
|
||||
"""Test that a page with title metadata has its title written."""
|
||||
response = client.get('/')
|
||||
assert response.status_code == 200
|
||||
assert b'<title>Index - example.com</title>' in response.data
|
||||
|
||||
|
||||
def test_page_without_title_metadata(client):
|
||||
"""Test that a page without title metadata gets the default title."""
|
||||
response = client.get('/no-title')
|
||||
assert response.status_code == 200
|
||||
assert b'<title>/no-title - example.com</title>' in response.data
|
||||
|
||||
|
||||
def test_page_in_subdir_without_title_metadata(client):
|
||||
"""Test that the title-less page display is as expected."""
|
||||
response = client.get('/subdir//page-no-title')
|
||||
assert response.status_code == 200
|
||||
assert b'<title>/subdir/page-no-title - example.com</title>' in response.data
|
||||
|
||||
|
||||
def test_page_with_card_metadata(client):
|
||||
"""Test that a page with opengraph metadata."""
|
||||
response = client.get('/more-metadata')
|
||||
assert response.status_code == 200
|
||||
assert b'<meta property="og:title" content="title for the page - example.com">' in response.data
|
||||
assert b'<meta property="og:description" content="description of this page made even longer">' in response.data
|
||||
assert b'<meta property="og:image" content="http://buh.com/test.img">' in response.data
|
||||
|
||||
|
||||
def test_page_with_card_title_even_when_no_metadata(client):
|
||||
"""Test that a page without metadata still has a card with the derived title."""
|
||||
response = client.get('/no-title')
|
||||
assert response.status_code == 200
|
||||
assert b'<meta property="og:title" content="/no-title - example.com">' in response.data
|
||||
assert b'<meta property="og:description"' not in response.data
|
||||
assert b'<meta property="og:image"' not in response.data
|
||||
|
||||
|
||||
def test_page_with_forced_empty_title_just_shows_suffix(client):
|
||||
"""Test that if a page specifies a blank Title meta tag explicitly, only the suffix is used in the title."""
|
||||
response = client.get('/forced-no-title')
|
||||
assert response.status_code == 200
|
||||
assert b'<title>example.com</title>' in response.data
|
||||
|
||||
|
||||
def test_page_with_redirect_meta_url_redirects(client):
|
||||
"""Test that if a page specifies a URL to redirect to, that the site serves up a 301."""
|
||||
response = client.get('/redirect')
|
||||
assert response.status_code == 301
|
||||
assert response.location == 'http://www.google.com/'
|
||||
|
||||
|
||||
def test_page_has_modified_timestamp(client):
|
||||
"""Test that pages have modified timestamps in them."""
|
||||
response = client.get('/')
|
||||
assert response.status_code == 200
|
||||
assert re.search(r'Last modified: ....-..-.. ..:..:.. ...', response.data.decode()) is not None
|
||||
|
||||
|
||||
def test_that_page_request_redirects_to_directory(client):
|
||||
"""Test that a request to /foo reirects to /foo/, if foo is a directory.
|
||||
|
||||
This might be useful in cases where a formerly page-only page has been
|
||||
converted to a directory with subpages.
|
||||
"""
|
||||
response = client.get('/subdir')
|
||||
assert response.status_code == 301
|
||||
assert response.location == '/subdir/'
|
||||
|
||||
|
||||
def test_that_request_to_symlink_redirects_markdown(client):
|
||||
"""Test that a request to /foo redirects to /what-foo-points-at."""
|
||||
response = client.get('/symlink-to-no-title')
|
||||
assert response.status_code == 301
|
||||
assert response.location == '/no-title'
|
||||
|
||||
|
||||
def test_that_request_to_symlink_redirects_file(client):
|
||||
"""Test that a request to /foo.txt redirects to /what-foo-points-at.txt."""
|
||||
response = client.get('/symlink-to-foo.txt')
|
||||
assert response.status_code == 301
|
||||
assert response.location == '/foo.txt'
|
||||
|
||||
|
||||
def test_that_request_to_symlink_redirects_directory(client):
|
||||
"""Test that a request to /foo/ redirects to /what-foo-points-at/."""
|
||||
response = client.get('/symlink-to-subdir/')
|
||||
assert response.status_code == 301
|
||||
assert response.location == '/subdir'
|
||||
# sadly, this location also redirects
|
||||
response = client.get('/subdir')
|
||||
assert response.status_code == 301
|
||||
assert response.location == '/subdir/'
|
||||
# but we do get there
|
||||
response = client.get('/subdir/')
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_that_request_to_symlink_redirects_subdirectory(client):
|
||||
"""Test that a request to /foo/bar redirects to /what-foo-points-at/bar."""
|
||||
response = client.get('/symlink-to-subdir/page-no-title')
|
||||
assert response.status_code == 301
|
||||
assert response.location == '/subdir/page-no-title'
|
||||
response = client.get('/subdir/page-no-title')
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_that_dir_request_does_not_redirect(client):
|
||||
"""Test that a request to /foo/ serves the index page, if foo is a directory."""
|
||||
response = client.get('/subdir/')
|
||||
assert response.status_code == 200
|
||||
assert b'another page' in response.data
|
||||
|
||||
|
||||
def test_setting_selected_style_includes_cookie(client):
|
||||
"""Test that a request with style=foo sets the cookie and renders appropriately."""
|
||||
response = client.get('/')
|
||||
style_cookie = client.get_cookie('user-style')
|
||||
assert style_cookie is None
|
||||
|
||||
response = client.get('/?style=light')
|
||||
style_cookie = client.get_cookie('user-style')
|
||||
assert response.status_code == 200
|
||||
assert b'/static/css/light.css' in response.data
|
||||
assert b'/static/css/dark.css' not in response.data
|
||||
assert style_cookie.value == 'light'
|
||||
|
||||
response = client.get('/?style=dark')
|
||||
style_cookie = client.get_cookie('user-style')
|
||||
assert response.status_code == 200
|
||||
assert b'/static/css/dark.css' in response.data
|
||||
assert b'/static/css/light.css' not in response.data
|
||||
assert style_cookie.value == 'dark'
|
||||
|
||||
|
||||
def test_pages_can_supply_alternate_templates(client):
|
||||
"""Test that pages can supply templates other than the default."""
|
||||
response = client.get('/')
|
||||
assert b'class="site-wrap site-wrap-normal-width"' in response.data
|
||||
assert b'class="site-wrap site-wrap-double-width"' not in response.data
|
||||
response = client.get('/custom-template')
|
||||
assert b'class="site-wrap site-wrap-normal-width"' not in response.data
|
||||
assert b'class="site-wrap site-wrap-double-width"' in response.data
|
||||
|
||||
|
||||
def test_extra_footer_per_page(client):
|
||||
"""Test that we don't include the extra-footer if there isn't one (or do if there is)."""
|
||||
response = client.get('/')
|
||||
assert b'<div class="extra-footer">' not in response.data
|
||||
response = client.get('/index-but-with-footer')
|
||||
assert b'<div class="extra-footer"><i>ooo <a href="a">a</a></i>' in response.data
|
||||
|
||||
|
||||
def test_serving_static_files(client):
|
||||
"""Test the usage of send_from_directory to serve extra static files."""
|
||||
response = client.get('/custom-static/css/warm.css')
|
||||
assert response.status_code == 200
|
||||
|
||||
# can't serve directories, just files
|
||||
response = client.get('/custom-static/')
|
||||
assert response.status_code == 404
|
||||
response = client.get('/custom-static/css/')
|
||||
assert response.status_code == 404
|
||||
response = client.get('/custom-static/css')
|
||||
assert response.status_code == 404
|
||||
|
||||
# can't serve files that don't exist or bad paths
|
||||
response = client.get('/custom-static/css/cold.css')
|
||||
assert response.status_code == 404
|
||||
response = client.get('/custom-static/css/../../unreachable.md')
|
||||
assert response.status_code == 404
|
@ -23,5 +23,6 @@
|
||||
"handlers": ["console"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"INSTANCE_VALUE": "hi"
|
||||
}
|
||||
|
@ -5,79 +5,19 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
import os
|
||||
|
||||
from incorporealcms import create_app
|
||||
from incorporealcms import init_instance
|
||||
from incorporealcms.config import Config
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def test_config():
|
||||
"""Test create_app without passing test config."""
|
||||
"""Test that the app initialization sets values not normally present in the config."""
|
||||
assert not getattr(Config, 'INSTANCE_VALUE', None)
|
||||
assert not getattr(Config, 'EXTRA_VALUE', None)
|
||||
|
||||
instance_path = os.path.join(HERE, 'instance')
|
||||
assert not create_app(instance_path=instance_path).testing
|
||||
assert create_app(instance_path=instance_path, test_config={"TESTING": True}).testing
|
||||
init_instance(instance_path=instance_path, extra_config={"EXTRA_VALUE": "hello"})
|
||||
|
||||
|
||||
def test_markdown_meta_extension_always():
|
||||
"""Test that the markdown meta extension is always loaded, even if not specified."""
|
||||
app = create_app(instance_path=os.path.join(HERE, 'instance'),
|
||||
test_config={'MARKDOWN_EXTENSIONS': []})
|
||||
client = app.test_client()
|
||||
response = client.get('/')
|
||||
assert response.status_code == 200
|
||||
assert b'<title>Index - example.com</title>' in response.data
|
||||
|
||||
|
||||
def test_custom_markdown_extensions_work():
|
||||
"""Test we can change extensions via config, and that they work.
|
||||
|
||||
This used to test smarty, but that's added by default now, so we test
|
||||
that it can be removed by overriding the option.
|
||||
"""
|
||||
app = create_app(instance_path=os.path.join(HERE, 'instance'))
|
||||
client = app.test_client()
|
||||
response = client.get('/mdash-or-triple-dash')
|
||||
assert response.status_code == 200
|
||||
assert b'word — word' in response.data
|
||||
|
||||
app = create_app(instance_path=os.path.join(HERE, 'instance'),
|
||||
test_config={'MARKDOWN_EXTENSIONS': []})
|
||||
client = app.test_client()
|
||||
response = client.get('/mdash-or-triple-dash')
|
||||
assert response.status_code == 200
|
||||
assert b'word --- word' in response.data
|
||||
|
||||
|
||||
def test_title_override():
|
||||
"""Test that a configuration with a specific title overrides the default."""
|
||||
instance_path = os.path.join(HERE, 'instance')
|
||||
app = create_app(instance_path=instance_path, test_config={'TITLE_SUFFIX': 'suou.net'})
|
||||
client = app.test_client()
|
||||
response = client.get('/no-title')
|
||||
assert response.status_code == 200
|
||||
assert b'<title>/no-title - suou.net</title>' in response.data
|
||||
|
||||
|
||||
def test_media_file_access(client):
|
||||
"""Test that media files are served, and properly."""
|
||||
response = client.get('/media/favicon.png')
|
||||
assert response.status_code == 200
|
||||
assert response.headers['content-type'] == 'image/png'
|
||||
|
||||
|
||||
def test_favicon_override():
|
||||
"""Test that a configuration with a specific favicon overrides the default."""
|
||||
instance_path = os.path.join(HERE, 'instance')
|
||||
app = create_app(instance_path=instance_path, test_config={'FAVICON': '/media/foo.png'})
|
||||
client = app.test_client()
|
||||
response = client.get('/no-title')
|
||||
assert response.status_code == 200
|
||||
assert b'<link rel="icon" href="/media/foo.png">' in response.data
|
||||
|
||||
|
||||
def test_misconfigured_markdown_extensions():
|
||||
"""Test that a misconfigured markdown extensions leads to a 500 at render time."""
|
||||
instance_path = os.path.join(HERE, 'instance')
|
||||
app = create_app(instance_path=instance_path, test_config={'MARKDOWN_EXTENSIONS': 'WRONG'})
|
||||
client = app.test_client()
|
||||
response = client.get('/no-title')
|
||||
assert response.status_code == 500
|
||||
assert getattr(Config, 'INSTANCE_VALUE', None) == "hi"
|
||||
assert getattr(Config, 'EXTRA_VALUE', None) == "hello"
|
||||
|
106
tests/test_markdown.py
Normal file
106
tests/test_markdown.py
Normal file
@ -0,0 +1,106 @@
|
||||
"""Test the conversion of Markdown pages.
|
||||
|
||||
SPDX-FileCopyrightText: © 2025 Brian S. Stephan <bss@incorporeal.org>
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
from incorporealcms.markdown import (generate_parent_navs, handle_markdown_file_path,
|
||||
instance_resource_path_to_request_path, request_path_to_breadcrumb_display)
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
os.chdir(os.path.join(HERE, 'instance/', 'pages/'))
|
||||
|
||||
|
||||
def test_generate_page_navs_index():
|
||||
"""Test that the index page has navs to the root (itself)."""
|
||||
assert generate_parent_navs('index.md') == [('example.org', '/')]
|
||||
|
||||
|
||||
def test_generate_page_navs_subdir_index():
|
||||
"""Test that dir pages have navs to the root and themselves."""
|
||||
assert generate_parent_navs('subdir/index.md') == [('example.org', '/'), ('subdir', '/subdir/')]
|
||||
|
||||
|
||||
def test_generate_page_navs_subdir_real_page():
|
||||
"""Test that real pages have navs to the root, their parent, and themselves."""
|
||||
assert generate_parent_navs('subdir/page.md') == [('example.org', '/'), ('subdir', '/subdir/'),
|
||||
('Page', '/subdir/page')]
|
||||
|
||||
|
||||
def test_generate_page_navs_subdir_with_title_parsing_real_page():
|
||||
"""Test that title metadata is used in the nav text."""
|
||||
assert generate_parent_navs('subdir-with-title/page.md') == [
|
||||
('example.org', '/'),
|
||||
('SUB!', '/subdir-with-title/'),
|
||||
('page', '/subdir-with-title/page')
|
||||
]
|
||||
|
||||
|
||||
def test_generate_page_navs_subdir_with_no_index():
|
||||
"""Test that breadcrumbs still generate even if a subdir doesn't have an index.md."""
|
||||
assert generate_parent_navs('no-index-dir/page.md') == [
|
||||
('example.org', '/'),
|
||||
('/no-index-dir/', '/no-index-dir/'),
|
||||
('page', '/no-index-dir/page')
|
||||
]
|
||||
|
||||
|
||||
def test_page_includes_themes_with_default():
|
||||
"""Test that a request contains the configured themes and sets the default as appropriate."""
|
||||
assert '<link rel="stylesheet" type="text/css" title="light" href="/static/css/light.css">'\
|
||||
in handle_markdown_file_path('index.md')
|
||||
assert '<link rel="alternate stylesheet" type="text/css" title="dark" href="/static/css/dark.css">'\
|
||||
in handle_markdown_file_path('index.md')
|
||||
|
||||
|
||||
def test_render_with_style_overrides():
|
||||
"""Test that the default can be changed."""
|
||||
with patch('incorporealcms.Config.DEFAULT_PAGE_STYLE', 'dark'):
|
||||
assert '<link rel="stylesheet" type="text/css" title="dark" href="/static/css/dark.css">'\
|
||||
in handle_markdown_file_path('index.md')
|
||||
assert '<link rel="alternate stylesheet" type="text/css" title="light" href="/static/css/light.css">'\
|
||||
in handle_markdown_file_path('index.md')
|
||||
|
||||
|
||||
def test_render_with_default_style_override():
|
||||
"""Test that theme overrides work, and if a requested theme doesn't exist, the default is loaded."""
|
||||
with patch('incorporealcms.Config.PAGE_STYLES', {'cool': '/static/css/cool.css',
|
||||
'warm': '/static/css/warm.css'}):
|
||||
with patch('incorporealcms.Config.DEFAULT_PAGE_STYLE', 'warm'):
|
||||
assert '<link rel="stylesheet" type="text/css" title="warm" href="/static/css/warm.css">'\
|
||||
in handle_markdown_file_path('index.md')
|
||||
assert '<link rel="alternate stylesheet" type="text/css" title="cool" href="/static/css/cool.css">'\
|
||||
in handle_markdown_file_path('index.md')
|
||||
assert '<link rel="alternate stylesheet" type="text/css" title="light" href="/static/css/light.css">'\
|
||||
not in handle_markdown_file_path('index.md')
|
||||
|
||||
|
||||
def test_instance_resource_path_to_request_path_on_index():
|
||||
"""Test index.md -> /."""
|
||||
assert instance_resource_path_to_request_path('index.md') == '/'
|
||||
|
||||
|
||||
def test_instance_resource_path_to_request_path_on_page():
|
||||
"""Test no-title.md -> no-title."""
|
||||
assert instance_resource_path_to_request_path('no-title.md') == '/no-title'
|
||||
|
||||
|
||||
def test_instance_resource_path_to_request_path_on_subdir():
|
||||
"""Test subdir/index.md -> subdir/."""
|
||||
assert instance_resource_path_to_request_path('subdir/index.md') == '/subdir/'
|
||||
|
||||
|
||||
def test_instance_resource_path_to_request_path_on_subdir_and_page():
|
||||
"""Test subdir/page.md -> subdir/page."""
|
||||
assert instance_resource_path_to_request_path('subdir/page.md') == '/subdir/page'
|
||||
|
||||
|
||||
def test_request_path_to_breadcrumb_display_patterns():
|
||||
"""Test various conversions from request path to leaf nodes for display in the breadcrumbs."""
|
||||
assert request_path_to_breadcrumb_display('/foo') == 'foo'
|
||||
assert request_path_to_breadcrumb_display('/foo/') == 'foo'
|
||||
assert request_path_to_breadcrumb_display('/foo/bar') == 'bar'
|
||||
assert request_path_to_breadcrumb_display('/foo/bar/') == 'bar'
|
||||
assert request_path_to_breadcrumb_display('/') == ''
|
@ -1,282 +0,0 @@
|
||||
"""Unit test helper methods.
|
||||
|
||||
SPDX-FileCopyrightText: © 2020 Brian S. Stephan <bss@incorporeal.org>
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from werkzeug.http import dump_cookie
|
||||
|
||||
from incorporealcms import create_app
|
||||
from incorporealcms.pages import (generate_parent_navs, instance_resource_path_to_request_path, render,
|
||||
request_path_to_breadcrumb_display, request_path_to_instance_resource_path)
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def test_generate_page_navs_index(app):
|
||||
"""Test that the index page has navs to the root (itself)."""
|
||||
with app.app_context():
|
||||
assert generate_parent_navs('pages/index.md') == [('example.com', '/')]
|
||||
|
||||
|
||||
def test_generate_page_navs_subdir_index(app):
|
||||
"""Test that dir pages have navs to the root and themselves."""
|
||||
with app.app_context():
|
||||
assert generate_parent_navs('pages/subdir/index.md') == [('example.com', '/'), ('subdir', '/subdir/')]
|
||||
|
||||
|
||||
def test_generate_page_navs_subdir_real_page(app):
|
||||
"""Test that real pages have navs to the root, their parent, and themselves."""
|
||||
with app.app_context():
|
||||
assert generate_parent_navs('pages/subdir/page.md') == [('example.com', '/'), ('subdir', '/subdir/'),
|
||||
('Page', '/subdir/page')]
|
||||
|
||||
|
||||
def test_generate_page_navs_subdir_with_title_parsing_real_page(app):
|
||||
"""Test that title metadata is used in the nav text."""
|
||||
with app.app_context():
|
||||
assert generate_parent_navs('pages/subdir-with-title/page.md') == [
|
||||
('example.com', '/'),
|
||||
('SUB!', '/subdir-with-title/'),
|
||||
('page', '/subdir-with-title/page')
|
||||
]
|
||||
|
||||
|
||||
def test_generate_page_navs_subdir_with_no_index(app):
|
||||
"""Test that breadcrumbs still generate even if a subdir doesn't have an index.md."""
|
||||
with app.app_context():
|
||||
assert generate_parent_navs('pages/no-index-dir/page.md') == [
|
||||
('example.com', '/'),
|
||||
('/no-index-dir/', '/no-index-dir/'),
|
||||
('page', '/no-index-dir/page')
|
||||
]
|
||||
|
||||
|
||||
def test_render_with_user_dark_theme(app):
|
||||
"""Test that a request with the dark theme selected renders the dark theme."""
|
||||
cookie = dump_cookie("user-style", 'dark')
|
||||
with app.test_request_context(headers={'COOKIE': cookie}):
|
||||
assert b'/static/css/dark.css' in render('base.html').data
|
||||
assert b'/static/css/light.css' not in render('base.html').data
|
||||
|
||||
|
||||
def test_render_with_user_light_theme(app):
|
||||
"""Test that a request with the light theme selected renders the light theme."""
|
||||
with app.test_request_context():
|
||||
assert b'/static/css/light.css' in render('base.html').data
|
||||
assert b'/static/css/dark.css' not in render('base.html').data
|
||||
|
||||
|
||||
def test_render_with_no_user_theme(app):
|
||||
"""Test that a request with no theme set renders the light theme."""
|
||||
with app.test_request_context():
|
||||
assert b'/static/css/light.css' in render('base.html').data
|
||||
assert b'/static/css/dark.css' not in render('base.html').data
|
||||
|
||||
|
||||
def test_render_with_theme_defaults_affects_html(app):
|
||||
"""Test that the base themes are all that's presented in the HTML."""
|
||||
# test we can remove stuff from the default
|
||||
with app.test_request_context():
|
||||
assert b'?style=light' in render('base.html').data
|
||||
assert b'?style=dark' in render('base.html').data
|
||||
assert b'?style=plain' in render('base.html').data
|
||||
|
||||
|
||||
def test_render_with_theme_overrides_affects_html(app):
|
||||
"""Test that the overridden themes are presented in the HTML."""
|
||||
# test we can remove stuff from the default
|
||||
restyled_app = create_app(instance_path=os.path.join(HERE, 'instance'),
|
||||
test_config={'PAGE_STYLES': {'light': '/static/css/light.css'}})
|
||||
with restyled_app.test_request_context():
|
||||
assert b'?style=light' in render('base.html').data
|
||||
assert b'?style=dark' not in render('base.html').data
|
||||
assert b'?style=plain' not in render('base.html').data
|
||||
|
||||
# test that we can add new stuff too/instead
|
||||
restyled_app = create_app(instance_path=os.path.join(HERE, 'instance'),
|
||||
test_config={'PAGE_STYLES': {'cool': '/static/css/cool.css',
|
||||
'warm': '/static/css/warm.css'},
|
||||
'DEFAULT_PAGE_STYLE': 'warm'})
|
||||
with restyled_app.test_request_context():
|
||||
assert b'?style=cool' in render('base.html').data
|
||||
assert b'?style=warm' in render('base.html').data
|
||||
|
||||
|
||||
def test_render_with_theme_overrides(app):
|
||||
"""Test that the loaded themes can be overridden from the default."""
|
||||
cookie = dump_cookie("user-style", 'cool')
|
||||
restyled_app = create_app(instance_path=os.path.join(HERE, 'instance'),
|
||||
test_config={'PAGE_STYLES': {'cool': '/static/css/cool.css',
|
||||
'warm': '/static/css/warm.css'}})
|
||||
with restyled_app.test_request_context(headers={'COOKIE': cookie}):
|
||||
assert b'/static/css/cool.css' in render('base.html').data
|
||||
assert b'/static/css/warm.css' not in render('base.html').data
|
||||
|
||||
|
||||
def test_render_with_theme_overrides_not_found_is_default(app):
|
||||
"""Test that theme overrides work, and if a requested theme doesn't exist, the default is loaded."""
|
||||
cookie = dump_cookie("user-style", 'nonexistent')
|
||||
restyled_app = create_app(instance_path=os.path.join(HERE, 'instance'),
|
||||
test_config={'PAGE_STYLES': {'cool': '/static/css/cool.css',
|
||||
'warm': '/static/css/warm.css'},
|
||||
'DEFAULT_PAGE_STYLE': 'warm'})
|
||||
with restyled_app.test_request_context(headers={'COOKIE': cookie}):
|
||||
assert b'/static/css/warm.css' in render('base.html').data
|
||||
assert b'/static/css/nonexistent.css' not in render('base.html').data
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path(app):
|
||||
"""Test a normal URL request is transformed into the file path."""
|
||||
with app.test_request_context():
|
||||
assert request_path_to_instance_resource_path('index') == ('pages/index.md', 'markdown')
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_direct_file(app):
|
||||
"""Test a normal URL request is transformed into the file path."""
|
||||
with app.test_request_context():
|
||||
assert request_path_to_instance_resource_path('no-title') == ('pages/no-title.md', 'markdown')
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_in_subdir(app):
|
||||
"""Test a normal URL request is transformed into the file path."""
|
||||
with app.test_request_context():
|
||||
assert request_path_to_instance_resource_path('subdir/page') == ('pages/subdir/page.md', 'markdown')
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_subdir_index(app):
|
||||
"""Test a normal URL request is transformed into the file path."""
|
||||
with app.test_request_context():
|
||||
assert request_path_to_instance_resource_path('subdir/') == ('pages/subdir/index.md', 'markdown')
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_relatives_walked(app):
|
||||
"""Test a normal URL request is transformed into the file path."""
|
||||
with app.test_request_context():
|
||||
assert (request_path_to_instance_resource_path('subdir/more-subdir/../../more-metadata') ==
|
||||
('pages/more-metadata.md', 'markdown'))
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_relatives_walked_indexes_work_too(app):
|
||||
"""Test a normal URL request is transformed into the file path."""
|
||||
with app.test_request_context():
|
||||
assert request_path_to_instance_resource_path('subdir/more-subdir/../../') == ('pages/index.md', 'markdown')
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_relatives_walked_into_subdirs_also_fine(app):
|
||||
"""Test a normal URL request is transformed into the file path."""
|
||||
with app.test_request_context():
|
||||
assert (request_path_to_instance_resource_path('subdir/more-subdir/../../subdir/page') ==
|
||||
('pages/subdir/page.md', 'markdown'))
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_permission_error_on_ref_above_pages(app):
|
||||
"""Test that attempts to get above the base dir ("/../../foo") fail."""
|
||||
with app.test_request_context():
|
||||
with pytest.raises(PermissionError):
|
||||
assert request_path_to_instance_resource_path('../unreachable')
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_isadirectory_on_file_like_req_for_dir(app):
|
||||
"""Test that a request for e.g. '/foo' when foo is a dir indicate to redirect."""
|
||||
with app.test_request_context():
|
||||
with pytest.raises(IsADirectoryError):
|
||||
assert request_path_to_instance_resource_path('subdir')
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_actual_file(app):
|
||||
"""Test that a request for e.g. '/foo.png' when foo.png is a real file works."""
|
||||
with app.test_request_context():
|
||||
assert (request_path_to_instance_resource_path('bss-square-no-bg.png') ==
|
||||
('pages/bss-square-no-bg.png', 'file'))
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_markdown_symlink(app):
|
||||
"""Test that a request for e.g. '/foo' when foo.md is a symlink to another .md file redirects."""
|
||||
with app.test_request_context():
|
||||
assert (request_path_to_instance_resource_path('symlink-to-no-title') ==
|
||||
('pages/no-title.md', 'symlink'))
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_file_symlink(app):
|
||||
"""Test that a request for e.g. '/foo' when foo.txt is a symlink to another .txt file redirects."""
|
||||
with app.test_request_context():
|
||||
assert (request_path_to_instance_resource_path('symlink-to-foo.txt') ==
|
||||
('pages/foo.txt', 'symlink'))
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_dir_symlink(app):
|
||||
"""Test that a request for e.g. '/foo' when /foo is a symlink to /bar redirects."""
|
||||
with app.test_request_context():
|
||||
assert (request_path_to_instance_resource_path('symlink-to-subdir/') ==
|
||||
('pages/subdir', 'symlink'))
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_subdir_symlink(app):
|
||||
"""Test that a request for e.g. '/foo/baz' when /foo is a symlink to /bar redirects."""
|
||||
with app.test_request_context():
|
||||
assert (request_path_to_instance_resource_path('symlink-to-subdir/page-no-title') ==
|
||||
('pages/subdir/page-no-title.md', 'symlink'))
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_nonexistant_file_errors(app):
|
||||
"""Test that a request for something not on disk errors."""
|
||||
with app.test_request_context():
|
||||
with pytest.raises(FileNotFoundError):
|
||||
assert request_path_to_instance_resource_path('nthanpthpnh')
|
||||
|
||||
|
||||
def test_request_path_to_instance_resource_path_absolute_file_errors(app):
|
||||
"""Test that a request for something not on disk errors."""
|
||||
with app.test_request_context():
|
||||
with pytest.raises(PermissionError):
|
||||
assert request_path_to_instance_resource_path('/etc/hosts')
|
||||
|
||||
|
||||
def test_instance_resource_path_to_request_path_on_index(app):
|
||||
"""Test index.md -> /."""
|
||||
with app.test_request_context():
|
||||
assert instance_resource_path_to_request_path('index.md') == ''
|
||||
|
||||
|
||||
def test_instance_resource_path_to_request_path_on_page(app):
|
||||
"""Test no-title.md -> no-title."""
|
||||
with app.test_request_context():
|
||||
assert instance_resource_path_to_request_path('no-title.md') == 'no-title'
|
||||
|
||||
|
||||
def test_instance_resource_path_to_request_path_on_subdir(app):
|
||||
"""Test subdir/index.md -> subdir/."""
|
||||
with app.test_request_context():
|
||||
assert instance_resource_path_to_request_path('subdir/index.md') == 'subdir/'
|
||||
|
||||
|
||||
def test_instance_resource_path_to_request_path_on_subdir_and_page(app):
|
||||
"""Test subdir/page.md -> subdir/page."""
|
||||
with app.test_request_context():
|
||||
assert instance_resource_path_to_request_path('subdir/page.md') == 'subdir/page'
|
||||
|
||||
|
||||
def test_request_resource_request_root(app):
|
||||
"""Test that a request can resolve to a resource and back to a request."""
|
||||
with app.test_request_context():
|
||||
instance_path, _ = request_path_to_instance_resource_path('index')
|
||||
instance_resource_path_to_request_path(instance_path) == ''
|
||||
|
||||
|
||||
def test_request_resource_request_page(app):
|
||||
"""Test that a request can resolve to a resource and back to a request."""
|
||||
with app.test_request_context():
|
||||
instance_path, _ = request_path_to_instance_resource_path('no-title')
|
||||
instance_resource_path_to_request_path(instance_path) == 'no-title'
|
||||
|
||||
|
||||
def test_request_path_to_breadcrumb_display_patterns():
|
||||
"""Test various conversions from request path to leaf nodes for display in the breadcrumbs."""
|
||||
assert request_path_to_breadcrumb_display('/foo') == 'foo'
|
||||
assert request_path_to_breadcrumb_display('/foo/') == 'foo'
|
||||
assert request_path_to_breadcrumb_display('/foo/bar') == 'bar'
|
||||
assert request_path_to_breadcrumb_display('/foo/bar/') == 'bar'
|
||||
assert request_path_to_breadcrumb_display('/') == ''
|
Loading…
x
Reference in New Issue
Block a user