# frozen_string_literal: true require "test_helper" class PostTest < Minitest::Test def test_parse_builds_post content = <<~POST +++ title = "Hello" slug = "hello" lang = "en" tags = ["intro", "ruby"] date = 2026-01-15 draft = true +++ First paragraph. POST post = Volumen::Post.parse(content) assert_equal "hello", post.slug assert_equal "Hello", post.title assert_equal "en", post.lang assert_equal %w[intro ruby], post.tags assert_predicate post, :draft? assert_equal "2026-01-15", post.date_string end def test_excerpt_derived_from_body post = Volumen::Post.new(metadata: { "slug" => "x" }, body: "# Heading\n\nThe body paragraph.") assert_equal "The body paragraph.", post.excerpt end def test_derived_excerpt_strips_html_tags body = "Bold and italic." post = Volumen::Post.new(metadata: { "slug" => "x" }, body: body) assert_equal "Bold and italic.", post.excerpt end def test_summary_shape post = Volumen::Post.new(metadata: { "slug" => "x", "title" => "X" }, body: "Body") summary = post.summary assert_equal "/api/volumen/posts/x", summary["url"] assert_equal "X", summary["title"] refute_predicate post, :draft? end def test_detail_includes_rendered_html post = Volumen::Post.new(metadata: { "slug" => "x" }, body: "**hi**") assert_includes post.detail["html"], "hi" end def test_summary_includes_cover_and_reading_time post = Volumen::Post.new( metadata: { "slug" => "x", "cover" => "/media/c.png" }, body: ("word " * 250) ) assert_equal "/media/c.png", post.summary["cover"] assert_equal 2, post.summary["reading_time"] end def test_reading_time_is_at_least_one post = Volumen::Post.new(metadata: { "slug" => "x" }, body: "short") assert_equal 1, post.reading_time end def test_scheduled_post_is_not_scheduled_when_publish_at_is_past post = Volumen::Post.new(metadata: { "publish_at" => Date.today - 1 }) refute_predicate post, :scheduled? end def test_scheduled_post_is_scheduled_when_publish_at_is_future post = Volumen::Post.new(metadata: { "publish_at" => Date.today + 7 }) assert_predicate post, :scheduled? end def test_post_without_publish_at_is_not_scheduled post = Volumen::Post.new(metadata: {}) refute_predicate post, :scheduled? end def test_fediverse_creator_returns_metadata_value post = Volumen::Post.new( metadata: { "slug" => "x", "fediverse_creator" => "@user@example.social" } ) assert_equal "@user@example.social", post.fediverse_creator end def test_fediverse_creator_is_nil_when_missing post = Volumen::Post.new(metadata: { "slug" => "x" }) assert_nil post.fediverse_creator end def test_fediverse_creator_is_nil_when_blank post = Volumen::Post.new(metadata: { "slug" => "x", "fediverse_creator" => " " }) assert_nil post.fediverse_creator end def test_fediverse_creator_validation_accepts_well_formed_handles post = Volumen::Post.new( metadata: { "slug" => "x", "fediverse_creator" => "@ada@lovelace.org" } ) assert_predicate post, :valid_fediverse_creator? end def test_fediverse_creator_validation_rejects_malformed_handles [ "ada@example.com", "@only-one-at", "@user@example.com extra", "@user@", "@@example.com" ].each do |value| post = Volumen::Post.new(metadata: { "slug" => "x", "fediverse_creator" => value }) refute_predicate post, :valid_fediverse_creator?, "expected #{value.inspect} to be invalid" end end def test_fediverse_creator_validation_accepts_padded_value post = Volumen::Post.new( metadata: { "slug" => "x", "fediverse_creator" => " @user@example.com " } ) assert_predicate post, :valid_fediverse_creator? end def test_summary_includes_fediverse_creator post = Volumen::Post.new( metadata: { "slug" => "x", "fediverse_creator" => "@user@example.social" }, body: "body" ) assert_equal "@user@example.social", post.summary["fediverse_creator"] end def test_summary_omits_fediverse_creator_when_absent post = Volumen::Post.new(metadata: { "slug" => "x" }, body: "body") assert_nil post.summary["fediverse_creator"] end end