265 lines
7.1 KiB
Elixir
265 lines
7.1 KiB
Elixir
defmodule Blogex.RouterTest do
|
|
use ExUnit.Case
|
|
import Plug.Test
|
|
import Plug.Conn, only: [put_req_header: 3]
|
|
|
|
import Blogex.Test.PostBuilder
|
|
alias Blogex.Test.FakeBlog
|
|
|
|
setup do
|
|
posts = [
|
|
build(id: "first-post", title: "First", tags: ["elixir"], date: ~D[2026-03-10]),
|
|
build(id: "second-post", title: "Second", tags: ["otp"], date: ~D[2026-02-01]),
|
|
build(id: "draft", published: false, date: ~D[2026-03-12]),
|
|
build(
|
|
id: "future-post",
|
|
title: "Future",
|
|
tags: ["elixir"],
|
|
date: ~D[2099-01-01],
|
|
published: true
|
|
)
|
|
]
|
|
|
|
{:ok, _} =
|
|
FakeBlog.start(posts,
|
|
blog_id: :test,
|
|
title: "Test Blog",
|
|
description: "Test",
|
|
base_path: "/blog/test"
|
|
)
|
|
|
|
:ok
|
|
end
|
|
|
|
defp call(method, path) do
|
|
conn(method, path)
|
|
|> Blogex.Router.call(Blogex.Router.init(blog: FakeBlog))
|
|
end
|
|
|
|
defp call_html(path) do
|
|
conn(:get, path)
|
|
|> put_req_header("accept", "text/html")
|
|
|> Blogex.Router.call(Blogex.Router.init(blog: FakeBlog))
|
|
end
|
|
|
|
defp call_json(path) do
|
|
conn(:get, path)
|
|
|> put_req_header("accept", "application/json")
|
|
|> Blogex.Router.call(Blogex.Router.init(blog: FakeBlog))
|
|
end
|
|
|
|
describe "GET /feed.xml" do
|
|
test "returns RSS XML" do
|
|
conn = call(:get, "/feed.xml")
|
|
|
|
assert conn.status == 200
|
|
assert get_content_type(conn) =~ "application/rss+xml"
|
|
assert conn.resp_body =~ "<rss version=\"2.0\""
|
|
end
|
|
|
|
test "includes published posts" do
|
|
conn = call(:get, "/feed.xml")
|
|
|
|
assert conn.resp_body =~ "first-post"
|
|
refute conn.resp_body =~ "draft"
|
|
end
|
|
|
|
test "excludes future-dated posts from feed" do
|
|
conn = call(:get, "/feed.xml")
|
|
|
|
refute conn.resp_body =~ "future-post"
|
|
end
|
|
end
|
|
|
|
describe "GET /atom.xml" do
|
|
test "returns Atom XML" do
|
|
conn = call(:get, "/atom.xml")
|
|
|
|
assert conn.status == 200
|
|
assert get_content_type(conn) =~ "application/atom+xml"
|
|
assert conn.resp_body =~ "<feed xmlns="
|
|
end
|
|
end
|
|
|
|
describe "GET /:slug" do
|
|
test "returns post as JSON" do
|
|
conn = call(:get, "/first-post")
|
|
|
|
assert conn.status == 200
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert body["id"] == "first-post"
|
|
assert body["title"] == "First"
|
|
end
|
|
|
|
test "returns 404 for unknown slug" do
|
|
conn = call(:get, "/nonexistent")
|
|
|
|
assert conn.status == 404
|
|
end
|
|
|
|
test "returns 200 for draft post accessed by slug" do
|
|
conn = call(:get, "/draft")
|
|
|
|
assert conn.status == 200
|
|
end
|
|
|
|
test "returns 200 for future-dated post accessed by slug" do
|
|
conn = call(:get, "/future-post")
|
|
|
|
assert conn.status == 200
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert body["id"] == "future-post"
|
|
end
|
|
end
|
|
|
|
describe "GET /tag/:tag" do
|
|
test "returns posts matching tag" do
|
|
conn = call(:get, "/tag/elixir")
|
|
|
|
assert conn.status == 200
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert body["tag"] == "elixir"
|
|
assert length(body["posts"]) == 1
|
|
assert hd(body["posts"])["id"] == "first-post"
|
|
end
|
|
|
|
test "excludes future-dated posts from tag results" do
|
|
conn = call(:get, "/tag/elixir")
|
|
|
|
body = Jason.decode!(conn.resp_body)
|
|
ids = Enum.map(body["posts"], & &1["id"])
|
|
refute "future-post" in ids
|
|
end
|
|
|
|
test "returns empty list for unknown tag" do
|
|
conn = call(:get, "/tag/unknown")
|
|
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert body["posts"] == []
|
|
end
|
|
end
|
|
|
|
describe "GET /" do
|
|
test "returns paginated post list" do
|
|
conn = call(:get, "/")
|
|
|
|
assert conn.status == 200
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert is_list(body["posts"])
|
|
assert body["total_entries"] == 2
|
|
end
|
|
|
|
test "excludes draft posts from listing" do
|
|
conn = call(:get, "/")
|
|
|
|
body = Jason.decode!(conn.resp_body)
|
|
ids = Enum.map(body["posts"], & &1["id"])
|
|
refute "draft" in ids
|
|
end
|
|
|
|
test "excludes future-dated posts from listing" do
|
|
conn = call(:get, "/")
|
|
|
|
body = Jason.decode!(conn.resp_body)
|
|
ids = Enum.map(body["posts"], & &1["id"])
|
|
refute "future-post" in ids
|
|
end
|
|
end
|
|
|
|
# The browser arms were previously unexercised: every test above goes through
|
|
# `call/2`, which sends no Accept header, so `wants_html?/1` was always false.
|
|
describe "GET / (browser)" do
|
|
test "returns an HTML page with the blog title and the published posts" do
|
|
conn = call_html("/")
|
|
|
|
assert conn.status == 200
|
|
assert get_content_type(conn) =~ "text/html"
|
|
assert conn.resp_body =~ "Test Blog"
|
|
assert conn.resp_body =~ "First"
|
|
assert conn.resp_body =~ ~s(href="/blog/test/first-post")
|
|
refute conn.resp_body =~ "future-post"
|
|
end
|
|
|
|
test "leaves JSON clients alone" do
|
|
conn = call_json("/")
|
|
|
|
assert get_content_type(conn) =~ "application/json"
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert body["title"] == "Test Blog"
|
|
assert body["total_entries"] == 2
|
|
end
|
|
end
|
|
|
|
describe "GET /:slug (browser)" do
|
|
test "returns the post as an HTML page" do
|
|
conn = call_html("/first-post")
|
|
|
|
assert conn.status == 200
|
|
assert get_content_type(conn) =~ "text/html"
|
|
assert conn.resp_body =~ "First"
|
|
assert conn.resp_body =~ "<p>Post body.</p>"
|
|
refute conn.resp_body =~ "<p>"
|
|
end
|
|
|
|
test "leaves JSON clients alone" do
|
|
conn = call_json("/first-post")
|
|
|
|
assert get_content_type(conn) =~ "application/json"
|
|
assert Jason.decode!(conn.resp_body)["id"] == "first-post"
|
|
end
|
|
end
|
|
|
|
describe "GET /tag/:tag (browser)" do
|
|
test "returns HTML with only the posts carrying that tag" do
|
|
conn = call_html("/tag/elixir")
|
|
|
|
assert conn.status == 200
|
|
assert get_content_type(conn) =~ "text/html"
|
|
assert conn.resp_body =~ "First"
|
|
refute conn.resp_body =~ "Second"
|
|
end
|
|
|
|
test "leaves JSON clients alone" do
|
|
conn = call_json("/tag/elixir")
|
|
|
|
assert get_content_type(conn) =~ "application/json"
|
|
assert Jason.decode!(conn.resp_body)["tag"] == "elixir"
|
|
end
|
|
end
|
|
|
|
describe "unknown routes" do
|
|
test "answers 404 Not found" do
|
|
conn = call(:post, "/whatever")
|
|
|
|
assert conn.status == 404
|
|
assert conn.resp_body == "Not found"
|
|
end
|
|
end
|
|
|
|
describe "Helpers.base_url/1" do
|
|
# `port_suffix/2` has one clause per (scheme, default port) pair plus the
|
|
# catch-all, and the feeds embed this URL. All four shapes are pinned so a
|
|
# clause cannot be dropped or reordered without a failure.
|
|
test "omits the port for the default scheme ports" do
|
|
assert base_url(:http, 80) == "http://www.example.com"
|
|
assert base_url(:https, 443) == "https://www.example.com"
|
|
end
|
|
|
|
test "keeps a non-default port" do
|
|
assert base_url(:http, 4000) == "http://www.example.com:4000"
|
|
assert base_url(:https, 8443) == "https://www.example.com:8443"
|
|
end
|
|
|
|
defp base_url(scheme, port) do
|
|
%{Plug.Test.conn(:get, "/") | scheme: scheme, port: port}
|
|
|> Blogex.Router.Helpers.base_url()
|
|
end
|
|
end
|
|
|
|
defp get_content_type(conn) do
|
|
conn
|
|
|> Plug.Conn.get_resp_header("content-type")
|
|
|> List.first("")
|
|
end
|
|
end
|