From b3009fd1028942556a8466f7855b1c04d37ec871 Mon Sep 17 00:00:00 2001 From: Tobias Schmidl Date: Thu, 17 Oct 2024 21:06:19 +0200 Subject: [PATCH] first test structure --- README.md | 1 + pelican/plugins/activitypub/activitypub.py | 50 ++++++++------- .../plugins/activitypub/activitypub_test.py | 62 +++++++++++++++++++ pyproject.toml | 6 ++ 4 files changed, 95 insertions(+), 24 deletions(-) create mode 100644 pelican/plugins/activitypub/activitypub_test.py diff --git a/README.md b/README.md index 9b6be72..a7b3585 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ ACTIVITYPUB_AUTHORS = { 'alice': { 'name': 'Alice', 'movedTo': 'https://fedi.example/users/alice', + 'movedToName': '@alice@fedi.example', 'alsoKnownAs': ['https://fedi.example/users/alice'], 'summary': 'Hi, I\'m Alice! Please follow me at @alice@fedi.example.', 'attachment': [ diff --git a/pelican/plugins/activitypub/activitypub.py b/pelican/plugins/activitypub/activitypub.py index ea76717..bf20da3 100644 --- a/pelican/plugins/activitypub/activitypub.py +++ b/pelican/plugins/activitypub/activitypub.py @@ -1,28 +1,30 @@ import datetime -import logging import json +import logging import os import urllib.parse +from typing import Any, Dict + import pelican.writers from pelican import signals - log = logging.getLogger(__name__) __version__ = '0.1.2' -pagination = 25 +PAGINATION = 25 +ENCODING = 'UTF-8' def ap_article(generator: pelican.ArticlesGenerator, writer: pelican.writers.Writer): now = datetime.datetime.utcnow() author = generator.settings['AUTHOR'] - domain = urllib.parse.urlparse(generator.settings['SITEURL']).netloc + domain: str = urllib.parse.urlparse(generator.settings['SITEURL']).netloc wkhmpath = os.path.join(writer.output_path, '.well-known/host-meta') wknipath = os.path.join(writer.output_path, '.well-known/nodeinfo') @@ -72,7 +74,7 @@ def ap_article(generator: pelican.ArticlesGenerator, writer: pelican.writers.Wri } } wfurl = os.path.join(generator.settings['SITEURL'], '.well-known/webfinger?resource={uri}') - hostmeta = f'' + hostmeta = f'' webfinger = { 'subject': f'acct:{author}@{domain}', 'aliases': [ @@ -92,13 +94,13 @@ def ap_article(generator: pelican.ArticlesGenerator, writer: pelican.writers.Wri } ] } - with open(wkhmpath, 'w') as hf: + with open(wkhmpath, 'w', encoding=ENCODING) as hf: hf.write(hostmeta) - with open(wknipath, 'w') as nf: + with open(wknipath, 'w', encoding=ENCODING) as nf: json.dump(wknodeinfo, nf) - with open(nipath, 'w') as nf: + with open(nipath, 'w', encoding=ENCODING) as nf: json.dump(nodeinfo, nf) - with open(wfpath, 'w') as wf: + with open(wfpath, 'w', encoding=ENCODING) as wf: json.dump(webfinger, wf) for t in generator.tags: @@ -118,7 +120,7 @@ def ap_article(generator: pelican.ArticlesGenerator, writer: pelican.writers.Wri 'totalItems': len(articles), 'orderedItems': articles } - with open(path, 'w') as f: + with open(path, 'w', encoding=ENCODING) as f: json.dump(tag, f) articlemap = {} @@ -142,13 +144,13 @@ def ap_article(generator: pelican.ArticlesGenerator, writer: pelican.writers.Wri replyto = os.path.join(generator.settings['SITEURL'], 'activitypub/posts', sa.slug) break cc = [os.path.join(generator.settings['SITEURL'], 'activitypub/collections/followers', article.author.slug)] - aa = generator.settings.get('ACTIVITYPUB_AUTHORS', {}).get(author.name, {}) + aa: Dict[str, Dict[str, Any]] = generator.settings.get('ACTIVITYPUB_AUTHORS', {}).get(author.name, {}) if 'movedTo' in aa: cc.append(aa['movedTo']) tags.append({ 'type': 'Mention', 'href': aa['movedTo'], - 'name': aa['_movedTo_name'] + 'name': aa['movedToName'] }) cmap = {} tmap = {} @@ -173,7 +175,7 @@ def ap_article(generator: pelican.ArticlesGenerator, writer: pelican.writers.Wri 'attachment': [], 'tag': tags } - with open(apath, 'w') as f: + with open(apath, 'w', encoding=ENCODING) as f: json.dump(articlemap[article.slug], f) for author, articles in generator.authors: @@ -255,7 +257,7 @@ def ap_article(generator: pelican.ArticlesGenerator, writer: pelican.writers.Wri 'totalItems': 0, 'orderedItems': [] } - maxpage = len(creates) // pagination + maxpage = len(creates) // PAGINATION outbox = { '@context': ['https://www.w3.org/ns/activitystreams'], 'type': 'OrderedCollection', @@ -278,8 +280,8 @@ def ap_article(generator: pelican.ArticlesGenerator, writer: pelican.writers.Wri 'totalItems': 0, 'orderedItems': [] } - for i in range(0, len(creates), pagination): - ipage = i // pagination + for i in range(0, len(creates), PAGINATION): + ipage = i // PAGINATION outpageurl = os.path.join(generator.settings['SITEURL'], 'activitypub/collections/outbox_page', author.slug, str(ipage)) outpagepath = os.path.join(writer.output_path, 'activitypub/collections/outbox_page', author.slug, str(ipage)) page = { @@ -288,13 +290,13 @@ def ap_article(generator: pelican.ArticlesGenerator, writer: pelican.writers.Wri 'id': outpageurl, 'totalItems': len(creates), 'partOf': outboxurl, - 'orderedItems': creates[i:i+pagination] + 'orderedItems': creates[i:i+PAGINATION] } if ipage > 0: page['prev'] = os.path.join(generator.settings['SITEURL'], 'activitypub/collections/outbox_page', author.slug, str(ipage-1)) if ipage < maxpage: page['next'] = os.path.join(generator.settings['SITEURL'], 'activitypub/collections/outbox_page', author.slug, str(ipage+1)) - with open(outpagepath, 'w') as f: + with open(outpagepath, 'w', encoding=ENCODING) as f: json.dump(page, f) author_webfinger = { 'subject': f'acct:{author.name}@{domain}', @@ -315,17 +317,17 @@ def ap_article(generator: pelican.ArticlesGenerator, writer: pelican.writers.Wri } ] } - with open(os.path.join(awfpath, author.name), 'w') as f: + with open(os.path.join(awfpath, author.name), 'w', encoding=ENCODING) as f: json.dump(author_webfinger, f) - with open(os.path.join(authorpath, author.name), 'w') as f: + with open(os.path.join(authorpath, author.name), 'w', encoding=ENCODING) as f: json.dump(a, f) - with open(inboxpath, 'w') as f: + with open(inboxpath, 'w', encoding=ENCODING) as f: json.dump(inbox, f) - with open(outboxpath, 'w') as f: + with open(outboxpath, 'w', encoding=ENCODING) as f: json.dump(outbox, f) - with open(followingpath, 'w') as f: + with open(followingpath, 'w', encoding=ENCODING) as f: json.dump(following, f) - with open(followerspath, 'w') as f: + with open(followerspath, 'w', encoding=ENCODING) as f: json.dump(followers, f) diff --git a/pelican/plugins/activitypub/activitypub_test.py b/pelican/plugins/activitypub/activitypub_test.py new file mode 100644 index 0000000..88c47a1 --- /dev/null +++ b/pelican/plugins/activitypub/activitypub_test.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2024 Tobias Schmidl +# +# SPDX-License-Identifier: MIT + +"""Unit tests for the ActivityPub plugin""" + +import datetime +import unittest +from unittest.mock import MagicMock, patch +import os +import json +from activitypub import ap_article, ENCODING + +class TestApArticle(unittest.TestCase): + """Tests the ap_article function""" + + @patch('os.makedirs') + @patch('builtins.open') + @patch('json.dump') + def test_ap_article(self, mock_json_dump, mock_open, mock_makedirs): + # Mock the generator and writer + generator = MagicMock() + writer = MagicMock() + + # Mock settings + generator.settings = { + 'AUTHOR': 'test_author', + 'SITEURL': 'http://example.com', + 'SITENAME': 'Test Site', + 'ACTIVITYPUB_AUTHORS': {} + } + + # Mock authors and articles + generator.authors = [('test_author', MagicMock(slug='test_author'))] + generator.articles = [MagicMock(slug='test_article', date=datetime.datetime.utcnow(), title='Test Article', content='Test Content', metadata={'tags': ['test_tag']}, author=MagicMock(slug='test_author'), url='test_article.html', translations=[])] + generator.tags = [MagicMock(slug='test_tag', name='test_tag')] + + # Call the function + ap_article(generator, writer) + + # Check if directories were created + mock_makedirs.assert_any_call(os.path.join(writer.output_path, '.well-known'), exist_ok=True) + mock_makedirs.assert_any_call(os.path.join(writer.output_path, 'activitypub/users'), exist_ok=True) + mock_makedirs.assert_any_call(os.path.join(writer.output_path, 'activitypub/posts'), exist_ok=True) + mock_makedirs.assert_any_call(os.path.join(writer.output_path, 'activitypub/tags'), exist_ok=True) + mock_makedirs.assert_any_call(os.path.join(writer.output_path, 'activitypub/collections/inbox'), exist_ok=True) + mock_makedirs.assert_any_call(os.path.join(writer.output_path, 'activitypub/collections/outbox'), exist_ok=True) + mock_makedirs.assert_any_call(os.path.join(writer.output_path, 'activitypub/collections/outbox_page', 'test_author'), exist_ok=True) + mock_makedirs.assert_any_call(os.path.join(writer.output_path, 'activitypub/collections/following'), exist_ok=True) + mock_makedirs.assert_any_call(os.path.join(writer.output_path, 'activitypub/collections/followers'), exist_ok=True) + + # Check if files were written + mock_open.assert_any_call(os.path.join(writer.output_path, '.well-known/host-meta'), 'w', encoding=ENCODING) + mock_open.assert_any_call(os.path.join(writer.output_path, '.well-known/nodeinfo'), 'w', encoding=ENCODING) + mock_open.assert_any_call(os.path.join(writer.output_path, 'activitypub/nodeinfo'), 'w', encoding=ENCODING) + mock_open.assert_any_call(os.path.join(writer.output_path, '.well-known/webfinger'), 'w', encoding=ENCODING) + + # Check if JSON data was dumped + self.assertTrue(mock_json_dump.called) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 685d567..b744860 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,3 +40,9 @@ build-backend = "setuptools.build_meta" [tool.setuptools.dynamic] version = {attr = "pelican.plugins.activitypub.activitypub.__version__"} + +[project.optional-dependencies] +Test = [ + "pytest>=8.3.0", + "isort" +] \ No newline at end of file