From e689dbfe354274d7ad0d219769e44c3be4590bdf Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 22:41:05 +0100 Subject: [PATCH] fixes the tests --- .../BlogApiMappedClaimsTests.cs | 16 +++++---- src/Yavsc.Blogs.Tests/BlogApiTests.cs | 33 ++++++++++--------- src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs | 11 ------- .../{ => Fixtures}/BlogsWebServerFixture.cs | 0 .../MappedClaimsBlogsWebServerFixture.cs | 2 +- src/Yavsc.Blogs.Tests/PublishEndpointTests.cs | 23 ++++++------- src/Yavsc.Server/Helpers/PayloadHelpers.cs | 5 +-- src/Yavsc.Tests.Shared/BlogHelpers.cs | 31 +++++++++++++++++ .../IBackendFixture.cs | 6 ++-- src/Yavsc.Tests.Shared/WebHostFixture.cs | 2 +- .../Yavsc.Tests.Shared.csproj | 5 ++- 11 files changed, 79 insertions(+), 55 deletions(-) delete mode 100644 src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs rename src/Yavsc.Blogs.Tests/{ => Fixtures}/BlogsWebServerFixture.cs (100%) rename src/Yavsc.Blogs.Tests/{ => Fixtures}/MappedClaimsBlogsWebServerFixture.cs (99%) create mode 100644 src/Yavsc.Tests.Shared/BlogHelpers.cs rename src/{Yavsc.Blogs.Tests/Fixtures => Yavsc.Tests.Shared}/IBackendFixture.cs (80%) diff --git a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs index 7c5747d0..f6b46ba9 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs @@ -8,11 +8,12 @@ using Microsoft.IdentityModel.Tokens; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Tests.Shared; +using Yavsc.Blogs.Tests.Fixtures; namespace Yavsc.Blogs.Tests; [Collection("JwtClaimMapping")] -public sealed class BlogApiMappedClaimsTests : +public sealed class BlogApiMappedClaimsTests : IClassFixture, IBackendFixture { @@ -85,7 +86,10 @@ IBackendFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); + var response = await http.PostAsJsonAsync( + _fixture.BlogSpotUrl(), + draft, + TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, response.StatusCode); var created = await response.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); @@ -99,7 +103,7 @@ IBackendFixture ResetDatabase(); using var http = NewClient(subject: "mapped-owner"); - var createdResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), new BlogPost + var createdResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), new BlogPost { Id = 0, Title = "Billet à modifier", @@ -113,7 +117,7 @@ IBackendFixture var created = await createdResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); Assert.NotNull(created); - var updateResponse = await http.PutAsJsonAsync($"/api/v1/blog/{created!.Id}", new BlogPost + var updateResponse = await http.PutAsJsonAsync(_fixture.BlogSpotUrl() + $"/{created!.Id}", new BlogPost { Id = created.Id, Title = "Billet modifié", @@ -132,7 +136,7 @@ IBackendFixture ResetDatabase(); using var ownerHttp = NewClient(subject: "mapped-owner"); - var createdResponse = await ownerHttp.PostAsJsonAsync(_fixture.BlogUrl(), new BlogPost + var createdResponse = await ownerHttp.PostAsJsonAsync(_fixture.BlogSpotUrl(), new BlogPost { Id = 0, Title = "Billet protégé", @@ -147,7 +151,7 @@ IBackendFixture Assert.NotNull(created); using var otherHttp = NewClient(subject: "mapped-other"); - var updateResponse = await otherHttp.PutAsJsonAsync($"/api/v1/blog/{created!.Id}", new BlogPost + var updateResponse = await otherHttp.PutAsJsonAsync(_fixture.BlogSpotUrl() + $"/{created!.Id}", new BlogPost { Id = created.Id, Title = "Tentative de modification", diff --git a/src/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs index f188912f..11f5c0a2 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiTests.cs @@ -6,6 +6,7 @@ using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Server.Helpers; using Yavsc.Tests.Shared; +using Yavsc.Blogs.Tests.Fixtures; namespace Yavsc.Blogs.Tests; @@ -46,7 +47,7 @@ public sealed class BlogApiTests : IClassFixture _fixture.SeedUser("tester"); } - + /// Build an authenticated client: a real /// Authorization: Bearer <jwt> header where the JWT /// is signed by and carries @@ -94,7 +95,7 @@ public sealed class BlogApiTests : IClassFixture using var http = NewClient(); var response = await http.GetAsync( - _fixture.BlogUrl(), + _fixture.BlogSpotUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -127,7 +128,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -140,7 +141,7 @@ public sealed class BlogApiTests : IClassFixture Assert.Equal(draft.Title, created.Title); // The list should now contain exactly one entry. - var listResponse = await http.GetAsync(_fixture.BlogUrl(), + var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); @@ -168,7 +169,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -178,7 +179,7 @@ public sealed class BlogApiTests : IClassFixture Assert.NotNull(created); Assert.Equal("tester", created!.AuthorId); - var listResponse = await http.GetAsync(_fixture.BlogUrl(), + var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); @@ -206,7 +207,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -255,7 +256,7 @@ public sealed class BlogApiTests : IClassFixture // the framework returns 401. This is the proof that the // production policy is wired in the test host and not // short-circuited by a test-only auth bypass. - var response = await http.GetAsync(_fixture.BlogUrl(), + var response = await http.GetAsync(_fixture.BlogSpotUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } @@ -283,7 +284,7 @@ public sealed class BlogApiTests : IClassFixture DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -302,13 +303,13 @@ public sealed class BlogApiTests : IClassFixture DateCreated = created.DateCreated, DateModified = DateTime.UtcNow }; - var putResponse = await http.PutAsJsonAsync(_fixture.BlogUrl()+$"/{created.Id}", + var putResponse = await http.PutAsJsonAsync(_fixture.BlogSpotUrl()+$"/{created.Id}", update, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NoContent, putResponse.StatusCode); // The list should now reflect the new title. - var listResponse = await http.GetAsync(_fixture.BlogUrl(), + var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); using var doc = JsonDocument.Parse( @@ -336,19 +337,19 @@ public sealed class BlogApiTests : IClassFixture DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); var created = (await postResponse.Content.ReadFromJsonAsync( TestContext.Current.CancellationToken ))!; - var deleteResponse = await http.DeleteAsync(_fixture.BlogUrl()+$"/{created.Id}", + var deleteResponse = await http.DeleteAsync(_fixture.BlogSpotUrl()+$"/{created.Id}", TestContext.Current.CancellationToken ); Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode); // The list should now be empty. - var listResponse = await http.GetAsync(_fixture.BlogUrl(), + var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), TestContext.Current.CancellationToken); String response = await listResponse.Content.ReadAsStringAsync( TestContext.Current.CancellationToken @@ -391,7 +392,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var response = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); // Dump the body on failure so the test name + the response @@ -436,7 +437,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, + var response = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, TestContext.Current.CancellationToken); if (response.StatusCode != HttpStatusCode.BadRequest) diff --git a/src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs b/src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs deleted file mode 100644 index 06e9a5af..00000000 --- a/src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Yavsc.Blogs.Tests.Fixtures; - - -public static class BlogHelpers -{ - public static string BlogUrl(this IBackendFixture fixture) - => $"{fixture.Addresses.First(a => a.StartsWith("https://"))}/{Constants.APIPrefix}/{Constants.BlogSpotPath}"; - - public static string BlogAclUrl(this IBackendFixture fixture) - => $"{fixture.Addresses.First(a => a.StartsWith("https://"))}/{Constants.APIPrefix}/{Constants.BlogAclPath}"; -} \ No newline at end of file diff --git a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs similarity index 100% rename from src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs rename to src/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs diff --git a/src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs similarity index 99% rename from src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs rename to src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs index 127f38fe..2546de0a 100644 --- a/src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs +++ b/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs @@ -21,7 +21,7 @@ namespace Yavsc.Blogs.Tests; /// This is the closest in-process reproduction of the production /// authentication surface for the blog API. /// -public sealed class MappedClaimsBlogsWebServerFixture : IDisposable +public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFixture { private readonly InMemoryDatabaseRoot _inMemoryRoot = new(); private readonly Dictionary _savedInboundMap; diff --git a/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs b/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs index 7d5a02a9..113707ca 100644 --- a/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs +++ b/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Tests.Shared; +using Yavsc.Blogs.Tests.Fixtures; namespace Yavsc.Blogs.Tests; @@ -71,12 +72,6 @@ public sealed class PublishEndpointTests : IClassFixture return post.Id; } - private string PublishUrl(long id) - => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/api/v1/blog/{id}/publish"; - - private string BlogsUrl - => _fixture.Addresses.First(a => a.StartsWith("https://")) + "/api/v1/blog"; - private HttpClient NewClient(string subject) { var handler = new HttpClientHandler @@ -100,12 +95,13 @@ public sealed class PublishEndpointTests : IClassFixture var postId = SeedPost("alice"); using var http = NewClient("alice"); - var put = await http.PutAsJsonAsync(PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NoContent, put.StatusCode); - var get = await http.GetAsync($"{BlogsUrl}/{postId}", TestContext.Current.CancellationToken); + var get = await http.GetAsync(_fixture.BlogSpotUrl() + $"/{postId}", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, get.StatusCode); using var doc = JsonDocument.Parse(await get.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); + Assert.Equal($"post-by-alice", doc.RootElement.GetProperty("title").GetString()); Assert.True(doc.RootElement.GetProperty("isPublished").GetBoolean()); } @@ -116,11 +112,12 @@ public sealed class PublishEndpointTests : IClassFixture var postId = SeedPost("alice"); using var http = NewClient("alice"); - await http.PutAsJsonAsync(PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); - var put = await http.PutAsJsonAsync(PublishUrl(postId), new { publish = false }, TestContext.Current.CancellationToken); + await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = false }, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NoContent, put.StatusCode); - var get = await http.GetAsync($"{BlogsUrl}/{postId}", TestContext.Current.CancellationToken); + var get = await http.GetAsync(_fixture.BlogSpotUrl() + $"/{postId}", TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.OK, get.StatusCode); using var doc = JsonDocument.Parse(await get.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); Assert.False(doc.RootElement.GetProperty("isPublished").GetBoolean()); } @@ -130,7 +127,7 @@ public sealed class PublishEndpointTests : IClassFixture { ResetDatabase(); using var http = NewClient("alice"); - var put = await http.PutAsJsonAsync(PublishUrl(99999L), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(_fixture.PublishUrl(99999L), new { publish = true }, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NotFound, put.StatusCode); } @@ -141,7 +138,7 @@ public sealed class PublishEndpointTests : IClassFixture var postId = SeedPost("alice"); using var http = NewClient("bob"); - var put = await http.PutAsJsonAsync(PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); // 401 Challenge (the controller returns Challenge() // for AuthorizationFailureException). The exact code // is framework-dependent; what matters is "not 204". diff --git a/src/Yavsc.Server/Helpers/PayloadHelpers.cs b/src/Yavsc.Server/Helpers/PayloadHelpers.cs index 293cab02..cd359bb4 100644 --- a/src/Yavsc.Server/Helpers/PayloadHelpers.cs +++ b/src/Yavsc.Server/Helpers/PayloadHelpers.cs @@ -15,7 +15,8 @@ public static class PayloadHelpers post.UserModified, post.AuthorId, ACL = post.GetACL(), - Tags = post.GetTags() + Tags = post.GetTags(), + post.IsPublished }; } -} \ No newline at end of file +} diff --git a/src/Yavsc.Tests.Shared/BlogHelpers.cs b/src/Yavsc.Tests.Shared/BlogHelpers.cs new file mode 100644 index 00000000..ef5a7688 --- /dev/null +++ b/src/Yavsc.Tests.Shared/BlogHelpers.cs @@ -0,0 +1,31 @@ + +namespace Yavsc.Blogs.Tests.Fixtures; +using static Yavsc.Constants; + +public static class BlogHelpers +{ + public static string ApiUrl(this IBackendFixture fixture, string apiSubPath) + { + var secured = fixture.Addresses.FirstOrDefault(a => a.StartsWith("https://")); + if (secured is null) + { + var unsecured = fixture.Addresses.FirstOrDefault(a => a.StartsWith("http://")); + if (unsecured is null) + { + throw new InvalidOperationException("No backend address found"); + } + return $"{unsecured}/{APIPrefix}/{apiSubPath}"; + } + return $"{secured}/{APIPrefix}/{apiSubPath}"; + } + + public static string BlogAclUrl(this IBackendFixture fixture) + => fixture.ApiUrl(BlogAclPath); + + public static string BlogSpotUrl(this IBackendFixture fixture) + => fixture.ApiUrl(BlogSpotPath); + + public static string PublishUrl(this IBackendFixture fixture, long id) + => fixture.ApiUrl(BlogSpotPath) +"/" + id + "/publish"; + +} diff --git a/src/Yavsc.Blogs.Tests/Fixtures/IBackendFixture.cs b/src/Yavsc.Tests.Shared/IBackendFixture.cs similarity index 80% rename from src/Yavsc.Blogs.Tests/Fixtures/IBackendFixture.cs rename to src/Yavsc.Tests.Shared/IBackendFixture.cs index 61202a0a..4e2f009d 100644 --- a/src/Yavsc.Blogs.Tests/Fixtures/IBackendFixture.cs +++ b/src/Yavsc.Tests.Shared/IBackendFixture.cs @@ -1,7 +1,5 @@ -using Yavsc.Blogs.Tests; - -public interface IBackendFixture +public interface IBackendFixture { /// /// The addresses the fixture bound to. @@ -12,4 +10,4 @@ public interface IBackendFixture /// The service provider for the fixture host. /// IServiceProvider Services { get; } -} \ No newline at end of file +} diff --git a/src/Yavsc.Tests.Shared/WebHostFixture.cs b/src/Yavsc.Tests.Shared/WebHostFixture.cs index 417d29d8..95b23082 100644 --- a/src/Yavsc.Tests.Shared/WebHostFixture.cs +++ b/src/Yavsc.Tests.Shared/WebHostFixture.cs @@ -30,7 +30,7 @@ namespace Yavsc.Tests.Shared; /// mapping are the responsibility of the subclass, through /// . /// -public abstract class WebHostFixture : IDisposable +public abstract class WebHostFixture : IDisposable, IBackendFixture { private static readonly Lazy _selfSignedCertificate = new Lazy(CreateSelfSignedCertificate); diff --git a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj index 7bd8f24a..6e7a4c5a 100644 --- a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj +++ b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj @@ -23,4 +23,7 @@ - \ No newline at end of file + + + +