Files
daniel-c-harvey f3b89ca9d7 feature: Phase 22 SEO metadata component for public site
One presentational SeoHead renders the full OG/Twitter/JSON-LD head surface at prerender via typed schema.org builders. Per-medium release schema, config-sourced canonicals, 404 noindex. Zero CMS change.
2026-06-23 05:41:55 -04:00

69 lines
2.3 KiB
C#

using DeepDrftPublic.Client.Common;
namespace DeepDrftTests;
/// <summary>
/// Unit tests for <see cref="SeoUrls"/> — the absolute-URL composition shared by the SeoModel factories
/// and SeoHead (Phase 22). Origin always comes from config (never a browser API), so these pin the
/// slash-join, the cover-vs-default fallback (C6/AC4), and the ISO-8601 duration edge cases.
/// </summary>
[TestFixture]
public class SeoUrlsTests
{
private static readonly SeoOptions Options = new()
{
BaseUrl = "https://deepdrft.com",
DefaultImageUrl = "/img/og-default.png",
};
[TestCase("/cuts/key", "https://deepdrft.com/cuts/key")]
[TestCase("cuts/key", "https://deepdrft.com/cuts/key")]
[TestCase("/", "https://deepdrft.com/")]
[TestCase("", "https://deepdrft.com")]
public void Absolute_JoinsOriginAndPath_WithoutDoublingOrDroppingSlash(string path, string expected)
{
Assert.That(SeoUrls.Absolute(Options, path), Is.EqualTo(expected));
}
[Test]
public void Absolute_TrimsTrailingSlashOnBaseUrl()
{
var withSlash = Options with { BaseUrl = "https://deepdrft.com/" };
Assert.That(SeoUrls.Absolute(withSlash, "/cuts"), Is.EqualTo("https://deepdrft.com/cuts"));
}
[Test]
public void CoverOrDefault_WithCover_BuildsEscapedImageRoute()
{
Assert.That(SeoUrls.CoverOrDefault(Options, "my cover.jpg"),
Is.EqualTo("https://deepdrft.com/api/image/my%20cover.jpg"));
}
[TestCase(null)]
[TestCase("")]
[TestCase(" ")]
public void CoverOrDefault_WithoutCover_FallsBackToDefaultImage(string? image)
{
Assert.That(SeoUrls.CoverOrDefault(Options, image),
Is.EqualTo("https://deepdrft.com/img/og-default.png"));
}
[TestCase(30.0, "PT30S")]
[TestCase(90.0, "PT1M30S")]
[TestCase(3723.0, "PT1H2M3S")]
public void IsoDuration_PositiveSeconds_RendersIso8601(double seconds, string expected)
{
Assert.That(SeoUrls.IsoDuration(seconds), Is.EqualTo(expected));
}
[TestCase(null)]
[TestCase(0.0)]
[TestCase(-5.0)]
[TestCase(double.NaN)]
[TestCase(double.PositiveInfinity)]
public void IsoDuration_NonPositiveOrNonFinite_ReturnsNull(double? seconds)
{
Assert.That(SeoUrls.IsoDuration(seconds), Is.Null);
}
}