95 lines
3.1 KiB
C#
95 lines
3.1 KiB
C#
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<FencedCodeBlock>()
|
||
|
|
.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<Post>(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<TestCaseData> 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('\\', '/')}");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|