Merge branch 'master' of git.incorporeal.org:bss/incorporeal-cms
This commit is contained in:
commit
da447d2873
@ -3,7 +3,7 @@ import logging
|
||||
import os
|
||||
from logging.config import dictConfig
|
||||
|
||||
from flask import Flask, request, send_from_directory
|
||||
from flask import Flask, request
|
||||
|
||||
from ._version import get_versions
|
||||
|
||||
@ -40,11 +40,6 @@ def create_app(instance_path=None, test_config=None):
|
||||
logger.info("RESPONSE: %s %s: %s", request.method, request.path, response.status)
|
||||
return response
|
||||
|
||||
@app.route(f'/{app.config["MEDIA_DIR"]}/<path:filename>')
|
||||
def media_files(filename):
|
||||
return send_from_directory(os.path.join(app.instance_path, app.config['MEDIA_DIR']),
|
||||
filename)
|
||||
|
||||
from . import error_pages, pages
|
||||
app.register_blueprint(pages.bp)
|
||||
app.register_error_handler(400, error_pages.bad_request)
|
||||
|
@ -54,3 +54,5 @@ class Config(object):
|
||||
DEFAULT_PAGE_STYLE = 'light'
|
||||
TITLE_SUFFIX = 'incorporeal.org'
|
||||
CONTACT_EMAIL = 'bss@incorporeal.org'
|
||||
|
||||
# specify FAVICON in your instance config.py to override the suou icon
|
||||
|
@ -34,6 +34,7 @@ def render(template_name_or_list, **context):
|
||||
PAGE_STYLES = {
|
||||
'dark': 'css/dark.css',
|
||||
'light': 'css/light.css',
|
||||
'plain': 'css/plain.css',
|
||||
}
|
||||
|
||||
selected_style = request.args.get('style', None)
|
||||
|
@ -6,7 +6,7 @@ import re
|
||||
|
||||
from flask import Blueprint, Markup, abort
|
||||
from flask import current_app as app
|
||||
from flask import redirect, request
|
||||
from flask import redirect, request, send_from_directory
|
||||
from tzlocal import get_localzone
|
||||
|
||||
from incorporealcms.lib import get_meta_str, init_md, render
|
||||
@ -21,8 +21,9 @@ bp = Blueprint('pages', __name__, url_prefix='/')
|
||||
def display_page(path):
|
||||
"""Get the file contents of the requested path and render the file."""
|
||||
try:
|
||||
resolved_path = request_path_to_instance_resource_path(path)
|
||||
logger.debug("received request for path '%s', resolved to '%s'", path, resolved_path)
|
||||
resolved_path, render_type = request_path_to_instance_resource_path(path)
|
||||
logger.debug("received request for path '%s', resolved to '%s', type '%s'",
|
||||
path, resolved_path, render_type)
|
||||
except PermissionError:
|
||||
abort(400)
|
||||
except IsADirectoryError:
|
||||
@ -30,6 +31,14 @@ def display_page(path):
|
||||
except FileNotFoundError:
|
||||
abort(404)
|
||||
|
||||
if render_type == 'file':
|
||||
return send_from_directory(app.instance_path, resolved_path)
|
||||
elif render_type == 'symlink':
|
||||
logger.debug("attempting to redirect path '%s' to reverse of resource '%s'", path, resolved_path)
|
||||
redirect_path = f'/{instance_resource_path_to_request_path(resolved_path)}'
|
||||
logger.debug("redirect path: '%s'", redirect_path)
|
||||
return redirect(redirect_path, code=301)
|
||||
elif render_type == 'markdown':
|
||||
try:
|
||||
with app.open_instance_resource(resolved_path, 'r') as entry_file:
|
||||
mtime = datetime.datetime.fromtimestamp(os.path.getmtime(entry_file.name), get_localzone())
|
||||
@ -50,8 +59,11 @@ def display_page(path):
|
||||
logger.debug("title (potentially derived): %s", page_title)
|
||||
|
||||
return render('base.html', title=page_title, description=get_meta_str(md, 'description'),
|
||||
image=get_meta_str(md, 'image'), base_url=request.base_url, content=content, navs=parent_navs,
|
||||
mtime=mtime.strftime('%Y-%m-%d %H:%M:%S %Z'))
|
||||
image=get_meta_str(md, 'image'), base_url=request.base_url, content=content,
|
||||
navs=parent_navs, mtime=mtime.strftime('%Y-%m-%d %H:%M:%S %Z'))
|
||||
else:
|
||||
logger.exception("unsupported render_type '%s'!?", render_type)
|
||||
abort(500)
|
||||
|
||||
|
||||
def request_path_to_instance_resource_path(path):
|
||||
@ -63,34 +75,52 @@ def request_path_to_instance_resource_path(path):
|
||||
"""
|
||||
# check if the path is allowed
|
||||
base_dir = os.path.realpath(f'{app.instance_path}/pages/')
|
||||
resolved_path = os.path.realpath(os.path.join(base_dir, path))
|
||||
logger.debug("base_dir: %s, constructed resolved_path: %s", base_dir, resolved_path)
|
||||
verbatim_path = os.path.abspath(os.path.join(base_dir, path))
|
||||
resolved_path = os.path.realpath(verbatim_path)
|
||||
logger.debug("base_dir '%s', constructed resolved_path '%s' for path '%s'", base_dir, resolved_path, path)
|
||||
|
||||
# bail if the requested real path isn't inside the base directory
|
||||
if base_dir != os.path.commonpath((base_dir, resolved_path)):
|
||||
logger.warning("client tried to request a path '%s' outside of the base_dir!", path)
|
||||
raise PermissionError
|
||||
|
||||
# if this is a file-like requset but actually a directory, redirect the user
|
||||
# see if we have a real file or if we should infer markdown rendering
|
||||
if os.path.exists(resolved_path):
|
||||
# if this is a file-like request but actually a directory, redirect the user
|
||||
if os.path.isdir(resolved_path) and not path.endswith('/'):
|
||||
logger.info("client requested a path '%s' that is actually a directory", path)
|
||||
raise IsADirectoryError
|
||||
|
||||
# derive the proper markdown file depending on if this is a dir or file
|
||||
if os.path.isdir(resolved_path):
|
||||
absolute_resource = os.path.join(resolved_path, 'index.md')
|
||||
else:
|
||||
absolute_resource = f'{resolved_path}.md'
|
||||
# if the requested path contains a symlink, redirect the user
|
||||
if verbatim_path != resolved_path:
|
||||
logger.info("client requested a path '%s' that is actually a symlink to file '%s'", path, resolved_path)
|
||||
return resolved_path.replace(f'{app.instance_path}{os.path.sep}', ''), 'symlink'
|
||||
|
||||
logger.info("final path = '%s' for request '%s'", absolute_resource, path)
|
||||
# derive the proper markdown or actual file depending on if this is a dir or file
|
||||
if os.path.isdir(resolved_path):
|
||||
resolved_path = os.path.join(resolved_path, 'index.md')
|
||||
return resolved_path.replace(f'{app.instance_path}{os.path.sep}', ''), 'markdown'
|
||||
|
||||
logger.info("final DIRECT path = '%s' for request '%s'", resolved_path, path)
|
||||
return resolved_path.replace(f'{app.instance_path}{os.path.sep}', ''), 'file'
|
||||
|
||||
# if we're here, this isn't direct file access, so try markdown inference
|
||||
verbatim_path = os.path.abspath(os.path.join(base_dir, f'{path}.md'))
|
||||
resolved_path = os.path.realpath(verbatim_path)
|
||||
|
||||
# does the final file actually exist?
|
||||
if not os.path.exists(absolute_resource):
|
||||
logger.warning("requested final path '%s' does not exist!", absolute_resource)
|
||||
if not os.path.exists(resolved_path):
|
||||
logger.warning("requested final path '%s' does not exist!", resolved_path)
|
||||
raise FileNotFoundError
|
||||
|
||||
# check for symlinks
|
||||
if verbatim_path != resolved_path:
|
||||
logger.info("client requested a path '%s' that is actually a symlink to file '%s'", path, resolved_path)
|
||||
return resolved_path.replace(f'{app.instance_path}{os.path.sep}', ''), 'symlink'
|
||||
|
||||
logger.info("final path = '%s' for request '%s'", resolved_path, path)
|
||||
# we checked that the file exists via absolute path, but now we need to give the path relative to instance dir
|
||||
return absolute_resource.replace(f'{app.instance_path}{os.path.sep}', '')
|
||||
return resolved_path.replace(f'{app.instance_path}{os.path.sep}', ''), 'markdown'
|
||||
|
||||
|
||||
def instance_resource_path_to_request_path(path):
|
||||
@ -127,8 +157,19 @@ def generate_parent_navs(path):
|
||||
md = init_md()
|
||||
|
||||
# read the resource
|
||||
try:
|
||||
with app.open_instance_resource(path, 'r') as entry_file:
|
||||
entry = entry_file.read()
|
||||
_ = Markup(md.convert(entry))
|
||||
page_name = " ".join(md.Meta.get('title')) if md.Meta.get('title') else request_path
|
||||
page_name = (" ".join(md.Meta.get('title')) if md.Meta.get('title')
|
||||
else request_path_to_breadcrumb_display(request_path))
|
||||
return generate_parent_navs(parent_resource_path) + [(page_name, request_path)]
|
||||
except FileNotFoundError:
|
||||
return generate_parent_navs(parent_resource_path) + [(request_path, request_path)]
|
||||
|
||||
|
||||
def request_path_to_breadcrumb_display(path):
|
||||
"""Given a request path, e.g. "/foo/bar/baz/", turn it into breadcrumby text "baz"."""
|
||||
undired = path.rstrip('/')
|
||||
leaf = undired[undired.rfind('/'):]
|
||||
return leaf.strip('/')
|
||||
|
@ -1,3 +1,5 @@
|
||||
@import '/static/css/base.css';
|
||||
|
||||
html {
|
||||
color: #CCC;
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
@import '/static/css/base.css';
|
||||
|
||||
html {
|
||||
color: #222;
|
||||
}
|
||||
|
8
incorporealcms/static/css/plain.css
Normal file
8
incorporealcms/static/css/plain.css
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
.img-25 {
|
||||
max-width: 25% !important;
|
||||
}
|
||||
|
||||
.img-50 {
|
||||
max-width: 50% !important;
|
||||
}
|
@ -7,9 +7,8 @@
|
||||
<meta property="og:url" content="{{ base_url }}">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename=user_style) }}">
|
||||
<link rel="icon" href="{{ url_for('static', filename='img/favicon.png') }}">
|
||||
<link rel="icon" href="{% if config.FAVICON %}{{ config.FAVICON }}{% else %}{{ url_for('static', filename='img/favicon.png') }}{% endif %}">
|
||||
|
||||
<div class="site-wrap">
|
||||
{% block header %}
|
||||
@ -23,6 +22,7 @@
|
||||
<div class="styles">
|
||||
<a href="?style=dark">[dark]</a>
|
||||
<a href="?style=light">[light]</a>
|
||||
<a href="?style=plain">[plain]</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -16,7 +16,7 @@ click==7.1.2
|
||||
# via
|
||||
# flask
|
||||
# pip-tools
|
||||
coverage==5.4
|
||||
coverage==5.5
|
||||
# via pytest-cov
|
||||
distlib==0.3.1
|
||||
# via virtualenv
|
||||
@ -30,7 +30,7 @@ flake8-blind-except==0.2.0
|
||||
# via -r requirements/requirements-dev.in
|
||||
flake8-builtins==1.5.3
|
||||
# via -r requirements/requirements-dev.in
|
||||
flake8-docstrings==1.5.0
|
||||
flake8-docstrings==1.6.0
|
||||
# via -r requirements/requirements-dev.in
|
||||
flake8-executable==2.1.1
|
||||
# via -r requirements/requirements-dev.in
|
||||
@ -42,7 +42,7 @@ flake8-logging-format==0.6.0
|
||||
# via -r requirements/requirements-dev.in
|
||||
flake8-mutable==1.2.0
|
||||
# via -r requirements/requirements-dev.in
|
||||
flake8==3.8.4
|
||||
flake8==3.9.1
|
||||
# via
|
||||
# -r requirements/requirements-dev.in
|
||||
# dlint
|
||||
@ -53,19 +53,19 @@ flake8==3.8.4
|
||||
# flake8-mutable
|
||||
flask==1.1.2
|
||||
# via -r requirements/requirements.in
|
||||
gitdb==4.0.5
|
||||
gitdb==4.0.7
|
||||
# via gitpython
|
||||
gitpython==3.1.13
|
||||
gitpython==3.1.14
|
||||
# via bandit
|
||||
iniconfig==1.1.1
|
||||
# via pytest
|
||||
isort==5.7.0
|
||||
isort==5.8.0
|
||||
# via flake8-isort
|
||||
itsdangerous==1.1.0
|
||||
# via flask
|
||||
jinja2==2.11.3
|
||||
# via flask
|
||||
markdown==3.3.3
|
||||
markdown==3.3.4
|
||||
# via
|
||||
# -r requirements/requirements.in
|
||||
# mdx-linkify
|
||||
@ -82,7 +82,9 @@ packaging==20.9
|
||||
# tox
|
||||
pbr==5.5.1
|
||||
# via stevedore
|
||||
pip-tools==5.5.0
|
||||
pep517==0.10.0
|
||||
# via pip-tools
|
||||
pip-tools==6.1.0
|
||||
# via -r requirements/requirements-dev.in
|
||||
pluggy==0.13.1
|
||||
# via
|
||||
@ -92,17 +94,17 @@ py==1.10.0
|
||||
# via
|
||||
# pytest
|
||||
# tox
|
||||
pycodestyle==2.6.0
|
||||
pycodestyle==2.7.0
|
||||
# via flake8
|
||||
pydocstyle==5.1.1
|
||||
pydocstyle==6.0.0
|
||||
# via flake8-docstrings
|
||||
pyflakes==2.2.0
|
||||
pyflakes==2.3.1
|
||||
# via flake8
|
||||
pyparsing==2.4.7
|
||||
# via packaging
|
||||
pytest-cov==2.11.1
|
||||
# via -r requirements/requirements-dev.in
|
||||
pytest==6.2.2
|
||||
pytest==6.2.3
|
||||
# via
|
||||
# -r requirements/requirements-dev.in
|
||||
# pytest-cov
|
||||
@ -116,7 +118,7 @@ six==1.15.0
|
||||
# bleach
|
||||
# tox
|
||||
# virtualenv
|
||||
smmap==3.0.5
|
||||
smmap==4.0.0
|
||||
# via gitdb
|
||||
snowballstemmer==2.1.0
|
||||
# via pydocstyle
|
||||
@ -126,11 +128,12 @@ testfixtures==6.17.1
|
||||
# via flake8-isort
|
||||
toml==0.10.2
|
||||
# via
|
||||
# pep517
|
||||
# pytest
|
||||
# tox
|
||||
tox-wheel==0.6.0
|
||||
# via -r requirements/requirements-dev.in
|
||||
tox==3.22.0
|
||||
tox==3.23.0
|
||||
# via
|
||||
# -r requirements/requirements-dev.in
|
||||
# tox-wheel
|
||||
@ -138,7 +141,7 @@ tzlocal==2.1
|
||||
# via -r requirements/requirements.in
|
||||
versioneer==0.19
|
||||
# via -r requirements/requirements-dev.in
|
||||
virtualenv==20.4.2
|
||||
virtualenv==20.4.3
|
||||
# via tox
|
||||
webencodings==0.5.1
|
||||
# via bleach
|
||||
|
@ -14,7 +14,7 @@ itsdangerous==1.1.0
|
||||
# via flask
|
||||
jinja2==2.11.3
|
||||
# via flask
|
||||
markdown==3.3.3
|
||||
markdown==3.3.4
|
||||
# via
|
||||
# -r requirements/requirements.in
|
||||
# mdx-linkify
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Test page requests."""
|
||||
import re
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
def test_page_that_exists(client):
|
||||
@ -9,6 +10,13 @@ def test_page_that_exists(client):
|
||||
assert b'<h1>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')
|
||||
@ -34,6 +42,24 @@ def test_internal_server_error_serves_error_page(client):
|
||||
assert b'bss@incorporeal.org' 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('/../../')
|
||||
@ -101,6 +127,44 @@ def test_that_page_request_redirects_to_directory(client):
|
||||
"""
|
||||
response = client.get('/subdir')
|
||||
assert response.status_code == 301
|
||||
assert response.location == 'http://localhost/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 == 'http://localhost/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 == 'http://localhost/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 == 'http://localhost/subdir'
|
||||
# sadly, this location also redirects
|
||||
response = client.get('/subdir')
|
||||
assert response.status_code == 301
|
||||
assert response.location == 'http://localhost/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 == 'http://localhost/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):
|
||||
|
BIN
tests/instance/pages/bss-square-no-bg.png
Normal file
BIN
tests/instance/pages/bss-square-no-bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
1
tests/instance/pages/foo.txt
Normal file
1
tests/instance/pages/foo.txt
Normal file
@ -0,0 +1 @@
|
||||
test file
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
1
tests/instance/pages/no-index-dir/page.md
Normal file
1
tests/instance/pages/no-index-dir/page.md
Normal file
@ -0,0 +1 @@
|
||||
this is a test page
|
1
tests/instance/pages/symlink-to-foo.txt
Symbolic link
1
tests/instance/pages/symlink-to-foo.txt
Symbolic link
@ -0,0 +1 @@
|
||||
foo.txt
|
1
tests/instance/pages/symlink-to-no-title.md
Symbolic link
1
tests/instance/pages/symlink-to-no-title.md
Symbolic link
@ -0,0 +1 @@
|
||||
no-title.md
|
1
tests/instance/pages/symlink-to-subdir
Symbolic link
1
tests/instance/pages/symlink-to-subdir
Symbolic link
@ -0,0 +1 @@
|
||||
subdir
|
@ -58,3 +58,13 @@ def test_media_file_access(client):
|
||||
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
|
||||
|
@ -3,7 +3,7 @@ import pytest
|
||||
from werkzeug.http import dump_cookie
|
||||
|
||||
from incorporealcms.pages import (generate_parent_navs, instance_resource_path_to_request_path, render,
|
||||
request_path_to_instance_resource_path)
|
||||
request_path_to_breadcrumb_display, request_path_to_instance_resource_path)
|
||||
|
||||
|
||||
def test_generate_page_navs_index(app):
|
||||
@ -15,13 +15,13 @@ def test_generate_page_navs_index(app):
|
||||
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') == [('incorporeal.org', '/'), ('/subdir/', '/subdir/')]
|
||||
assert generate_parent_navs('pages/subdir/index.md') == [('incorporeal.org', '/'), ('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') == [('incorporeal.org', '/'), ('/subdir/', '/subdir/'),
|
||||
assert generate_parent_navs('pages/subdir/page.md') == [('incorporeal.org', '/'), ('subdir', '/subdir/'),
|
||||
('Page', '/subdir/page')]
|
||||
|
||||
|
||||
@ -31,7 +31,17 @@ def test_generate_page_navs_subdir_with_title_parsing_real_page(app):
|
||||
assert generate_parent_navs('pages/subdir-with-title/page.md') == [
|
||||
('incorporeal.org', '/'),
|
||||
('SUB!', '/subdir-with-title/'),
|
||||
('/subdir-with-title/page', '/subdir-with-title/page')
|
||||
('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') == [
|
||||
('incorporeal.org', '/'),
|
||||
('/no-index-dir/', '/no-index-dir/'),
|
||||
('page', '/no-index-dir/page')
|
||||
]
|
||||
|
||||
|
||||
@ -60,44 +70,45 @@ def test_render_with_no_user_theme(app):
|
||||
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'
|
||||
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'
|
||||
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'
|
||||
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'
|
||||
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')
|
||||
('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'
|
||||
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'
|
||||
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):
|
||||
@ -114,6 +125,41 @@ def test_request_path_to_instance_resource_path_isadirectory_on_file_like_req_fo
|
||||
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():
|
||||
@ -155,10 +201,21 @@ def test_instance_resource_path_to_request_path_on_subdir_and_page(app):
|
||||
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_resource_path_to_request_path(request_path_to_instance_resource_path('index')) == ''
|
||||
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_resource_path_to_request_path(request_path_to_instance_resource_path('no-title')) == 'no-title'
|
||||
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