Exemplo n.º 1
0
def test_get_assistants(
    mocker: MockerFixture,
    make_entry_point: Type[MockEntryPoint],
    root: Path,
    config: Config,
) -> None:
    """
    Test ``get_assistants``.
    """
    DummyAssistant = make_dummy_assistant("DummyAssistant", [Scope.SITE])
    entry_points = [
        make_entry_point("dummy_assistant", DummyAssistant),
    ]
    mocker.patch(
        "nefelibata.assistants.base.iter_entry_points",
        return_value=entry_points,
    )

    config.assistants = {
        "dummy_assistant": AssistantModel(**{
            "plugin": "dummy_assistant",
        }),
    }
    assistants = get_assistants(root, config, Scope.SITE)
    assert len(assistants) == 1

    assistants = get_assistants(root, config, Scope.POST)
    assert len(assistants) == 0
Exemplo n.º 2
0
async def test_run_with_type(
    monkeypatch: pytest.MonkeyPatch,
    mocker: MockerFixture,
    root: Path,
    config: Config,
) -> None:
    """
    Test creating a new post with a valid type.
    """
    monkeypatch.setenv("EDITOR", "")
    config.templates = {
        "book": ["title", "author"],
    }
    mocker.patch("nefelibata.cli.new.get_config", return_value=config)

    await new.run(root, "A book I liked", "book")

    filepath = root / "posts/a_book_i_liked/index.mkd"
    with open(filepath, encoding="utf-8") as input_:
        content = input_.read()
    assert content == (
        "subject: A book I liked\n"
        "summary: \n"
        "keywords: \n"
        "type: book\n"
        "book-title: \n"
        "book-author: \n"
        "\n\n"
    )
Exemplo n.º 3
0
def config(fs: FakeFilesystem, root: Path) -> Iterator[Config]:
    """
    Create configuration file.
    """
    fs.create_file(root / CONFIG_FILENAME, contents=yaml.dump(CONFIG))

    yield Config(**CONFIG)
Exemplo n.º 4
0
def test_get_config(root: Path, config: Config) -> None:
    """
    Test ``get_config``.
    """
    config = get_config(root)
    assert config.dict() == {
        "author": {
            "email": "*****@*****.**",
            "name": "Beto Dealmeida",
            "note": "Este, sou eu",
            "url": "https://taoetc.org/",
        },
        "title": "道&c.",
        "subtitle": "Musings about the path and other things",
        "language": "en",
        "categories": {
            "stem": {
                "description": "Science, technology, engineering, & math",
                "label": "STEM",
                "tags": ["blog", "programming"],
            },
        },
        "announcers": {
            "announcer": {
                "plugin": "announcer"
            }
        },
        "assistants": {
            "assistant": {
                "plugin": "assistant"
            }
        },
        "builders": {
            "builder": {
                "announce_on": ["announcer"],
                "home": "https://example.com/",
                "path": "generic",
                "plugin": "builder",
                "publish_to": ["publisher"],
            },
        },
        "publishers": {
            "publisher": {
                "plugin": "publisher"
            }
        },
        "social": [{
            "title": "My page",
            "url": "https://example.com/user"
        }],
        "templates": {
            "short": []
        },
    }

    with pytest.raises(SystemExit) as excinfo:
        get_config(Path("/path/to"))
    assert str(excinfo.value) == "No configuration found!"
Exemplo n.º 5
0
def test_get_announcers(
    mocker: MockerFixture,
    make_entry_point: Type[MockEntryPoint],
    root: Path,
    config: Config,
) -> None:
    """
    Test ``get_announcers``.
    """
    DummyAnnouncer = make_dummy_announcer("DummyAnnouncer", [Scope.SITE])
    entry_points = [
        make_entry_point("dummy_announcer", DummyAnnouncer),
    ]
    mocker.patch(
        "nefelibata.announcers.base.iter_entry_points",
        return_value=entry_points,
    )
    mocker.patch(
        "nefelibata.announcers.base.get_builders",
    )

    config.builders = {
        "site_builder": BuilderModel(
            **{
                "plugin": "site_builder",
                "announce-on": ["dummy_announcer"],
                "publish-to": [],
                "home": "https://example.com/",
                "path": "generic",
            }
        ),
    }
    config.announcers = {
        "dummy_announcer": AnnouncerModel(
            **{
                "plugin": "dummy_announcer",
            }
        ),
    }
    announcers = get_announcers(root, config, Scope.SITE)
    assert len(announcers) == 1

    announcers = get_announcers(root, config, Scope.POST)
    assert len(announcers) == 0
Exemplo n.º 6
0
async def test_build_post(fs: FakeFilesystem, root: Path,
                          config: Config) -> None:
    """
    Test ``build_post``.
    """
    path = Path(root / "posts/first/index.mkd")

    # create post
    with freeze_time("2021-01-01T00:00:00Z"):
        fs.create_file(path, contents=POST_CONTENT)
        local_date = formatdate(1609459200.0, localtime=True)

    config.announcers = {"antenna": AnnouncerModel(plugin="antenna")}

    post = build_post(root, config, path)

    assert post.path == path
    assert post.title == "This is your first post"
    assert post.timestamp == datetime(2021, 1, 1, 0, 0, tzinfo=timezone.utc)
    assert post.metadata == {
        "keywords": "welcome, blog",
        "summary": "Hello, world!",
    }
    assert post.tags == {"welcome", "blog"}
    assert post.categories == {"stem"}
    assert post.type == "post"
    assert post.url == "first/index"
    assert (post.content == """# Welcome #

This is your first post. It should be written using Markdown.

Read more about [Nefelibata](https://nefelibata.readthedocs.io/).""")
    assert post.announcers == {"antenna"}

    # check to file was updated with the ``date`` header
    with open(path, encoding="utf-8") as input_:
        content = input_.read()
    assert (content == f"""subject: This is your first post
keywords: welcome, blog
summary: Hello, world!
date: {local_date}
announce-on: antenna

# Welcome #

This is your first post. It should be written using Markdown.

Read more about [Nefelibata](https://nefelibata.readthedocs.io/).""")

    # build again, and test that the file wasn't modified since it
    # already has all the required headers
    last_update = path.stat().st_mtime
    build_post(root, config, path)
    assert path.stat().st_mtime == last_update
Exemplo n.º 7
0
def test_get_publishers(
    mocker: MockerFixture,
    make_entry_point: Type[MockEntryPoint],
    root: Path,
    config: Config,
) -> None:
    """
    Test ``get_publishers``.
    """
    class DummyPublisher(Publisher):
        """
        A dummy publisher.
        """
        async def publish(
            self,
            since: Optional[datetime] = None,
            force: bool = False,
        ) -> None:
            pass

    entry_points = [make_entry_point("dummy_publisher", DummyPublisher)]
    mocker.patch(
        "nefelibata.publishers.base.iter_entry_points",
        return_value=entry_points,
    )

    config.publishers = {"dummy": PublisherModel(plugin="dummy_publisher")}
    config.builders = {
        "site_builder":
        BuilderModel(
            **{
                "plugin": "site_builder",
                "announce-on": [],
                "publish-to": ["dummy"],
                "home": "https://example.com/",
                "path": "site",
            }),
    }
    publishers = get_publishers(root, config)
    assert len(publishers) == 1
    assert isinstance(publishers["site_builder => dummy"], DummyPublisher)
Exemplo n.º 8
0
def get_config(root: Path) -> Config:
    """
    Return the configuration for a blog.
    """
    path = root / CONFIG_FILENAME
    if not path.exists():
        raise SystemExit("No configuration found!")

    with open(path, encoding="utf-8") as input_:
        config = Config(**yaml.full_load(input_))

    return config
Exemplo n.º 9
0
async def test_builder_site(
    mocker: MockerFixture,
    root: Path,
    config: Config,
    post: Post,
) -> None:
    """
    Test ``process_site``.
    """
    _logger = mocker.patch("nefelibata.builders.base._logger")
    mocker.patch("nefelibata.builders.base.get_posts", return_value=[post])

    config.social = [
        SocialModel(title="Mastodon", url="https://2c.taoetc.org/@beto")
    ]
    builder = HTMLBuilder(root, config, "https://example.com/")
    builder.setup()

    with freeze_time("2021-01-02T00:00:00Z"):
        await builder.process_site()

    assets_directory = root / "build/html"
    assets = (
        "index.html",
        "atom.xml",
        "tags/welcome.html",
        "tags/blog.html",
        "categories/stem.html",
    )

    # test that files were created
    last_update: Dict[Any, Any] = {}
    for asset in assets:
        assert (assets_directory / asset).exists()
        last_update[asset] = (assets_directory / asset).stat().st_mtime
    _logger.info.assert_has_calls([
        mocker.call("Creating %s",
                    Path("/path/to/blog/build/html/index.html")),
        mocker.call("Creating %s", Path("/path/to/blog/build/html/atom.xml")),
        mocker.call(
            "Creating %s",
            Path("/path/to/blog/build/html/tags/blog.html"),
        ),
        mocker.call(
            "Creating %s",
            Path("/path/to/blog/build/html/tags/welcome.html"),
        ),
        mocker.call(
            "Creating %s",
            Path("/path/to/blog/build/html/categories/stem.html"),
        ),
    ], )
    with open(assets_directory / "index.html", encoding="utf-8") as input_:
        content = input_.read()
    assert (content == f"""<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="generator" content="Nefelibata {__version__}">
    <meta name="robots" content="index, follow">
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="description" content="Musings about the path and other things">

    <title>道&amp;c.: Musings about the path and other things</title>

    <link rel="canonical" href="https://example.com/" />
    <link rel="alternate" type="application/atom+xml" href="https://example.com/atom.xml" />
    <link rel="webmention" href="https://webmention.io/example.com/webmention" />
    <link rel="me" href="mailto:[email protected]" />

    <link rel="stylesheet" href="https://example.com/css/awsm_theme_big-stone.min.css" media="(prefers-color-scheme: dark)">
    <link rel="stylesheet" href="https://example.com/css/awsm_theme_pearl-lusta.min.css" media="(prefers-color-scheme: no-preference), (prefers-color-scheme: light)">
    <link rel="stylesheet" href="https://example.com/css/custom.css">
  </head>
  <body>
    <main>
      <h1>道&amp;c.: Musings about the path and other things</h1>

      <h2 class="h-feed">Posts</h2>

      <ul>
        <li class="h-entry"><a class="p-name" href="https://example.com/first/index.html">This is your first post</a> </li>
      </ul>

      <h2>Links</h2>

      <ul>
        <li><a href="https://2c.taoetc.org/@beto" rel="me">Mastodon</a></li>
      </ul>

      <footer>
        <p>Crafted with ❤️ using <a href="https://nefelibata.readthedocs.io/">Nefelibata</a>.</p>
      </footer>
    </main>
  </body>
</html>""")
    with open(assets_directory / "atom.xml", encoding="utf-8") as input_:
        content = input_.read()
    assert (content == """<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>道&amp;c.</title>
    <link rel="self" type="application/atom+xml" href="https://example.com/atom.xml" />
    <id>https://example.com/</id>
    <updated>2021-01-01T00:00:00+00:00</updated>
<entry>
    <title>This is your first post</title>
    <link rel="alternate" type="text/html" href="https://example.com/first/index.html" />
    <id>tag:example.com,2021-01-01:first/index.html</id>
    <updated>2021-01-01T00:00:00+00:00</updated>
    <summary type="html">
        Hello, world!
        &lt;p&gt;&lt;a href="https://example.com/first/index.html"&gt;Permalink&lt;/p&gt;
    </summary>
    <author>
        <name>Beto Dealmeida</name>
        <email>[email protected]</email>
    </author>
    <category term="stem" />
    <content type="html">
        &lt;h1&gt;Welcome&lt;/h1&gt;
&lt;p&gt;This is your first post. It should be written using Markdown.&lt;/p&gt;
&lt;p&gt;Read more about &lt;a href=&#34;https://nefelibata.readthedocs.io/&#34;&gt;Nefelibata&lt;/a&gt;.&lt;/p&gt;

        &lt;p&gt;&lt;a href="https://example.com/first/index.html"&gt;Permalink&lt;/p&gt;
    </content>
</entry>
</feed>""")

    # call again, test that file is up-to-date
    _logger.reset_mock()
    with freeze_time("2021-01-03T00:00:00Z"):
        await builder.process_site()
    for asset in assets:
        assert (assets_directory / asset).stat().st_mtime == last_update[asset]
    _logger.debug.assert_has_calls([
        mocker.call(
            "File %s is up-to-date, nothing to do",
            Path("/path/to/blog/build/html/index.html"),
        ),
        mocker.call(
            "File %s is up-to-date, nothing to do",
            Path("/path/to/blog/build/html/atom.xml"),
        ),
        mocker.call(
            "File %s is up-to-date, nothing to do",
            Path("/path/to/blog/build/html/tags/blog.html"),
        ),
        mocker.call(
            "File %s is up-to-date, nothing to do",
            Path("/path/to/blog/build/html/tags/welcome.html"),
        ),
        mocker.call(
            "File %s is up-to-date, nothing to do",
            Path("/path/to/blog/build/html/categories/stem.html"),
        ),
    ], )

    # call again, forcing a rebuild
    _logger.reset_mock()
    with freeze_time("2021-01-04T00:00:00Z"):
        await builder.process_site(force=True)
    for asset in assets:
        assert (assets_directory / asset).stat().st_mtime > last_update[asset]
    _logger.info.assert_has_calls([
        mocker.call("Creating %s",
                    Path("/path/to/blog/build/html/index.html")),
        mocker.call("Creating %s", Path("/path/to/blog/build/html/atom.xml")),
        mocker.call(
            "Creating %s",
            Path("/path/to/blog/build/html/tags/blog.html"),
        ),
        mocker.call(
            "Creating %s",
            Path("/path/to/blog/build/html/tags/welcome.html"),
        ),
        mocker.call(
            "Creating %s",
            Path("/path/to/blog/build/html/categories/stem.html"),
        ),
    ], )
Exemplo n.º 10
0
async def test_builder_site(
    mocker: MockerFixture,
    root: Path,
    config: Config,
    post: Post,
) -> None:
    """
    Test ``process_site``.
    """
    _logger = mocker.patch("nefelibata.builders.base._logger")
    mocker.patch("nefelibata.builders.base.get_posts", return_value=[post])

    config.social = [
        SocialModel(title="Mastodon", url="https://2c.taoetc.org/@beto")
    ]
    builder = GeminiBuilder(root, config, "gemini://localhost:1965")
    with freeze_time("2021-01-02T00:00:00Z"):
        await builder.process_site()

    assets_directory = root / "build/gemini"
    assets = (
        "index.gmi",
        "feed.gmi",
        "tags/welcome.gmi",
        "tags/blog.gmi",
        "categories/stem.gmi",
    )

    # test that files were created
    last_update: Dict[Any, Any] = {}
    for asset in assets:
        assert (assets_directory / asset).exists()
        last_update[asset] = (assets_directory / asset).stat().st_mtime
    _logger.info.assert_has_calls([
        mocker.call("Creating %s",
                    Path("/path/to/blog/build/gemini/index.gmi")),
        mocker.call("Creating %s",
                    Path("/path/to/blog/build/gemini/feed.gmi")),
        mocker.call(
            "Creating %s",
            Path("/path/to/blog/build/gemini/tags/blog.gmi"),
        ),
        mocker.call(
            "Creating %s",
            Path("/path/to/blog/build/gemini/tags/welcome.gmi"),
        ),
        mocker.call(
            "Creating %s",
            Path("/path/to/blog/build/gemini/categories/stem.gmi"),
        ),
    ], )
    with open(assets_directory / "index.gmi", encoding="utf-8") as input_:
        content = input_.read()
    space = " "
    assert (content == f"""# 道&c.: Musings about the path and other things

This is the Gemini capsule of Beto Dealmeida.

=> https://taoetc.org/ Website
=> mailto://[email protected] Email address
=> gemini://localhost:1965/feed.gmi Gemlog

## Posts

=> gemini://localhost:1965/first/index.gmi 2021-01-01 00:00:00+00:00 — This is your first post{space}

## Links

=> https://2c.taoetc.org/@beto Mastodon

Crafted with ❤️ using Nefelibata

=> https://nefelibata.readthedocs.io/ Nefelibata""")
    with open(assets_directory / "feed.gmi", encoding="utf-8") as input_:
        content = input_.read()
    assert (content == """# 道&c.

=> gemini://localhost:1965/first/index.gmi 2021-01-01 — This is your first post
""")

    # call again, test that file is up-to-date
    _logger.reset_mock()
    with freeze_time("2021-01-03T00:00:00Z"):
        await builder.process_site()
    for asset in assets:
        assert (assets_directory / asset).stat().st_mtime == last_update[asset]
    _logger.debug.assert_has_calls([
        mocker.call(
            "File %s is up-to-date, nothing to do",
            Path("/path/to/blog/build/gemini/index.gmi"),
        ),
        mocker.call(
            "File %s is up-to-date, nothing to do",
            Path("/path/to/blog/build/gemini/feed.gmi"),
        ),
        mocker.call(
            "File %s is up-to-date, nothing to do",
            Path("/path/to/blog/build/gemini/tags/blog.gmi"),
        ),
        mocker.call(
            "File %s is up-to-date, nothing to do",
            Path("/path/to/blog/build/gemini/tags/welcome.gmi"),
        ),
        mocker.call(
            "File %s is up-to-date, nothing to do",
            Path("/path/to/blog/build/gemini/categories/stem.gmi"),
        ),
    ], )

    # call again, forcing a rebuild
    _logger.reset_mock()
    with freeze_time("2021-01-04T00:00:00Z"):
        await builder.process_site(force=True)
    for asset in assets:
        assert (assets_directory / asset).stat().st_mtime > last_update[asset]
    _logger.info.assert_has_calls([
        mocker.call("Creating %s",
                    Path("/path/to/blog/build/gemini/index.gmi")),
        mocker.call("Creating %s",
                    Path("/path/to/blog/build/gemini/feed.gmi")),
        mocker.call(
            "Creating %s",
            Path("/path/to/blog/build/gemini/tags/blog.gmi"),
        ),
        mocker.call(
            "Creating %s",
            Path("/path/to/blog/build/gemini/tags/welcome.gmi"),
        ),
        mocker.call(
            "Creating %s",
            Path("/path/to/blog/build/gemini/categories/stem.gmi"),
        ),
    ], )