Repository: sunpy/ablog

Issue:
Atom feeds fail W3C validation
### Atom feeds fail W3C validation

The generated Atom feeds don't pass W3C validation

### Expected vs Actual behavior

Expected behavior: Generated Atom feeds pass W3C valdation
Actual behavior: Generated Atom feeds fail W3C valdation

```
This feed does not validate.

line 9, column 2: Duplicate alternate links with the same type and hreflang [help]

  <entry>
  ^
In addition, interoperability with the widest range of feed readers could be improved by implementing the following recommendation.

line 2, column 0: Missing atom:link with rel="self" [help]

<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
```

Also, the `blog_baseurl` value needs to have a trailing slash or the validator produces a 3rd error:

```
In addition, interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

line 3, column 32: Identifier "https://www.rpatterson.net" is not in canonical form (the canonical form would be "https://www.rpatterson.net/") [help]

  <id>https://www.rpatterson.net</id>
```

I greppd that adding the trailing slash doesn't result in double slashes in the generated site.  So the requirement for a trailing slash should be documented or better yet should be added when the site is built/generated.

### Steps to Reproduce

1. Create an Atom blog
2. Publish where publicly available
3. [Validate an Atom feed](https://validator.w3.org/feed/check.cgi?url=https%3A%2F%2Fwww.rpatterson.net%2Fblog%2Fatom.xml)

### System Details

```
$ ./.venv/bin/python --version
Python 3.8.6
$ ./.venv/bin/pip freeze
ablog==0.10.13
alabaster==0.7.12
Babel==2.9.0
certifi==2020.12.5
chardet==4.0.0
docutils==0.16
feedgen==0.9.0
idna==2.10
imagesize==1.2.0
invoke==1.5.0
Jinja2==2.11.3
lxml==4.6.2
MarkupSafe==1.1.1
packaging==20.9
Pygments==2.8.1
pygments-solarized==0.0.3
pyparsing==2.4.7
python-dateutil==2.8.1
pytz==2021.1
requests==2.25.1
six==1.15.0
snowballstemmer==2.1.0
Sphinx==3.5.2
sphinx-fasvg==0.1.4
sphinx-nervproject-theme==2.0.3
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==1.0.3
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.4
urllib3==1.26.4
watchdog==2.0.2
$ uname -a
Linux rpatterson.rpatterson.net 5.8.0-7642-generic #47~1614007149~20.10~82fb226-Ubuntu SMP Tue Feb 23 02
:59:01 UTC  x86_64 x86_64 x86_64 GNU/Linux                                                             
```

Discussion:

Patch:
diff --git a/ablog/post.py b/ablog/post.py
index b6ea94f..2c73f6b 100644
--- a/ablog/post.py
+++ b/ablog/post.py
@@ -726,10 +726,13 @@ def generate_atom_feeds(app):
             feed_entry = feed.add_entry()
             feed_entry.id(post_url)
             feed_entry.title(post.title)
+            feed_entry.summary(" ".join(paragraph.astext() for paragraph in post.excerpt[0]))
             feed_entry.link(href=post_url)
             feed_entry.author({"name": author.name for author in post.author})
             feed_entry.pubDate(post.date.astimezone())
             feed_entry.updated(post.update.astimezone())
+            for tag in post.tags:
+                feed_entry.category(dict(term=tag.name, label=tag.label))
             feed_entry.content(content=content, type="html")
 
         parent_dir = os.path.dirname(feed_path)


Test patch:
diff --git a/tests/roots/test-build/conf.py b/tests/roots/test-build/conf.py
index 9fefc4c..470bfe1 100644
--- a/tests/roots/test-build/conf.py
+++ b/tests/roots/test-build/conf.py
@@ -1,1 +1,6 @@
 extensions = ["ablog"]
+
+# Enable Atom feed generation
+blog_baseurl = u"https://blog.example.com/"
+# Include full post in feeds
+blog_feed_fulltext = True
diff --git a/tests/roots/test-build/post.rst b/tests/roots/test-build/post.rst
index ce87887..d8f1d1e 100644
--- a/tests/roots/test-build/post.rst
+++ b/tests/roots/test-build/post.rst
@@ -1,4 +1,9 @@
 .. post:: 2020-12-01
+   :tags: Foo Tag, BarTag
 
-post
-=======
+Foo Post Title
+==============
+
+    Foo post description.
+
+Foo post content.
diff --git a/tests/test_build.py b/tests/test_build.py
index 96f30cd..f53d49b 100644
--- a/tests/test_build.py
+++ b/tests/test_build.py
@@ -1,3 +1,4 @@
+import lxml
 import pytest
 
 
@@ -9,3 +10,32 @@ def test_build(app, status, warning):
     assert (app.outdir / "index.html").exists()
     assert (app.outdir / "blog/archive.html").exists()
     assert (app.outdir / "post.html").exists()
+
+
+@pytest.mark.sphinx("html", testroot="build")  # using roots/test-build
+def test_feed(app, status, warning):
+    """
+    Atom syndication feeds are built correctly.
+    """
+    app.build()
+    assert app.statuscode == 0, "Test ABlog project did not build successfully"
+
+    feed_path = app.outdir / "blog/atom.xml"
+    assert (feed_path).exists(), "Atom feed was not built"
+
+    with feed_path.open() as feed_opened:
+        feed_tree = lxml.etree.parse(feed_opened)
+    entries = feed_tree.findall("{http://www.w3.org/2005/Atom}entry")
+    assert len(entries) == 1, "Wrong number of Atom feed entries"
+
+    entry = entries[0]
+    title = entry.find("{http://www.w3.org/2005/Atom}title")
+    assert title.text == "Foo Post Title", "Wrong Atom feed entry title"
+    summary = entry.find("{http://www.w3.org/2005/Atom}summary")
+    assert summary.text == "Foo post description.", "Wrong Atom feed entry summary"
+    categories = entry.findall("{http://www.w3.org/2005/Atom}category")
+    assert len(categories) == 2, "Wrong number of Atom feed categories"
+    assert categories[0].attrib["label"] == "Foo Tag", "Wrong Atom feed first category"
+    assert categories[1].attrib["label"] == "BarTag", "Wrong Atom feed second category"
+    content = entry.find("{http://www.w3.org/2005/Atom}content")
+    assert "Foo post content." in content.text, "Wrong Atom feed entry content"