using System.Text.Json; using Markdig; using Markdig.Syntax; namespace Blog.Tests.Unit; [TestFixture] public class PostMetadataTests { private static readonly MarkdownPipeline MarkdownPipeline = new MarkdownPipelineBuilder() .UseAdvancedExtensions() .Build(); private static readonly JsonSerializerOptions JsonSerializerOptions = new() { PropertyNameCaseInsensitive = true, AllowTrailingCommas = true, }; private const string MetaFence = "meta"; [Test] public void PostsDirectory_ShouldContainIndexFiles() { var postsDir = GetPostsDirectory(); var files = Directory.GetFiles(postsDir, "index.md", SearchOption.AllDirectories); files.Should().NotBeEmpty("The wwwroot/posts directory should contain blog posts with index.md files."); } [TestCaseSource(nameof(GetPostIndexFiles))] public async Task IndexMd_Metadata_CanBeProperlyParsedAndRead(string filePath) { var markdown = await File.ReadAllTextAsync(filePath); var document = Markdown.Parse(markdown, MarkdownPipeline); var metadataBlock = document .OfType() .FirstOrDefault(block => block.Arguments is not null && block.Arguments.Contains(MetaFence) ); metadataBlock.Should().NotBeNull($"file '{filePath}' should contain a fenced code block with '{MetaFence}' argument."); var jsonContent = metadataBlock.Lines.ToString(); jsonContent.Should().NotBeNullOrWhiteSpace($"metadata code block in '{filePath}' should not be empty."); Post? post = null; Action deserializeAction = () => post = JsonSerializer.Deserialize(jsonContent, JsonSerializerOptions); deserializeAction.Should().NotThrow($"JSON metadata in '{filePath}' should be valid JSON matching the Post record schema."); post.Should().NotBeNull($"deserialized Post from '{filePath}' should not be null."); post.Title.Should().NotBeNullOrWhiteSpace($"post in '{filePath}' must have a title."); post.Lead.Should().NotBeNullOrWhiteSpace($"post in '{filePath}' must have a lead summary."); post.PublishedAt.Should().NotBe(default, $"post in '{filePath}' must have a valid published date."); post.OpenGraphImage.Should().NotBeNullOrWhiteSpace($"post in '{filePath}' must have an openGraphImage path."); } private static string GetPostsDirectory() { var current = new DirectoryInfo(AppContext.BaseDirectory); while (current is not null) { var candidate = Path.Combine(current.FullName, "src", "Blog", "wwwroot", "posts"); if (Directory.Exists(candidate)) { return candidate; } current = current.Parent; } throw new DirectoryNotFoundException($"Could not locate 'src/Blog/wwwroot/posts' starting from '{AppContext.BaseDirectory}'."); } private static IEnumerable GetPostIndexFiles() { var postsDir = GetPostsDirectory(); var files = Directory.GetFiles(postsDir, "index.md", SearchOption.AllDirectories); foreach (var file in files) { var relativePath = Path.GetRelativePath(postsDir, file); yield return new TestCaseData(file).SetName($"PostMetadata_{relativePath.Replace('\\', '/')}"); } } }