···11+defmodule AnnotAt.Feeds.AtomTest do
22+ use ExUnit.Case, async: true
33+44+ alias AnnotAt.Feeds.Atom
55+ alias AnnotAt.Feeds.Entry
66+ alias AnnotAt.Feeds.Feed
77+88+ @fixture "../../support/fixtures/feeds/atom_sample.xml"
99+ |> Path.expand(__DIR__)
1010+ |> File.read!()
1111+1212+ test "parses feed-level metadata" do
1313+ assert {:ok, %Feed{} = feed} = Atom.parse(@fixture)
1414+ assert "Sample Blog" == feed.title
1515+ assert "https://example.com" == feed.url
1616+ assert "Thoughts about things." == feed.description
1717+ end
1818+1919+ test "parses entries with all fields" do
2020+ assert {:ok, %{entries: [first, _second]}} = Atom.parse(@fixture)
2121+2222+ assert %Entry{} = first
2323+ assert "First Post" == first.title
2424+ assert "https://example.com/posts/first" == first.url
2525+ assert "abc" == first.id
2626+ assert "A short summary of the first post." == first.summary
2727+ assert "<p>The full <strong>content</strong>\n of the first post.</p>" == first.content
2828+ assert %DateTime{} = first.published_at
2929+ assert ~U[2024-10-02 13:00:00Z] == first.published_at
3030+ end
3131+3232+ test "falls back to URL as ID when id is missing" do
3333+ assert {:ok, %{entries: [_first, second]}} = Atom.parse(@fixture)
3434+ assert second.id == second.url
3535+ end
3636+3737+ test "prefers published over updated for published_at" do
3838+ assert {:ok, %{entries: [first, _second]}} = Atom.parse(@fixture)
3939+ assert ~U[2024-10-02 13:00:00Z] == first.published_at
4040+ end
4141+4242+ test "leaves published_at nil when published and updated are missing" do
4343+ assert {:ok, %{entries: [_first, second]}} = Atom.parse(@fixture)
4444+ refute second.published_at
4545+ end
4646+4747+ test "leaves content nil when missing" do
4848+ assert {:ok, %{entries: [_first, second]}} = Atom.parse(@fixture)
4949+ refute second.content
5050+ end
5151+5252+ test "uses the alternate link, not self" do
5353+ assert {:ok, %Feed{url: "https://example.com"}} = Atom.parse(@fixture)
5454+ end
5555+5656+ test "returns an error on malformed" do
5757+ assert {:error, :invalid_feed} = Atom.parse("<feed><title>oops")
5858+ end
5959+6060+ test "accepts a valid but empty feed" do
6161+ body = """
6262+ <?xml version="1.0" encoding="utf-8"?>
6363+ <feed xmlns="http://www.w3.org/2005/Atom">
6464+ <title>Brand New Blog</title>
6565+ <link rel="alternate" href="https://example.com"/>
6666+ <subtitle>Nothing here yet.</subtitle>
6767+ </feed>
6868+ """
6969+7070+ assert {:ok, %Feed{title: "Brand New Blog", entries: []}} = Atom.parse(body)
7171+ end
7272+7373+ test "rejects a feed with no title as invalid" do
7474+ body = """
7575+ <?xml version="1.0" encoding="utf-8"?>
7676+ <feed xmlns="http://www.w3.org/2005/Atom">
7777+ <link rel="alternate" href="https://example.com"/>
7878+ <subtitle>No title here.</subtitle>
7979+ </feed>
8080+ """
8181+8282+ assert {:error, :invalid_feed} = Atom.parse(body)
8383+ end
8484+end