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."""
|
||||
# load the instance config.json, if there is one
|
||||
instance_config = os.path.join(instance_path, 'config.json')
|
||||
if os.path.isfile(instance_config):
|
||||
try:
|
||||
with open(instance_config, 'r') as config:
|
||||
config_dict = json.load(config)
|
||||
cprint(f"splicing {config_dict} into the config", 'yellow')
|
||||
Config.update(config_dict)
|
||||
except OSError:
|
||||
raise ValueError("instance path does not seem to be a site instance!")
|
||||
|
||||
if extra_config:
|
||||
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
|
||||
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.id(f'https://{Config.DOMAIN_NAME}/')
|
||||
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.content(content, type='html')
|
||||
|
||||
if feed_type == 'atom':
|
||||
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:
|
||||
if feed_type == 'rss':
|
||||
try:
|
||||
os.mkdir(os.path.join(dest_dir, 'feed'))
|
||||
except FileExistsError:
|
||||
pass
|
||||
with open(os.path.join(dest_dir, 'feed', 'rss'), 'wb') as feed_file:
|
||||
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):
|
||||
|
@ -63,15 +63,12 @@ def parse_md(path: str):
|
||||
logger.debug("path '%s' read", path)
|
||||
md = init_md()
|
||||
content = Markup(md.convert(entry))
|
||||
except OSError:
|
||||
except (OSError, FileNotFoundError):
|
||||
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
|
||||
|
||||
logger.debug("file metadata: %s", md.Meta)
|
||||
|
||||
@ -84,37 +81,26 @@ def parse_md(path: str):
|
||||
|
||||
def handle_markdown_file_path(path: str) -> str:
|
||||
"""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)
|
||||
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)
|
||||
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'
|
||||
content, md, page_name, page_title, mtime = parse_md(path)
|
||||
parent_navs = generate_parent_navs(path)
|
||||
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'
|
||||
|
||||
# check if this has a HTTP redirect
|
||||
redirect_url = get_meta_str(md, 'redirect') if md.Meta.get('redirect') else None
|
||||
if redirect_url:
|
||||
raise ValueError("redirects in markdown are unsupported!")
|
||||
# check if this has a HTTP redirect
|
||||
redirect_url = get_meta_str(md, 'redirect') if md.Meta.get('redirect') else None
|
||||
if redirect_url:
|
||||
raise NotImplementedError("redirects in markdown are unsupported!")
|
||||
|
||||
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,
|
||||
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'),
|
||||
extra_footer=extra_footer)
|
||||
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,
|
||||
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'),
|
||||
extra_footer=extra_footer)
|
||||
|
||||
|
||||
def generate_parent_navs(path):
|
||||
|
@ -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 pytest
|
||||
|
||||
from incorporealcms import init_instance
|
||||
from incorporealcms.config import Config
|
||||
|
||||
@ -27,3 +29,10 @@ def test_config():
|
||||
|
||||
assert getattr(Config, 'INSTANCE_VALUE', None) == "hi"
|
||||
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 '<description><p>hello</p></description>' 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
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from incorporealcms.markdown import (generate_parent_navs, handle_markdown_file_path,
|
||||
instance_resource_path_to_request_path, parse_md,
|
||||
request_path_to_breadcrumb_display)
|
||||
@ -78,6 +80,14 @@ def test_render_with_default_style_override():
|
||||
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():
|
||||
"""Test 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')
|
||||
assert page_name == '/subdir/'
|
||||
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