From 72c8543d001a606064d2d568d5138f40673d11c6 Mon Sep 17 00:00:00 2001 From: Steven Giesel Date: Tue, 24 Dec 2024 09:29:39 +0100 Subject: [PATCH 1/2] chore: Upgrade packages --- Directory.Packages.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 85fbcb4e..2e54c046 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,7 @@ - + @@ -18,11 +18,11 @@ - + - + @@ -47,4 +47,4 @@ - + \ No newline at end of file From 82e64d64cb9e44567ce6badd54804541cb6bfb35 Mon Sep 17 00:00:00 2001 From: Steven Giesel Date: Tue, 24 Dec 2024 13:13:19 +0100 Subject: [PATCH 2/2] feat: Create sitemap.xml dynamically --- docs/SEO/Readme.md | 2 +- .../Controller/SitemapController.cs | 52 ++++++++++++ .../Admin/Sitemap/Services/ISitemapService.cs | 6 +- .../Admin/Sitemap/Services/IXmlFileWriter.cs | 6 +- .../Admin/Sitemap/Services/SitemapService.cs | 41 +++++----- .../Admin/Sitemap/Services/XmlFileWriter.cs | 19 ----- .../Admin/Sitemap/Services/XmlWriter.cs | 19 +++++ .../Features/Admin/Sitemap/SitemapPage.razor | 55 ------------- .../Home/Components/AccessControl.razor | 1 - src/LinkDotNet.Blog.Web/ServiceExtensions.cs | 2 +- .../Shared/Services/SitemapServiceTests.cs | 60 +++++--------- .../Sitemap/Services/SitemapServiceTests.cs | 72 ----------------- .../Sitemap/Services/XmlFileWriterTests.cs | 38 --------- .../Admin/Sitemap/Services/XmlWriterTests.cs | 27 +++++++ .../Admin/Sitemap/SitemapPageTests.cs | 79 ------------------- 15 files changed, 145 insertions(+), 334 deletions(-) create mode 100644 src/LinkDotNet.Blog.Web/Controller/SitemapController.cs delete mode 100644 src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/XmlFileWriter.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/XmlWriter.cs delete mode 100644 src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/SitemapPage.razor delete mode 100644 tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/Services/SitemapServiceTests.cs delete mode 100644 tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/Services/XmlFileWriterTests.cs create mode 100644 tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/Services/XmlWriterTests.cs delete mode 100644 tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/SitemapPageTests.cs diff --git a/docs/SEO/Readme.md b/docs/SEO/Readme.md index d56823b8..ad1aac88 100644 --- a/docs/SEO/Readme.md +++ b/docs/SEO/Readme.md @@ -34,7 +34,7 @@ This blog also offers an RSS feed ([RSS 2.0 specification](https://validator.w3. ### Sitemap -This blog offers to generate a [sitemap](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) that lists all blog posts, the archive and pages of the blog. A sitemap can be generated in the Admin tab of the navigation bar under "Sitemap". This allows, especially new sites that don't have many inbound links, to be indexed easier by search engines. +This blog automatically generates a [sitemap](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) that lists all blog posts, the archive and pages of the blog. ## JSON LD This blog supports a JSON-LD for structured data. The current support is limited / rudimentary. Information like `Headline` (the title of the blog post), `Author`, `PublishDated` and `PreviewImage` are present. diff --git a/src/LinkDotNet.Blog.Web/Controller/SitemapController.cs b/src/LinkDotNet.Blog.Web/Controller/SitemapController.cs new file mode 100644 index 00000000..a213cf56 --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Controller/SitemapController.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; +using LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.RateLimiting; +using Microsoft.Extensions.Caching.Memory; + +namespace LinkDotNet.Blog.Web.Controller; + +[EnableRateLimiting("ip")] +[Route("sitemap.xml")] +public sealed class SitemapController : ControllerBase +{ + private readonly ISitemapService sitemapService; + private readonly IXmlWriter xmlWriter; + private readonly IMemoryCache memoryCache; + + public SitemapController( + ISitemapService sitemapService, + IXmlWriter xmlWriter, + IMemoryCache memoryCache) + { + this.sitemapService = sitemapService; + this.xmlWriter = xmlWriter; + this.memoryCache = memoryCache; + } + + [ResponseCache(Duration = 3600)] + [HttpGet] + public async Task GetSitemap() + { + var buffer = await memoryCache.GetOrCreateAsync("sitemap.xml", async e => + { + e.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1); + return await GetSitemapBuffer(); + }) + ?? throw new InvalidOperationException("Buffer is null"); + + return File(buffer, "application/xml"); + } + + private async Task GetSitemapBuffer() + { + var baseUri = $"{Request.Scheme}://{Request.Host}{Request.PathBase}"; + var sitemap = await sitemapService.CreateSitemapAsync(baseUri); + var buffer = await xmlWriter.WriteToBuffer(sitemap); + return buffer; + } +} diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/ISitemapService.cs b/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/ISitemapService.cs index 581e9467..92e80c8d 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/ISitemapService.cs +++ b/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/ISitemapService.cs @@ -4,7 +4,5 @@ namespace LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services; public interface ISitemapService { - Task CreateSitemapAsync(); - - Task SaveSitemapToFileAsync(SitemapUrlSet sitemap); -} \ No newline at end of file + Task CreateSitemapAsync(string baseUri); +} diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/IXmlFileWriter.cs b/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/IXmlFileWriter.cs index 12ab89b9..17dfc49a 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/IXmlFileWriter.cs +++ b/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/IXmlFileWriter.cs @@ -2,7 +2,7 @@ namespace LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services; -public interface IXmlFileWriter +public interface IXmlWriter { - Task WriteObjectToXmlFileAsync(T objectToSave, string fileName); -} \ No newline at end of file + Task WriteToBuffer(T objectToSave); +} diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/SitemapService.cs b/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/SitemapService.cs index 4ad004fb..3b49e46c 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/SitemapService.cs +++ b/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/SitemapService.cs @@ -2,66 +2,63 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; +using System.IO; using System.Linq; using System.Threading.Tasks; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure.Persistence; using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Caching.Memory; namespace LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services; public sealed class SitemapService : ISitemapService { private readonly IRepository repository; - private readonly NavigationManager navigationManager; - private readonly IXmlFileWriter xmlFileWriter; - public SitemapService( - IRepository repository, - NavigationManager navigationManager, - IXmlFileWriter xmlFileWriter) + public SitemapService(IRepository repository) { this.repository = repository; - this.navigationManager = navigationManager; - this.xmlFileWriter = xmlFileWriter; } - public async Task CreateSitemapAsync() + public async Task CreateSitemapAsync(string baseUri) { + ArgumentException.ThrowIfNullOrEmpty(baseUri); + var urlSet = new SitemapUrlSet(); + if (!baseUri.EndsWith('/')) + { + baseUri += "/"; + } + var blogPosts = await repository.GetAllAsync(f => f.IsPublished, b => b.UpdatedDate); - urlSet.Urls.Add(new SitemapUrl { Location = navigationManager.BaseUri }); - urlSet.Urls.Add(new SitemapUrl { Location = $"{navigationManager.BaseUri}archive" }); - urlSet.Urls.AddRange(CreateUrlsForBlogPosts(blogPosts)); - urlSet.Urls.AddRange(CreateUrlsForTags(blogPosts)); + urlSet.Urls.Add(new SitemapUrl { Location = baseUri }); + urlSet.Urls.Add(new SitemapUrl { Location = $"{baseUri}archive" }); + urlSet.Urls.AddRange(CreateUrlsForBlogPosts(blogPosts, baseUri)); + urlSet.Urls.AddRange(CreateUrlsForTags(blogPosts, baseUri)); return urlSet; } - public async Task SaveSitemapToFileAsync(SitemapUrlSet sitemap) - { - await xmlFileWriter.WriteObjectToXmlFileAsync(sitemap, "wwwroot/sitemap.xml"); - } - - private ImmutableArray CreateUrlsForBlogPosts(IEnumerable blogPosts) + private static ImmutableArray CreateUrlsForBlogPosts(IEnumerable blogPosts, string baseUri) { return blogPosts.Select(b => new SitemapUrl { - Location = $"{navigationManager.BaseUri}blogPost/{b.Id}", + Location = $"{baseUri}blogPost/{b.Id}", LastModified = b.UpdatedDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), }).ToImmutableArray(); } - private IEnumerable CreateUrlsForTags(IEnumerable blogPosts) + private static IEnumerable CreateUrlsForTags(IEnumerable blogPosts, string baseUri) { return blogPosts .SelectMany(b => b.Tags) .Distinct() .Select(t => new SitemapUrl { - Location = $"{navigationManager.BaseUri}searchByTag/{Uri.EscapeDataString(t)}", + Location = $"{baseUri}searchByTag/{Uri.EscapeDataString(t)}", }); } } diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/XmlFileWriter.cs b/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/XmlFileWriter.cs deleted file mode 100644 index 7dd7bb42..00000000 --- a/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/XmlFileWriter.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.IO; -using System.Threading.Tasks; -using System.Xml; -using System.Xml.Serialization; - -namespace LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services; - -public sealed class XmlFileWriter : IXmlFileWriter -{ - public async Task WriteObjectToXmlFileAsync(T objectToSave, string fileName) - { - await using var file = File.Create(fileName); - await using var xmlWriter = XmlWriter.Create(file, new XmlWriterSettings { Indent = true, Async = true }); - var serializer = new XmlSerializer(typeof(T)); - serializer.Serialize(xmlWriter, objectToSave); - xmlWriter.Close(); - file.Close(); - } -} \ No newline at end of file diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/XmlWriter.cs b/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/XmlWriter.cs new file mode 100644 index 00000000..1ae6c607 --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/XmlWriter.cs @@ -0,0 +1,19 @@ +using System.IO; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; + +namespace LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services; + +public sealed class XmlWriter : IXmlWriter +{ + public async Task WriteToBuffer(T objectToSave) + { + await using var memoryStream = new MemoryStream(); + await using var xmlWriter = System.Xml.XmlWriter.Create(memoryStream, new XmlWriterSettings { Indent = true, Async = true }); + var serializer = new XmlSerializer(typeof(T)); + serializer.Serialize(xmlWriter, objectToSave); + xmlWriter.Close(); + return memoryStream.ToArray(); + } +} diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/SitemapPage.razor b/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/SitemapPage.razor deleted file mode 100644 index 2304b0b7..00000000 --- a/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/SitemapPage.razor +++ /dev/null @@ -1,55 +0,0 @@ -@page "/Sitemap" -@using LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services -@inject ISitemapService SitemapService -@attribute [Authorize] - -Sitemap - -
-

Sitemap

-
-

A sitemap is a file which lists all important links in a webpage. It helps crawler to find all of the - important pages. Especially newer sites benefit from having a sitemap.xml. - The file will be created at the root of the site. To see the sitemap.xml go here: sitemap.xml.
- If you get a 404 there is currently no sitemap.xml

- - - @if (isGenerating) - { - - } - @if (sitemapUrlSet is not null) - { - - - - - - - - - @foreach (var url in sitemapUrlSet.Urls) - { - - - - - } - -
UrlLast Changed
@url.Location@url.LastModified
- } -
-
- -@code { - private SitemapUrlSet? sitemapUrlSet; - private bool isGenerating; - - private async Task CreateSitemap() - { - isGenerating = true; - sitemapUrlSet = await SitemapService.CreateSitemapAsync(); - isGenerating = false; - await SitemapService.SaveSitemapToFileAsync(sitemapUrlSet); - } -} diff --git a/src/LinkDotNet.Blog.Web/Features/Home/Components/AccessControl.razor b/src/LinkDotNet.Blog.Web/Features/Home/Components/AccessControl.razor index 87760dd2..83e4e2e2 100644 --- a/src/LinkDotNet.Blog.Web/Features/Home/Components/AccessControl.razor +++ b/src/LinkDotNet.Blog.Web/Features/Home/Components/AccessControl.razor @@ -16,7 +16,6 @@
  • Shortcodes
  • -
  • Sitemap
  • Releases
  • diff --git a/src/LinkDotNet.Blog.Web/ServiceExtensions.cs b/src/LinkDotNet.Blog.Web/ServiceExtensions.cs index 93aff1e8..6432c7d0 100644 --- a/src/LinkDotNet.Blog.Web/ServiceExtensions.cs +++ b/src/LinkDotNet.Blog.Web/ServiceExtensions.cs @@ -20,7 +20,7 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddSingleton(); diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Services/SitemapServiceTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Services/SitemapServiceTests.cs index c35bcaa2..2049ecfb 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Services/SitemapServiceTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Services/SitemapServiceTests.cs @@ -1,59 +1,41 @@ -using System; +using System; using System.IO; using System.Threading.Tasks; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services; using Microsoft.AspNetCore.Components; using TestContext = Xunit.TestContext; namespace LinkDotNet.Blog.IntegrationTests.Web.Shared.Services; -public sealed class SitemapServiceTests : IDisposable +public sealed class SitemapServiceTests : SqlDatabaseTestBase { - private const string OutputDirectory = "wwwroot"; - private const string OutputFilename = $"{OutputDirectory}/sitemap.xml"; private readonly SitemapService sut; public SitemapServiceTests() - { - var repositoryMock = Substitute.For>(); - sut = new SitemapService(repositoryMock, Substitute.For(), new XmlFileWriter()); - Directory.CreateDirectory("wwwroot"); - } + => sut = new SitemapService(Repository); [Fact] public async Task ShouldSaveSitemapUrlInCorrectFormat() { - var urlSet = new SitemapUrlSet - { - Urls = - [ - new SitemapUrl { Location = "here", } - ], - }; - await sut.SaveSitemapToFileAsync(urlSet); - - var lines = await File.ReadAllTextAsync(OutputFilename, TestContext.Current.CancellationToken); - lines.ShouldBe( - @" - - - here - -"); - } - - public void Dispose() - { - if (File.Exists(OutputFilename)) - { - File.Delete(OutputFilename); - } - - if (Directory.Exists(OutputDirectory)) - { - Directory.Delete(OutputDirectory, true); - } + var publishedBlogPost = new BlogPostBuilder() + .WithTitle("Title 1") + .WithUpdatedDate(new DateTime(2024, 12, 24)) + .IsPublished() + .Build(); + var unpublishedBlogPost = new BlogPostBuilder() + .IsPublished(false) + .Build(); + await Repository.StoreAsync(publishedBlogPost); + await Repository.StoreAsync(unpublishedBlogPost); + + var sitemap = await sut.CreateSitemapAsync("https://www.linkdotnet.blog"); + + sitemap.Urls.Count.ShouldBe(3); + sitemap.Urls.ShouldContain(u => u.Location == "https://www.linkdotnet.blog/"); + sitemap.Urls.ShouldContain(u => u.Location == "https://www.linkdotnet.blog/archive"); + sitemap.Urls.ShouldContain(u => u.Location == "https://www.linkdotnet.blog/blogPost/" + publishedBlogPost.Id); } } \ No newline at end of file diff --git a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/Services/SitemapServiceTests.cs b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/Services/SitemapServiceTests.cs deleted file mode 100644 index c83c524f..00000000 --- a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/Services/SitemapServiceTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Linq.Expressions; -using System.Threading.Tasks; -using LinkDotNet.Blog.Domain; -using LinkDotNet.Blog.Infrastructure; -using LinkDotNet.Blog.Infrastructure.Persistence; -using LinkDotNet.Blog.TestUtilities; -using LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services; - -namespace LinkDotNet.Blog.UnitTests.Web.Features.Admin.Sitemap.Services; - -public class SitemapServiceTests : BunitContext -{ - private readonly IRepository repositoryMock; - private readonly IXmlFileWriter xmlFileWriterMock; - private readonly SitemapService sut; - private readonly BunitNavigationManager fakeNavigationManager; - - public SitemapServiceTests() - { - repositoryMock = Substitute.For>(); - fakeNavigationManager = new BunitNavigationManager(this); - - xmlFileWriterMock = Substitute.For(); - sut = new SitemapService( - repositoryMock, - fakeNavigationManager, - xmlFileWriterMock); - } - - [Fact] - public async Task ShouldCreateSitemap() - { - var bp1 = new BlogPostBuilder() - .WithUpdatedDate(new DateTime(2020, 1, 1)) - .WithTags("tag1", "tag2") - .Build(); - bp1.Id = "id1"; - var bp2 = new BlogPostBuilder() - .WithUpdatedDate(new DateTime(2019, 1, 1)) - .WithTags("tag2") - .Build(); - bp2.Id = "id2"; - repositoryMock.GetAllAsync( - Arg.Any>>(), - Arg.Any>>(), - true, - Arg.Any(), - Arg.Any()) - .Returns(new PagedList([bp1, bp2], 2, 1, 10)); - - var sitemap = await sut.CreateSitemapAsync(); - - sitemap.Urls.Count.ShouldBe(6); - sitemap.Urls[0].Location.ShouldBe($"{fakeNavigationManager.BaseUri}"); - sitemap.Urls[1].Location.ShouldBe($"{fakeNavigationManager.BaseUri}archive"); - sitemap.Urls[2].Location.ShouldBe($"{fakeNavigationManager.BaseUri}blogPost/id1"); - sitemap.Urls[3].Location.ShouldBe($"{fakeNavigationManager.BaseUri}blogPost/id2"); - sitemap.Urls[4].Location.ShouldBe($"{fakeNavigationManager.BaseUri}searchByTag/tag1"); - sitemap.Urls[5].Location.ShouldBe($"{fakeNavigationManager.BaseUri}searchByTag/tag2"); - } - - [Fact] - public async Task ShouldSaveSitemapToFile() - { - var sitemap = new SitemapUrlSet(); - - await sut.SaveSitemapToFileAsync(sitemap); - - await xmlFileWriterMock.Received(1).WriteObjectToXmlFileAsync(sitemap, "wwwroot/sitemap.xml"); - } -} diff --git a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/Services/XmlFileWriterTests.cs b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/Services/XmlFileWriterTests.cs deleted file mode 100644 index ffbbabfa..00000000 --- a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/Services/XmlFileWriterTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services; -using TestContext = Xunit.TestContext; - -namespace LinkDotNet.Blog.UnitTests.Web.Features.Admin.Sitemap.Services; - -public sealed class XmlFileWriterTests : IDisposable -{ - private const string OutputFilename = "somefile.txt"; - - [Fact] - public async Task ShouldWriteToFile() - { - var myObj = new MyObject { Property = "Prop" }; - - await new XmlFileWriter().WriteObjectToXmlFileAsync(myObj, OutputFilename); - - var content = await File.ReadAllTextAsync(OutputFilename, cancellationToken: TestContext.Current.CancellationToken); - content.ShouldNotBeNull(); - content.ShouldContain("Prop"); - } - - public void Dispose() - { - if (File.Exists(OutputFilename)) - { - File.Delete(OutputFilename); - } - } - - public class MyObject - { - public required string Property { get; set; } - } -} \ No newline at end of file diff --git a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/Services/XmlWriterTests.cs b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/Services/XmlWriterTests.cs new file mode 100644 index 00000000..83255470 --- /dev/null +++ b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/Services/XmlWriterTests.cs @@ -0,0 +1,27 @@ +using System.IO; +using System.Threading.Tasks; +using System.Xml.Serialization; +using LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services; + +namespace LinkDotNet.Blog.UnitTests.Web.Features.Admin.Sitemap.Services; + +public sealed class XmlWriterTests +{ + [Fact] + public async Task ShouldWriteToFile() + { + var myObj = new MyObject { Property = "Prop" }; + + var buffer = await new XmlWriter().WriteToBuffer(myObj); + + var serializer = new XmlSerializer(typeof(MyObject)); + await using var memoryStream = new MemoryStream(buffer); + var myObject = serializer.Deserialize(memoryStream) as MyObject; + myObject.ShouldNotBeNull(); + } + + public class MyObject + { + public required string Property { get; set; } + } +} \ No newline at end of file diff --git a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/SitemapPageTests.cs b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/SitemapPageTests.cs deleted file mode 100644 index 4b47ffd3..00000000 --- a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/Sitemap/SitemapPageTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using LinkDotNet.Blog.Web.Features.Admin.Sitemap; -using LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services; -using LinkDotNet.Blog.Web.Features.Components; -using Microsoft.Extensions.DependencyInjection; -using TestContext = Xunit.TestContext; - -namespace LinkDotNet.Blog.UnitTests.Web.Features.Admin.Sitemap; - -public class SitemapPageTests : BunitContext -{ - [Fact] - public async Task ShouldSaveSitemap() - { - AddAuthorization().SetAuthorized("steven"); - var sitemapMock = Substitute.For(); - Services.AddScoped(_ => sitemapMock); - var sitemap = new SitemapUrlSet(); - sitemapMock.CreateSitemapAsync().Returns(sitemap); - var cut = Render(); - - cut.Find("button").Click(); - - await sitemapMock.Received(1).SaveSitemapToFileAsync(sitemap); - } - - [Fact] - public void ShouldDisplaySitemap() - { - AddAuthorization().SetAuthorized("steven"); - var sitemapMock = Substitute.For(); - Services.AddScoped(_ => sitemapMock); - var sitemap = new SitemapUrlSet - { - Urls = new List - { - new() { Location = "loc", LastModified = "Now" }, - }, - }; - sitemapMock.CreateSitemapAsync().Returns(sitemap); - var cut = Render(); - - cut.Find("button").Click(); - - var row = cut.WaitForElements("tr").Last(); - row.Children.First().InnerHtml.ShouldBe("loc"); - row.Children.Last().InnerHtml.ShouldBe("Now"); - } - - [Fact] - public void ShouldShowLoadingWhenGenerating() - { - AddAuthorization().SetAuthorized("steven"); - var sitemapMock = Substitute.For(); - Services.AddScoped(_ => sitemapMock); - var sitemap = new SitemapUrlSet - { - Urls = new List - { - new() { Location = "loc", LastModified = "Now" }, - }, - }; - sitemapMock.CreateSitemapAsync().Returns(Task.Run(async () => - { - await Task.Delay(1000, TestContext.Current.CancellationToken); - return sitemap; - })); - - var cut = Render(); - - cut.Find("button").Click(); - - cut.FindComponents().Count.ShouldBe(1); - var btn = cut.Find("button"); - btn.Attributes.Any(a => a.Name == "disabled").ShouldBeTrue(); - } -} \ No newline at end of file