Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e8377adcf5 | |||
| f4beb15a3b | |||
| da447d2873 | |||
| cde82ab918 | |||
| 1ac13f3b9c | |||
| 6705fa38eb | |||
| 30b79e9dc1 | |||
| 60715a3a5c | |||
| c90f0a3a42 | |||
| 71ead20f3f | |||
| be88c3c1bc | |||
| ced67bec8b | |||
| f46bff6ec6 |
@@ -3,7 +3,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
from logging.config import dictConfig
|
from logging.config import dictConfig
|
||||||
|
|
||||||
from flask import Flask, request, send_from_directory
|
from flask import Flask, request
|
||||||
|
|
||||||
from ._version import get_versions
|
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)
|
logger.info("RESPONSE: %s %s: %s", request.method, request.path, response.status)
|
||||||
return response
|
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
|
from . import error_pages, pages
|
||||||
app.register_blueprint(pages.bp)
|
app.register_blueprint(pages.bp)
|
||||||
app.register_error_handler(400, error_pages.bad_request)
|
app.register_error_handler(400, error_pages.bad_request)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import re
|
|||||||
|
|
||||||
from flask import Blueprint, Markup, abort
|
from flask import Blueprint, Markup, abort
|
||||||
from flask import current_app as app
|
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 tzlocal import get_localzone
|
||||||
|
|
||||||
from incorporealcms.lib import get_meta_str, init_md, render
|
from incorporealcms.lib import get_meta_str, init_md, render
|
||||||
@@ -21,15 +21,24 @@ bp = Blueprint('pages', __name__, url_prefix='/')
|
|||||||
def display_page(path):
|
def display_page(path):
|
||||||
"""Get the file contents of the requested path and render the file."""
|
"""Get the file contents of the requested path and render the file."""
|
||||||
try:
|
try:
|
||||||
resolved_path = request_path_to_instance_resource_path(path)
|
resolved_path, render_type = request_path_to_instance_resource_path(path)
|
||||||
logger.debug("received request for path '%s', resolved to '%s'", path, resolved_path)
|
logger.debug("received request for path '%s', resolved to '%s', type '%s'",
|
||||||
|
path, resolved_path, render_type)
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
abort(400)
|
abort(400)
|
||||||
except IsADirectoryError:
|
except IsADirectoryError:
|
||||||
return redirect(f'{path}/', code=301)
|
return redirect(f'/{path}/', code=301)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
abort(404)
|
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:
|
try:
|
||||||
with app.open_instance_resource(resolved_path, 'r') as entry_file:
|
with app.open_instance_resource(resolved_path, 'r') as entry_file:
|
||||||
mtime = datetime.datetime.fromtimestamp(os.path.getmtime(entry_file.name), get_localzone())
|
mtime = datetime.datetime.fromtimestamp(os.path.getmtime(entry_file.name), get_localzone())
|
||||||
@@ -49,9 +58,14 @@ def display_page(path):
|
|||||||
page_title = f'{page_name} - {app.config["TITLE_SUFFIX"]}' if page_name else app.config['TITLE_SUFFIX']
|
page_title = f'{page_name} - {app.config["TITLE_SUFFIX"]}' if page_name else app.config['TITLE_SUFFIX']
|
||||||
logger.debug("title (potentially derived): %s", page_title)
|
logger.debug("title (potentially derived): %s", page_title)
|
||||||
|
|
||||||
return render('base.html', title=page_title, description=get_meta_str(md, 'description'),
|
template = get_meta_str(md, 'template') if md.Meta.get('template') else 'base.html'
|
||||||
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'))
|
return render(template, 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'))
|
||||||
|
else:
|
||||||
|
logger.exception("unsupported render_type '%s'!?", render_type)
|
||||||
|
abort(500)
|
||||||
|
|
||||||
|
|
||||||
def request_path_to_instance_resource_path(path):
|
def request_path_to_instance_resource_path(path):
|
||||||
@@ -63,34 +77,52 @@ def request_path_to_instance_resource_path(path):
|
|||||||
"""
|
"""
|
||||||
# check if the path is allowed
|
# check if the path is allowed
|
||||||
base_dir = os.path.realpath(f'{app.instance_path}/pages/')
|
base_dir = os.path.realpath(f'{app.instance_path}/pages/')
|
||||||
resolved_path = os.path.realpath(os.path.join(base_dir, path))
|
verbatim_path = os.path.abspath(os.path.join(base_dir, path))
|
||||||
logger.debug("base_dir: %s, constructed resolved_path: %s", base_dir, resolved_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
|
# bail if the requested real path isn't inside the base directory
|
||||||
if base_dir != os.path.commonpath((base_dir, resolved_path)):
|
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)
|
logger.warning("client tried to request a path '%s' outside of the base_dir!", path)
|
||||||
raise PermissionError
|
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('/'):
|
if os.path.isdir(resolved_path) and not path.endswith('/'):
|
||||||
logger.info("client requested a path '%s' that is actually a directory", path)
|
logger.info("client requested a path '%s' that is actually a directory", path)
|
||||||
raise IsADirectoryError
|
raise IsADirectoryError
|
||||||
|
|
||||||
# derive the proper markdown file depending on if this is a dir or file
|
# if the requested path contains a symlink, redirect the user
|
||||||
if os.path.isdir(resolved_path):
|
if verbatim_path != resolved_path:
|
||||||
absolute_resource = os.path.join(resolved_path, 'index.md')
|
logger.info("client requested a path '%s' that is actually a symlink to file '%s'", path, resolved_path)
|
||||||
else:
|
return resolved_path.replace(f'{app.instance_path}{os.path.sep}', ''), 'symlink'
|
||||||
absolute_resource = f'{resolved_path}.md'
|
|
||||||
|
|
||||||
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?
|
# does the final file actually exist?
|
||||||
if not os.path.exists(absolute_resource):
|
if not os.path.exists(resolved_path):
|
||||||
logger.warning("requested final path '%s' does not exist!", absolute_resource)
|
logger.warning("requested final path '%s' does not exist!", resolved_path)
|
||||||
raise FileNotFoundError
|
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
|
# 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):
|
def instance_resource_path_to_request_path(path):
|
||||||
@@ -127,12 +159,15 @@ def generate_parent_navs(path):
|
|||||||
md = init_md()
|
md = init_md()
|
||||||
|
|
||||||
# read the resource
|
# read the resource
|
||||||
|
try:
|
||||||
with app.open_instance_resource(path, 'r') as entry_file:
|
with app.open_instance_resource(path, 'r') as entry_file:
|
||||||
entry = entry_file.read()
|
entry = entry_file.read()
|
||||||
_ = Markup(md.convert(entry))
|
_ = Markup(md.convert(entry))
|
||||||
page_name = (" ".join(md.Meta.get('title')) if md.Meta.get('title')
|
page_name = (" ".join(md.Meta.get('title')) if md.Meta.get('title')
|
||||||
else request_path_to_breadcrumb_display(request_path))
|
else request_path_to_breadcrumb_display(request_path))
|
||||||
return generate_parent_navs(parent_resource_path) + [(page_name, 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):
|
def request_path_to_breadcrumb_display(path):
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ body {
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.site-wrap-wide {
|
||||||
|
max-width: 95pc;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<h1>NOT FOUND</h1>
|
<h1>NOT FOUND</h1>
|
||||||
<p>Sorry, <b><tt>{{ request.path }}</tt></b> does not seem to exist, at least not anymore.</p>
|
<p>Sorry, <b><tt>{{ request.path }}</tt></b> does not seem to exist, at least not anymore.</p>
|
||||||
<p>It's possible you followed a dead link on this site, in which case I would appreciate it if you could email me via:
|
<p>It's possible you followed a dead link on this site, in which case I would appreciate it if you could email me at
|
||||||
{{ config.CONTACT_EMAIL }} and I can take a look. I make an effort to symlink old content to its new location,
|
{{ config.CONTACT_EMAIL }} and I can take a look. I make an effort to symlink old content to its new location,
|
||||||
so old links and URLs should, generally speaking, work.</p>
|
so old links and URLs should, generally speaking, work.</p>
|
||||||
<p>Otherwise, I suggest you go <a href="/">to the index</a> and navigate your way (hopefully) to what
|
<p>Otherwise, I suggest you go <a href="/">to the index</a> and navigate your way (hopefully) to what
|
||||||
|
|||||||
38
incorporealcms/templates/base-wide.html
Normal file
38
incorporealcms/templates/base-wide.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
{% if title %}<meta property="og:title" content="{{ title }}">{% endif %}
|
||||||
|
{% if description %}<meta property="og:description" content="{{ description }}">{% endif %}
|
||||||
|
{% if image %}<meta property="og:image" content="{{ image }}">{% endif %}
|
||||||
|
<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=user_style) }}">
|
||||||
|
<link rel="icon" href="{% if config.FAVICON %}{{ config.FAVICON }}{% else %}{{ url_for('static', filename='img/favicon.png') }}{% endif %}">
|
||||||
|
|
||||||
|
<div class="wide-site-wrap">
|
||||||
|
{% block header %}
|
||||||
|
<div class="header">
|
||||||
|
<div class="nav">
|
||||||
|
{% for nav in navs %}
|
||||||
|
<a href="{{ nav.1 }}">{{ nav.0 }}</a>
|
||||||
|
{% if not loop.last %} » {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="styles">
|
||||||
|
<a href="?style=dark">[dark]</a>
|
||||||
|
<a href="?style=light">[light]</a>
|
||||||
|
<a href="?style=plain">[plain]</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<div class="content">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
<i>Last modified: {{ mtime }}</i>
|
||||||
|
</footer>
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</html>
|
||||||
@@ -16,7 +16,7 @@ click==7.1.2
|
|||||||
# via
|
# via
|
||||||
# flask
|
# flask
|
||||||
# pip-tools
|
# pip-tools
|
||||||
coverage==5.4
|
coverage==5.5
|
||||||
# via pytest-cov
|
# via pytest-cov
|
||||||
distlib==0.3.1
|
distlib==0.3.1
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
@@ -30,7 +30,7 @@ flake8-blind-except==0.2.0
|
|||||||
# via -r requirements/requirements-dev.in
|
# via -r requirements/requirements-dev.in
|
||||||
flake8-builtins==1.5.3
|
flake8-builtins==1.5.3
|
||||||
# via -r requirements/requirements-dev.in
|
# via -r requirements/requirements-dev.in
|
||||||
flake8-docstrings==1.5.0
|
flake8-docstrings==1.6.0
|
||||||
# via -r requirements/requirements-dev.in
|
# via -r requirements/requirements-dev.in
|
||||||
flake8-executable==2.1.1
|
flake8-executable==2.1.1
|
||||||
# via -r requirements/requirements-dev.in
|
# via -r requirements/requirements-dev.in
|
||||||
@@ -42,7 +42,7 @@ flake8-logging-format==0.6.0
|
|||||||
# via -r requirements/requirements-dev.in
|
# via -r requirements/requirements-dev.in
|
||||||
flake8-mutable==1.2.0
|
flake8-mutable==1.2.0
|
||||||
# via -r requirements/requirements-dev.in
|
# via -r requirements/requirements-dev.in
|
||||||
flake8==3.8.4
|
flake8==3.9.1
|
||||||
# via
|
# via
|
||||||
# -r requirements/requirements-dev.in
|
# -r requirements/requirements-dev.in
|
||||||
# dlint
|
# dlint
|
||||||
@@ -53,19 +53,19 @@ flake8==3.8.4
|
|||||||
# flake8-mutable
|
# flake8-mutable
|
||||||
flask==1.1.2
|
flask==1.1.2
|
||||||
# via -r requirements/requirements.in
|
# via -r requirements/requirements.in
|
||||||
gitdb==4.0.5
|
gitdb==4.0.7
|
||||||
# via gitpython
|
# via gitpython
|
||||||
gitpython==3.1.13
|
gitpython==3.1.14
|
||||||
# via bandit
|
# via bandit
|
||||||
iniconfig==1.1.1
|
iniconfig==1.1.1
|
||||||
# via pytest
|
# via pytest
|
||||||
isort==5.7.0
|
isort==5.8.0
|
||||||
# via flake8-isort
|
# via flake8-isort
|
||||||
itsdangerous==1.1.0
|
itsdangerous==1.1.0
|
||||||
# via flask
|
# via flask
|
||||||
jinja2==2.11.3
|
jinja2==2.11.3
|
||||||
# via flask
|
# via flask
|
||||||
markdown==3.3.3
|
markdown==3.3.4
|
||||||
# via
|
# via
|
||||||
# -r requirements/requirements.in
|
# -r requirements/requirements.in
|
||||||
# mdx-linkify
|
# mdx-linkify
|
||||||
@@ -82,7 +82,9 @@ packaging==20.9
|
|||||||
# tox
|
# tox
|
||||||
pbr==5.5.1
|
pbr==5.5.1
|
||||||
# via stevedore
|
# 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
|
# via -r requirements/requirements-dev.in
|
||||||
pluggy==0.13.1
|
pluggy==0.13.1
|
||||||
# via
|
# via
|
||||||
@@ -92,17 +94,17 @@ py==1.10.0
|
|||||||
# via
|
# via
|
||||||
# pytest
|
# pytest
|
||||||
# tox
|
# tox
|
||||||
pycodestyle==2.6.0
|
pycodestyle==2.7.0
|
||||||
# via flake8
|
# via flake8
|
||||||
pydocstyle==5.1.1
|
pydocstyle==6.0.0
|
||||||
# via flake8-docstrings
|
# via flake8-docstrings
|
||||||
pyflakes==2.2.0
|
pyflakes==2.3.1
|
||||||
# via flake8
|
# via flake8
|
||||||
pyparsing==2.4.7
|
pyparsing==2.4.7
|
||||||
# via packaging
|
# via packaging
|
||||||
pytest-cov==2.11.1
|
pytest-cov==2.11.1
|
||||||
# via -r requirements/requirements-dev.in
|
# via -r requirements/requirements-dev.in
|
||||||
pytest==6.2.2
|
pytest==6.2.3
|
||||||
# via
|
# via
|
||||||
# -r requirements/requirements-dev.in
|
# -r requirements/requirements-dev.in
|
||||||
# pytest-cov
|
# pytest-cov
|
||||||
@@ -116,7 +118,7 @@ six==1.15.0
|
|||||||
# bleach
|
# bleach
|
||||||
# tox
|
# tox
|
||||||
# virtualenv
|
# virtualenv
|
||||||
smmap==3.0.5
|
smmap==4.0.0
|
||||||
# via gitdb
|
# via gitdb
|
||||||
snowballstemmer==2.1.0
|
snowballstemmer==2.1.0
|
||||||
# via pydocstyle
|
# via pydocstyle
|
||||||
@@ -126,11 +128,12 @@ testfixtures==6.17.1
|
|||||||
# via flake8-isort
|
# via flake8-isort
|
||||||
toml==0.10.2
|
toml==0.10.2
|
||||||
# via
|
# via
|
||||||
|
# pep517
|
||||||
# pytest
|
# pytest
|
||||||
# tox
|
# tox
|
||||||
tox-wheel==0.6.0
|
tox-wheel==0.6.0
|
||||||
# via -r requirements/requirements-dev.in
|
# via -r requirements/requirements-dev.in
|
||||||
tox==3.22.0
|
tox==3.23.0
|
||||||
# via
|
# via
|
||||||
# -r requirements/requirements-dev.in
|
# -r requirements/requirements-dev.in
|
||||||
# tox-wheel
|
# tox-wheel
|
||||||
@@ -138,7 +141,7 @@ tzlocal==2.1
|
|||||||
# via -r requirements/requirements.in
|
# via -r requirements/requirements.in
|
||||||
versioneer==0.19
|
versioneer==0.19
|
||||||
# via -r requirements/requirements-dev.in
|
# via -r requirements/requirements-dev.in
|
||||||
virtualenv==20.4.2
|
virtualenv==20.4.3
|
||||||
# via tox
|
# via tox
|
||||||
webencodings==0.5.1
|
webencodings==0.5.1
|
||||||
# via bleach
|
# via bleach
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ itsdangerous==1.1.0
|
|||||||
# via flask
|
# via flask
|
||||||
jinja2==2.11.3
|
jinja2==2.11.3
|
||||||
# via flask
|
# via flask
|
||||||
markdown==3.3.3
|
markdown==3.3.4
|
||||||
# via
|
# via
|
||||||
# -r requirements/requirements.in
|
# -r requirements/requirements.in
|
||||||
# mdx-linkify
|
# mdx-linkify
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"""Test page requests."""
|
"""Test page requests."""
|
||||||
import re
|
import re
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
|
||||||
def test_page_that_exists(client):
|
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
|
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):
|
def test_page_that_doesnt_exist(client):
|
||||||
"""Test that the app returns 404 for nonsense requests and they use my error page."""
|
"""Test that the app returns 404 for nonsense requests and they use my error page."""
|
||||||
response = client.get('/ohuesthaoeusth')
|
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
|
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):
|
def test_weird_paths_do_not_get_served(client):
|
||||||
"""Test that we clean up requests as desired."""
|
"""Test that we clean up requests as desired."""
|
||||||
response = client.get('/../../')
|
response = client.get('/../../')
|
||||||
@@ -101,6 +127,44 @@ def test_that_page_request_redirects_to_directory(client):
|
|||||||
"""
|
"""
|
||||||
response = client.get('/subdir')
|
response = client.get('/subdir')
|
||||||
assert response.status_code == 301
|
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):
|
def test_that_dir_request_does_not_redirect(client):
|
||||||
@@ -129,3 +193,13 @@ def test_setting_selected_style_includes_cookie(client):
|
|||||||
assert b'dark.css' in response.data
|
assert b'dark.css' in response.data
|
||||||
assert b'light.css' not in response.data
|
assert b'light.css' not in response.data
|
||||||
assert style_cookie.value == 'dark'
|
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"' in response.data
|
||||||
|
assert b'class="wide-site-wrap"' not in response.data
|
||||||
|
response = client.get('/custom-template')
|
||||||
|
assert b'class="site-wrap"' not in response.data
|
||||||
|
assert b'class="wide-site-wrap"' in response.data
|
||||||
|
|||||||
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 |
3
tests/instance/pages/custom-template.md
Normal file
3
tests/instance/pages/custom-template.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Template: base-wide.html
|
||||||
|
|
||||||
|
testttttttttt
|
||||||
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
|
||||||
@@ -35,6 +35,16 @@ def test_generate_page_navs_subdir_with_title_parsing_real_page(app):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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')
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_render_with_user_dark_theme(app):
|
def test_render_with_user_dark_theme(app):
|
||||||
"""Test that a request with the dark theme selected renders the dark theme."""
|
"""Test that a request with the dark theme selected renders the dark theme."""
|
||||||
cookie = dump_cookie("user-style", 'dark')
|
cookie = dump_cookie("user-style", 'dark')
|
||||||
@@ -60,44 +70,45 @@ def test_render_with_no_user_theme(app):
|
|||||||
def test_request_path_to_instance_resource_path(app):
|
def test_request_path_to_instance_resource_path(app):
|
||||||
"""Test a normal URL request is transformed into the file path."""
|
"""Test a normal URL request is transformed into the file path."""
|
||||||
with app.test_request_context():
|
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):
|
def test_request_path_to_instance_resource_path_direct_file(app):
|
||||||
"""Test a normal URL request is transformed into the file path."""
|
"""Test a normal URL request is transformed into the file path."""
|
||||||
with app.test_request_context():
|
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):
|
def test_request_path_to_instance_resource_path_in_subdir(app):
|
||||||
"""Test a normal URL request is transformed into the file path."""
|
"""Test a normal URL request is transformed into the file path."""
|
||||||
with app.test_request_context():
|
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):
|
def test_request_path_to_instance_resource_path_subdir_index(app):
|
||||||
"""Test a normal URL request is transformed into the file path."""
|
"""Test a normal URL request is transformed into the file path."""
|
||||||
with app.test_request_context():
|
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):
|
def test_request_path_to_instance_resource_path_relatives_walked(app):
|
||||||
"""Test a normal URL request is transformed into the file path."""
|
"""Test a normal URL request is transformed into the file path."""
|
||||||
with app.test_request_context():
|
with app.test_request_context():
|
||||||
assert (request_path_to_instance_resource_path('subdir/more-subdir/../../more-metadata') ==
|
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):
|
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."""
|
"""Test a normal URL request is transformed into the file path."""
|
||||||
with app.test_request_context():
|
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):
|
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."""
|
"""Test a normal URL request is transformed into the file path."""
|
||||||
with app.test_request_context():
|
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):
|
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')
|
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):
|
def test_request_path_to_instance_resource_path_nonexistant_file_errors(app):
|
||||||
"""Test that a request for something not on disk errors."""
|
"""Test that a request for something not on disk errors."""
|
||||||
with app.test_request_context():
|
with app.test_request_context():
|
||||||
@@ -155,13 +201,15 @@ def test_instance_resource_path_to_request_path_on_subdir_and_page(app):
|
|||||||
def test_request_resource_request_root(app):
|
def test_request_resource_request_root(app):
|
||||||
"""Test that a request can resolve to a resource and back to a request."""
|
"""Test that a request can resolve to a resource and back to a request."""
|
||||||
with app.test_request_context():
|
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):
|
def test_request_resource_request_page(app):
|
||||||
"""Test that a request can resolve to a resource and back to a request."""
|
"""Test that a request can resolve to a resource and back to a request."""
|
||||||
with app.test_request_context():
|
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():
|
def test_request_path_to_breadcrumb_display_patterns():
|
||||||
|
|||||||
Reference in New Issue
Block a user