many test fixes and improvements
Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
This commit is contained in:
parent
c8c39befb3
commit
f23154ba95
@ -17,11 +17,13 @@ def init_instance(instance_path: str, extra_config: dict = None):
|
|||||||
"""Create the instance context, with allowances for customizing path and test settings."""
|
"""Create the instance context, with allowances for customizing path and test settings."""
|
||||||
# load the instance config.json, if there is one
|
# load the instance config.json, if there is one
|
||||||
instance_config = os.path.join(instance_path, 'config.json')
|
instance_config = os.path.join(instance_path, 'config.json')
|
||||||
if os.path.isfile(instance_config):
|
try:
|
||||||
with open(instance_config, 'r') as config:
|
with open(instance_config, 'r') as config:
|
||||||
config_dict = json.load(config)
|
config_dict = json.load(config)
|
||||||
cprint(f"splicing {config_dict} into the config", 'yellow')
|
cprint(f"splicing {config_dict} into the config", 'yellow')
|
||||||
Config.update(config_dict)
|
Config.update(config_dict)
|
||||||
|
except OSError:
|
||||||
|
raise ValueError("instance path does not seem to be a site instance!")
|
||||||
|
|
||||||
if extra_config:
|
if extra_config:
|
||||||
cprint(f"splicing {extra_config} into the config", 'yellow')
|
cprint(f"splicing {extra_config} into the config", 'yellow')
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
"""Error page views for 400, 404, etc.
|
|
||||||
|
|
||||||
SPDX-FileCopyrightText: © 2021 Brian S. Stephan <bss@incorporeal.org>
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
"""
|
|
||||||
from incorporealcms.lib import render
|
|
||||||
|
|
||||||
|
|
||||||
def bad_request(error):
|
|
||||||
"""Display 400 error messaging."""
|
|
||||||
return render('400.html'), 400
|
|
||||||
|
|
||||||
|
|
||||||
def internal_server_error(error):
|
|
||||||
"""Display 500 error messaging."""
|
|
||||||
return render('500.html'), 500
|
|
||||||
|
|
||||||
|
|
||||||
def page_not_found(error):
|
|
||||||
"""Display 404 error messaging."""
|
|
||||||
return render('404.html'), 404
|
|
@ -29,9 +29,6 @@ def generate_feed(feed_type: str, instance_dir: str, dest_dir: str) -> None:
|
|||||||
instance_dir: the directory for the instance, containing both the feed dir and pages
|
instance_dir: the directory for the instance, containing both the feed dir and pages
|
||||||
dest_dir: the directory to place the feed subdir and requested feed
|
dest_dir: the directory to place the feed subdir and requested feed
|
||||||
"""
|
"""
|
||||||
if feed_type not in ('atom', 'rss'):
|
|
||||||
raise ValueError(f"unsupported feed type {feed_type}")
|
|
||||||
|
|
||||||
fg = FeedGenerator()
|
fg = FeedGenerator()
|
||||||
fg.id(f'https://{Config.DOMAIN_NAME}/')
|
fg.id(f'https://{Config.DOMAIN_NAME}/')
|
||||||
fg.title(f'{Config.TITLE_SUFFIX}')
|
fg.title(f'{Config.TITLE_SUFFIX}')
|
||||||
@ -62,20 +59,20 @@ def generate_feed(feed_type: str, instance_dir: str, dest_dir: str) -> None:
|
|||||||
fe.link(href=link)
|
fe.link(href=link)
|
||||||
fe.content(content, type='html')
|
fe.content(content, type='html')
|
||||||
|
|
||||||
if feed_type == 'atom':
|
if feed_type == 'rss':
|
||||||
try:
|
|
||||||
os.mkdir(os.path.join(dest_dir, 'feed'))
|
|
||||||
except FileExistsError:
|
|
||||||
pass
|
|
||||||
with open(os.path.join(dest_dir, 'feed', 'atom'), 'wb') as feed_file:
|
|
||||||
feed_file.write(fg.atom_str(pretty=True))
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
os.mkdir(os.path.join(dest_dir, 'feed'))
|
os.mkdir(os.path.join(dest_dir, 'feed'))
|
||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
pass
|
pass
|
||||||
with open(os.path.join(dest_dir, 'feed', 'rss'), 'wb') as feed_file:
|
with open(os.path.join(dest_dir, 'feed', 'rss'), 'wb') as feed_file:
|
||||||
feed_file.write(fg.rss_str(pretty=True))
|
feed_file.write(fg.rss_str(pretty=True))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.mkdir(os.path.join(dest_dir, 'feed'))
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
with open(os.path.join(dest_dir, 'feed', 'atom'), 'wb') as feed_file:
|
||||||
|
feed_file.write(fg.atom_str(pretty=True))
|
||||||
|
|
||||||
|
|
||||||
def _generate_feed_id(feed_entry_path, request_path):
|
def _generate_feed_id(feed_entry_path, request_path):
|
||||||
|
@ -63,15 +63,12 @@ def parse_md(path: str):
|
|||||||
logger.debug("path '%s' read", path)
|
logger.debug("path '%s' read", path)
|
||||||
md = init_md()
|
md = init_md()
|
||||||
content = Markup(md.convert(entry))
|
content = Markup(md.convert(entry))
|
||||||
except OSError:
|
except (OSError, FileNotFoundError):
|
||||||
logger.exception("path '%s' could not be opened!", path)
|
logger.exception("path '%s' could not be opened!", path)
|
||||||
raise
|
raise
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.exception("error parsing/rendering markdown!")
|
logger.exception("error parsing/rendering markdown!")
|
||||||
raise
|
raise
|
||||||
except TypeError:
|
|
||||||
logger.exception("error loading/rendering markdown!")
|
|
||||||
raise
|
|
||||||
|
|
||||||
logger.debug("file metadata: %s", md.Meta)
|
logger.debug("file metadata: %s", md.Meta)
|
||||||
|
|
||||||
@ -84,18 +81,7 @@ def parse_md(path: str):
|
|||||||
|
|
||||||
def handle_markdown_file_path(path: str) -> str:
|
def handle_markdown_file_path(path: str) -> str:
|
||||||
"""Given a location on disk, attempt to open it and render the markdown within."""
|
"""Given a location on disk, attempt to open it and render the markdown within."""
|
||||||
try:
|
|
||||||
content, md, page_name, page_title, mtime = parse_md(path)
|
content, md, page_name, page_title, mtime = parse_md(path)
|
||||||
except OSError:
|
|
||||||
logger.exception("path '%s' could not be opened!", path)
|
|
||||||
raise
|
|
||||||
except ValueError:
|
|
||||||
logger.exception("error parsing/rendering markdown!")
|
|
||||||
raise
|
|
||||||
except TypeError:
|
|
||||||
logger.exception("error loading/rendering markdown!")
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
parent_navs = generate_parent_navs(path)
|
parent_navs = generate_parent_navs(path)
|
||||||
extra_footer = get_meta_str(md, 'footer') if md.Meta.get('footer') else None
|
extra_footer = get_meta_str(md, 'footer') if md.Meta.get('footer') else None
|
||||||
template_name = get_meta_str(md, 'template') if md.Meta.get('template') else 'base.html'
|
template_name = get_meta_str(md, 'template') if md.Meta.get('template') else 'base.html'
|
||||||
@ -103,7 +89,7 @@ def handle_markdown_file_path(path: str) -> str:
|
|||||||
# check if this has a HTTP redirect
|
# check if this has a HTTP redirect
|
||||||
redirect_url = get_meta_str(md, 'redirect') if md.Meta.get('redirect') else None
|
redirect_url = get_meta_str(md, 'redirect') if md.Meta.get('redirect') else None
|
||||||
if redirect_url:
|
if redirect_url:
|
||||||
raise ValueError("redirects in markdown are unsupported!")
|
raise NotImplementedError("redirects in markdown are unsupported!")
|
||||||
|
|
||||||
template = jinja_env.get_template(template_name)
|
template = jinja_env.get_template(template_name)
|
||||||
return template.render(title=page_title,
|
return template.render(title=page_title,
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
"""Serve static files from the instance directory.
|
|
||||||
|
|
||||||
SPDX-FileCopyrightText: © 2022 Brian S. Stephan <bss@incorporeal.org>
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
|
|
||||||
from flask import Blueprint
|
|
||||||
from flask import current_app as app
|
|
||||||
from flask import send_from_directory
|
|
||||||
|
|
||||||
bp = Blueprint('static', __name__, url_prefix='/custom-static')
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<path:name>')
|
|
||||||
def serve_instance_static_file(name):
|
|
||||||
"""Serve a static file from the instance directory, used for customization."""
|
|
||||||
return send_from_directory(os.path.join(app.instance_path, 'custom-static'), name)
|
|
1
tests/instance/broken/redirect.md
Normal file
1
tests/instance/broken/redirect.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
Redirect: http://www.google.com/
|
@ -5,6 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from incorporealcms import init_instance
|
from incorporealcms import init_instance
|
||||||
from incorporealcms.config import Config
|
from incorporealcms.config import Config
|
||||||
|
|
||||||
@ -27,3 +29,10 @@ def test_config():
|
|||||||
|
|
||||||
assert getattr(Config, 'INSTANCE_VALUE', None) == "hi"
|
assert getattr(Config, 'INSTANCE_VALUE', None) == "hi"
|
||||||
assert getattr(Config, 'EXTRA_VALUE', None) == "hello"
|
assert getattr(Config, 'EXTRA_VALUE', None) == "hello"
|
||||||
|
|
||||||
|
|
||||||
|
def test_broken_config():
|
||||||
|
"""Test that the app initialization errors when not given an instance-looking thing."""
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
instance_path = os.path.join(HERE, 'blah')
|
||||||
|
init_instance(instance_path=instance_path)
|
||||||
|
@ -65,3 +65,13 @@ def test_rss_type_generated():
|
|||||||
assert '<guid isPermaLink="false">tag:example.org,2025-03-16:/more-metadata</guid>' in data
|
assert '<guid isPermaLink="false">tag:example.org,2025-03-16:/more-metadata</guid>' in data
|
||||||
assert '<description><p>hello</p></description>' in data
|
assert '<description><p>hello</p></description>' in data
|
||||||
assert '<author>admin@example.org (Test Name)</author>' in data
|
assert '<author>admin@example.org (Test Name)</author>' in data
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_runs_without_error():
|
||||||
|
"""Test that we can run the RSS and Atom feed generators in any order."""
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
src_dir = os.path.join(HERE, 'instance')
|
||||||
|
generate_feed('atom', src_dir, tmpdir)
|
||||||
|
generate_feed('rss', src_dir, tmpdir)
|
||||||
|
generate_feed('atom', src_dir, tmpdir)
|
||||||
|
generate_feed('rss', src_dir, tmpdir)
|
||||||
|
@ -6,6 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||||||
import os
|
import os
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from incorporealcms.markdown import (generate_parent_navs, handle_markdown_file_path,
|
from incorporealcms.markdown import (generate_parent_navs, handle_markdown_file_path,
|
||||||
instance_resource_path_to_request_path, parse_md,
|
instance_resource_path_to_request_path, parse_md,
|
||||||
request_path_to_breadcrumb_display)
|
request_path_to_breadcrumb_display)
|
||||||
@ -78,6 +80,14 @@ def test_render_with_default_style_override():
|
|||||||
not in handle_markdown_file_path('index.md')
|
not in handle_markdown_file_path('index.md')
|
||||||
|
|
||||||
|
|
||||||
|
def test_redirects_error_unsupported():
|
||||||
|
"""Test that we throw a warning about the barely-used Markdown redirect tag, which we can't support via SSG."""
|
||||||
|
os.chdir(os.path.join(HERE, 'instance/', 'broken/'))
|
||||||
|
with pytest.raises(NotImplementedError):
|
||||||
|
handle_markdown_file_path('redirect.md')
|
||||||
|
os.chdir(os.path.join(HERE, 'instance/', 'pages/'))
|
||||||
|
|
||||||
|
|
||||||
def test_instance_resource_path_to_request_path_on_index():
|
def test_instance_resource_path_to_request_path_on_index():
|
||||||
"""Test index.md -> /."""
|
"""Test index.md -> /."""
|
||||||
assert instance_resource_path_to_request_path('index.md') == '/'
|
assert instance_resource_path_to_request_path('index.md') == '/'
|
||||||
@ -126,3 +136,15 @@ def test_parse_md_metadata_no_title_so_path():
|
|||||||
content, md, page_name, page_title, mtime = parse_md('subdir/index.md')
|
content, md, page_name, page_title, mtime = parse_md('subdir/index.md')
|
||||||
assert page_name == '/subdir/'
|
assert page_name == '/subdir/'
|
||||||
assert page_title == '/subdir/ - example.org'
|
assert page_title == '/subdir/ - example.org'
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_md_no_file():
|
||||||
|
"""Test the direct results of parsing a markdown file."""
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
content, md, page_name, page_title, mtime = parse_md('nope.md')
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_md_bad_file():
|
||||||
|
"""Test the direct results of parsing a markdown file."""
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
content, md, page_name, page_title, mtime = parse_md('actually-a-png.md')
|
||||||
|
90
tests/test_ssg.py
Normal file
90
tests/test_ssg.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
"""Test the high level SSG operations.
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: © 2023 Brian S. Stephan <bss@incorporeal.org>
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import incorporealcms.ssg as ssg
|
||||||
|
from incorporealcms import init_instance
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
instance_dir = os.path.join(HERE, 'instance')
|
||||||
|
init_instance(instance_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_copy():
|
||||||
|
"""Test the ability to sync a file to the output dir."""
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
src_dir = os.path.join(HERE, 'instance')
|
||||||
|
generator = ssg.StaticSiteGenerator(src_dir, tmpdir)
|
||||||
|
os.chdir(os.path.join(instance_dir, 'pages'))
|
||||||
|
generator.build_file_in_destination(os.path.join(instance_dir, 'pages'), '', 'no-title.md', tmpdir,
|
||||||
|
True)
|
||||||
|
assert os.path.exists(os.path.join(tmpdir, 'no-title.md'))
|
||||||
|
assert os.path.exists(os.path.join(tmpdir, 'no-title.html'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_copy_no_markdown():
|
||||||
|
"""Test the ability to sync a file to the output dir."""
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
src_dir = os.path.join(HERE, 'instance')
|
||||||
|
generator = ssg.StaticSiteGenerator(src_dir, tmpdir)
|
||||||
|
os.chdir(os.path.join(instance_dir, 'pages'))
|
||||||
|
generator.build_file_in_destination(os.path.join(instance_dir, 'pages'), '', 'no-title.md', tmpdir,
|
||||||
|
False)
|
||||||
|
assert os.path.exists(os.path.join(tmpdir, 'no-title.md'))
|
||||||
|
assert not os.path.exists(os.path.join(tmpdir, 'no-title.html'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_copy_symlink():
|
||||||
|
"""Test the ability to sync a file to the output dir."""
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
src_dir = os.path.join(HERE, 'instance')
|
||||||
|
generator = ssg.StaticSiteGenerator(src_dir, tmpdir)
|
||||||
|
os.chdir(os.path.join(instance_dir, 'pages'))
|
||||||
|
generator.build_file_in_destination(os.path.join(instance_dir, 'pages'), '', 'symlink-to-foo.txt', tmpdir)
|
||||||
|
# need to copy the destination for os.path.exists to be happy with this
|
||||||
|
generator.build_file_in_destination(os.path.join(instance_dir, 'pages'), '', 'foo.txt', tmpdir)
|
||||||
|
assert os.path.exists(os.path.join(tmpdir, 'symlink-to-foo.txt'))
|
||||||
|
assert os.path.islink(os.path.join(tmpdir, 'symlink-to-foo.txt'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_dir_copy():
|
||||||
|
"""Test the ability to sync a directory to the output dir."""
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
src_dir = os.path.join(HERE, 'instance')
|
||||||
|
generator = ssg.StaticSiteGenerator(src_dir, tmpdir)
|
||||||
|
os.chdir(os.path.join(instance_dir, 'pages'))
|
||||||
|
generator.build_subdir_in_destination(os.path.join(instance_dir, 'pages'), '', 'media', tmpdir)
|
||||||
|
assert os.path.exists(os.path.join(tmpdir, 'media'))
|
||||||
|
assert os.path.isdir(os.path.join(tmpdir, 'media'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_dir_copy_symlink():
|
||||||
|
"""Test the ability to sync a directory to the output dir."""
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
src_dir = os.path.join(HERE, 'instance')
|
||||||
|
generator = ssg.StaticSiteGenerator(src_dir, tmpdir)
|
||||||
|
os.chdir(os.path.join(instance_dir, 'pages'))
|
||||||
|
generator.build_subdir_in_destination(os.path.join(instance_dir, 'pages'), '', 'symlink-to-subdir', tmpdir)
|
||||||
|
# need to copy the destination for os.path.exists to be happy with this
|
||||||
|
generator.build_subdir_in_destination(os.path.join(instance_dir, 'pages'), '', 'subdir', tmpdir)
|
||||||
|
assert os.path.exists(os.path.join(tmpdir, 'symlink-to-subdir'))
|
||||||
|
assert os.path.isdir(os.path.join(tmpdir, 'symlink-to-subdir'))
|
||||||
|
assert os.path.islink(os.path.join(tmpdir, 'symlink-to-subdir'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_in_destination():
|
||||||
|
"""Test the ability to walk a source and populate the destination."""
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
src_dir = os.path.join(HERE, 'instance')
|
||||||
|
generator = ssg.StaticSiteGenerator(src_dir, tmpdir)
|
||||||
|
generator.build_in_destination(os.path.join(src_dir, 'pages'), tmpdir)
|
||||||
|
|
||||||
|
assert os.path.exists(os.path.join(tmpdir, 'index.md'))
|
||||||
|
assert os.path.exists(os.path.join(tmpdir, 'index.html'))
|
||||||
|
assert os.path.exists(os.path.join(tmpdir, 'subdir', 'index.md'))
|
||||||
|
assert os.path.exists(os.path.join(tmpdir, 'subdir', 'index.html'))
|
Loading…
x
Reference in New Issue
Block a user