From 0034a311f279ecf3246305e854d324de4f183e24 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Wed, 26 Aug 2026 23:08:23 +0100 Subject: [PATCH 1/6] chore(release): bump version via gitversion for 1.0.8-rc4 --- src/PostIt/PostIt.Android/PostIt.Android.csproj | 13 ++++++------- src/PostIt/PostIt.Browser/PostIt.Browser.csproj | 4 ++-- src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj | 4 ++-- src/PostIt/PostIt.Tests/PostIt.Tests.csproj | 9 +++------ src/PostIt/PostIt/PostIt.csproj | 11 ++++++----- src/Yavsc.Abstract/Yavsc.Abstract.csproj | 4 ++-- src/Yavsc.Api.Client/Yavsc.Api.Client.csproj | 4 ++-- src/Yavsc.Api/Yavsc.Api.csproj | 4 ++-- src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj | 4 ++-- src/Yavsc.Blogs/Yavsc.Blogs.csproj | 4 ++-- src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj | 4 ++-- src/Yavsc.Org/Yavsc.Org.csproj | 4 ++-- src/Yavsc.Server/Yavsc.Server.csproj | 4 ++-- src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj | 4 ++-- src/cli/cli.csproj | 2 +- 15 files changed, 38 insertions(+), 41 deletions(-) diff --git a/src/PostIt/PostIt.Android/PostIt.Android.csproj b/src/PostIt/PostIt.Android/PostIt.Android.csproj index 62b494f8..7f44645d 100644 --- a/src/PostIt/PostIt.Android/PostIt.Android.csproj +++ b/src/PostIt/PostIt.Android/PostIt.Android.csproj @@ -9,23 +9,22 @@ 1.0 apk false + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1 - Resources\drawable\Icon.png - - - - + - - + \ No newline at end of file diff --git a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj index 2816b2f8..67600a72 100644 --- a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj +++ b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj @@ -6,7 +6,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -15,4 +15,4 @@ - + \ No newline at end of file diff --git a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj index e47c5b6d..30ba65c4 100644 --- a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj +++ b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj @@ -7,7 +7,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -24,4 +24,4 @@ - + \ No newline at end of file diff --git a/src/PostIt/PostIt.Tests/PostIt.Tests.csproj b/src/PostIt/PostIt.Tests/PostIt.Tests.csproj index 663bdd12..86a7998a 100644 --- a/src/PostIt/PostIt.Tests/PostIt.Tests.csproj +++ b/src/PostIt/PostIt.Tests/PostIt.Tests.csproj @@ -8,7 +8,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -16,9 +16,7 @@ - - @@ -28,6 +26,5 @@ - - - + + \ No newline at end of file diff --git a/src/PostIt/PostIt/PostIt.csproj b/src/PostIt/PostIt/PostIt.csproj index bde8fd41..1ae36a44 100644 --- a/src/PostIt/PostIt/PostIt.csproj +++ b/src/PostIt/PostIt/PostIt.csproj @@ -1,14 +1,16 @@ - + net10.0 enable latest + 1.1.0.0 + 1.1.0.0 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1 - - PreserveNewest @@ -31,9 +33,8 @@ - - + \ No newline at end of file diff --git a/src/Yavsc.Abstract/Yavsc.Abstract.csproj b/src/Yavsc.Abstract/Yavsc.Abstract.csproj index ebbb2d1e..5094b14d 100644 --- a/src/Yavsc.Abstract/Yavsc.Abstract.csproj +++ b/src/Yavsc.Abstract/Yavsc.Abstract.csproj @@ -11,10 +11,10 @@ latest 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 - + \ No newline at end of file diff --git a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj index 047223a6..4c560f23 100644 --- a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj +++ b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj @@ -17,7 +17,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -26,4 +26,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Api/Yavsc.Api.csproj b/src/Yavsc.Api/Yavsc.Api.csproj index 2ddeb72d..3655079b 100644 --- a/src/Yavsc.Api/Yavsc.Api.csproj +++ b/src/Yavsc.Api/Yavsc.Api.csproj @@ -7,7 +7,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -17,4 +17,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj index 34536806..bc4e34ba 100644 --- a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj +++ b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj @@ -9,7 +9,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -35,4 +35,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj index 019fb102..8692b28a 100644 --- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj +++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj @@ -8,7 +8,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -18,4 +18,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj index 34de5bd8..8019a78f 100644 --- a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj +++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj @@ -11,7 +11,7 @@ $(MSBuildProjectDirectory)\test.runsettings 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -89,4 +89,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Org/Yavsc.Org.csproj b/src/Yavsc.Org/Yavsc.Org.csproj index aeb94a8e..6dbceb4a 100644 --- a/src/Yavsc.Org/Yavsc.Org.csproj +++ b/src/Yavsc.Org/Yavsc.Org.csproj @@ -9,7 +9,7 @@ https://github.com/pazof/yavsc 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -54,4 +54,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Server/Yavsc.Server.csproj b/src/Yavsc.Server/Yavsc.Server.csproj index 7adb3a78..839e476a 100644 --- a/src/Yavsc.Server/Yavsc.Server.csproj +++ b/src/Yavsc.Server/Yavsc.Server.csproj @@ -9,7 +9,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -43,4 +43,4 @@ - + \ No newline at end of file diff --git a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj index 7f2f382a..7bd8f24a 100644 --- a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj +++ b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj @@ -14,7 +14,7 @@ --> 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 @@ -23,4 +23,4 @@ - + \ No newline at end of file diff --git a/src/cli/cli.csproj b/src/cli/cli.csproj index 237ccdd8..49aa64b1 100644 --- a/src/cli/cli.csproj +++ b/src/cli/cli.csproj @@ -7,7 +7,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 + 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 1.1.0-beta.1 -- 2.47.3 From e62adedc1782df80b387bb0823a261a69cc26b58 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 20:10:32 +0100 Subject: [PATCH 2/6] Fixes ACL backend support --- .vscode/tasks.json | 32 +++---- CHANGELOG.md | 10 +++ src/Yavsc.Abstract/Constants.cs | 7 ++ src/Yavsc.Blogs.Tests/BlogAclApiTests.cs | 90 ++++++++++++++++++- src/Yavsc.Blogs.Tests/BlogApiTests.cs | 21 +---- .../BlogsWebServerFixture.cs | 43 ++++++++- .../Controllers/BlogApiController.cs | 17 ++-- .../Controllers/BlogTagsApiController.cs | 2 +- .../Controllers/CircleApiController.cs | 2 +- .../Controllers/CommentsApiController.cs | 2 +- src/Yavsc.Server/Helpers/PayloadHelpers.cs | 21 +++++ src/Yavsc.Server/Models/Blog/BlogPost.cs | 3 +- src/Yavsc.Server/Models/Blog/BlogTag.cs | 3 + src/Yavsc.Server/Models/Blog/Comment.cs | 8 +- 14 files changed, 205 insertions(+), 56 deletions(-) create mode 100644 src/Yavsc.Server/Helpers/PayloadHelpers.cs diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3c0c0bf2..3faedae0 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,5 +1,21 @@ { "version": "2.0.0", + "isRoot": true, + "problemMatcher": [ + { + "owner": "dotnet", + "fileLocation": ["relative", "${workspaceFolder}"], + "source": "dotnet", + "pattern": { + "regexp": "^\\s+(.*)\\((\\d+):(\\d+)\\):\\s+(error|warning)\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + ], "tasks": [ { "label": "run-debug-android", @@ -20,21 +36,6 @@ "-p:AndroidAttachDebugger=true", "-p:AndroidSdbHostPort=55555", "-p:AndroidSdbTargetPort=55555" - ], - "problemMatcher": [ - { - "owner": "dotnet", - "fileLocation": ["relative", "${workspaceFolder}"], - "source": "dotnet", - "pattern": { - "regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*)$", - "file": 1, - "line": 2, - "column": 3, - "severity": 4, - "message": 5 - } - } ] }, { @@ -45,7 +46,6 @@ "group": "build", "isBuildCommand": true, "isTestCommand": false, - "problemMatcher": ["$msCompile"], "isBackground": true }, { diff --git a/CHANGELOG.md b/CHANGELOG.md index 6adf462a..8425c2c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,16 @@ Cette convention est partagée avec le dépôt [`postit-debian`](https://forgejo.pschneider.fr/notazof/postit-debian) pour la production des paquets `.deb`. +## [1.0.8-rc4] - unstable + +### Added + +### Changed + +### Fixed + +* [TODO] bug loading a blog post from PostIt, ACL come along with and don't need any "Refresh" button. + ## [1.0.8-rc3] - unstable ### Added diff --git a/src/Yavsc.Abstract/Constants.cs b/src/Yavsc.Abstract/Constants.cs index 78ff0838..a2649491 100644 --- a/src/Yavsc.Abstract/Constants.cs +++ b/src/Yavsc.Abstract/Constants.cs @@ -7,6 +7,13 @@ namespace Yavsc { public const string APIPrefix = "api/v1"; + + public const string BlogSpotPath = "blogspot"; + public const string BlogAclPath = "blogacl"; + public const string BlogTagPath = "blogtag"; + public const string CirclePath = "circle"; + public const string CommentsPath = "blogcomments"; + public static readonly Scope[] SiteScopes = { new Scope { Id = "profile", Description = "Your profile informations" }, new Scope { Id = "book" , Description ="Your booking interface"}, diff --git a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs index 92dd3b2c..c140c290 100644 --- a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs @@ -1,10 +1,12 @@ using System.Net; using System.Net.Http.Json; +using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Yavsc.Abstract.BlogSpot; using Yavsc.Models; using Yavsc.Models.Access; +using Yavsc.Models.Blog; using Yavsc.Tests.Shared; using static Yavsc.Constants; @@ -38,15 +40,16 @@ public sealed class BlogAclApiTests : IClassFixture { private readonly BlogsWebServerFixture _fixture; - public BlogAclApiTests(BlogsWebServerFixture fixture) { _fixture = fixture; } + private string BlogUrl() + => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/{BlogSpotPath}"; private string BlogAclUrl() - => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/blogacl"; + => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/{BlogAclPath}"; /// Delete any ACL rows tied to the fixture's seeded /// (CircleId, BlogPostId) pair. The shared SQLite store @@ -120,7 +123,7 @@ public sealed class BlogAclApiTests : IClassFixture // owned by the caller. We seed the same shape pre-POST so the // test reproduces the prod scenario end-to-end. CleanupAcl(); - using var http = NewClient("alice"); + using var http = NewClient(_fixture.DefaultUserLogin); var payload = new PostAccessControlRulePayload { @@ -178,7 +181,7 @@ public sealed class BlogAclApiTests : IClassFixture [MemberData(nameof(BlogAclPayloadsForNever500))] public async Task PostCircleAuthorization_never_returns_500(PostAccessControlRulePayload payload) { - using var http = NewClient("alice"); + using var http = NewClient(_fixture.DefaultUserLogin); var response = await http.PostAsJsonAsync( BlogAclUrl(), payload, @@ -216,4 +219,83 @@ public sealed class BlogAclApiTests : IClassFixture ); } + + [Fact] + public async Task PostBlog_with_ACL_creates_a_post_and_Get_returns_it_in_the_list() + { + CleanupAcl(); + _fixture.SeedUser(_fixture.DefaultUserLogin); + _fixture.SeedUser("tester"); + _fixture.SeedCircle(_fixture.DefaultUserLogin, "test", + false, + new String[] + { + _fixture.DefaultUserLogin, + "tester" + }); + using var http = NewClient(_fixture.DefaultUserLogin ); + + // Create a minimal BlogPost. The server assigns Id, so we + // send 0 + an explicit AuthorId; the production + // BlogSpotService.Create() tolerates that. + var draft = new BlogPost + { + Id = 0, + Title = "Premier billet", + AuthorId = "tester", + Article = "Contenu de test.", + DateCreated = DateTime.UtcNow, + DateModified = DateTime.UtcNow, + ACL = new List( + new CircleAuthorizationToBlogPost[] + { + new CircleAuthorizationToBlogPost + { + CircleId = _fixture.CircleId, + BlogPostId = _fixture.PostId + } + } + ) + }; + + var postResponse = await http.PostAsJsonAsync( + BlogUrl(), + draft, + TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); + + // The POST returns the server-issued post (with a real Id). + var created = await postResponse.Content.ReadFromJsonAsync( + TestContext.Current.CancellationToken + ); + Assert.NotNull(created); + Assert.NotEqual(0, created!.Id); + Assert.Equal(draft.Title, created.Title); + + // The list should now contain exactly one entry. + var listResponse = await http.GetAsync( + BlogUrl(), + TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); + + using var doc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync( + TestContext.Current.CancellationToken + )); + Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind); + Assert.Equal(2, doc.RootElement.GetArrayLength()); + Assert.Equal(created.Id, doc.RootElement[0].GetProperty("id").GetInt64()); + + // detail should return the same post, with ACL and tags. + var detailResponse = await http.GetAsync( + $"{BlogUrl()}/{created.Id}", + TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.OK, detailResponse.StatusCode); + using var detailDoc = JsonDocument.Parse(await detailResponse.Content.ReadAsStringAsync( + TestContext.Current.CancellationToken + )); + Assert.Equal(JsonValueKind.Object, detailDoc.RootElement.ValueKind); + Assert.Equal(created.Id, detailDoc.RootElement.GetProperty("id").GetInt64()); + Assert.Equal(1, detailDoc.RootElement.GetProperty("acl").GetArrayLength()); + } + } diff --git a/src/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs index 5a218316..e438e0b8 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiTests.cs @@ -2,7 +2,6 @@ using System.Net; using System.Net.Http.Json; using System.Security.Claims; using System.Text.Json; -using Microsoft.Extensions.DependencyInjection; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Server.Helpers; @@ -31,18 +30,6 @@ public sealed class BlogApiTests : IClassFixture _fixture = fixture; } - /// Reset the in-memory database to a known empty state. - /// UseInMemoryDatabase shares its store across the - /// lifetime of the instance, - /// so without a per-test reset the test order would leak - /// state between tests. - private void ResetDatabase() - { - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - db.Database.EnsureDeleted(); - db.Database.EnsureCreated(); - } /// Reset the database and seed the /// tester row. Required @@ -55,7 +42,7 @@ public sealed class BlogApiTests : IClassFixture /// at SaveChanges and the controller returns 500. private void ResetAndSeedDefaultUser() { - ResetDatabase(); + _fixture.ResetDatabase(); _fixture.SeedUser("tester"); } @@ -111,7 +98,7 @@ public sealed class BlogApiTests : IClassFixture [Fact] public async Task GetBlogs_returns_200_with_empty_list_when_no_posts() { - ResetDatabase(); + _fixture.ResetDatabase(); using var http = NewClient(); var response = await http.GetAsync("/api/v1/blog", @@ -266,7 +253,7 @@ public sealed class BlogApiTests : IClassFixture [Fact] public async Task GetBlog_returns_401_when_no_token_is_provided() { - ResetDatabase(); + _fixture.ResetDatabase(); using var http = NewAnonymousClient(); // No Authorization header → the JwtBearer middleware @@ -443,7 +430,7 @@ public sealed class BlogApiTests : IClassFixture // behaviour so a future change that, say, makes Title // nullable in the model or drops [Required], triggers a // conscious update of the test (and probably of the VM). - ResetDatabase(); + _fixture.ResetDatabase(); using var http = NewClient(subject: "tester"); var draft = new BlogPost diff --git a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs index 218904ef..695af4c5 100644 --- a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs +++ b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs @@ -61,6 +61,7 @@ public sealed class BlogsWebServerFixture : WebHostFixture public long CircleId { get; private set; } public long PostId { get; private set; } + public string DefaultUserLogin { get => "alice"; } // A single SqliteConnection held open at the static level, // mirroring how Yavsc.Org.Tests.WebServerFixture hoists its @@ -169,7 +170,8 @@ public sealed class BlogsWebServerFixture : WebHostFixture // PermissionHandler ownership check sees a null // user id and rejects every PUT. options.MapInboundClaims = false; - options.TokenValidationParameters = new TokenValidationParameters + options.TokenValidationParameters + = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = TestTokenIssuer.Issuer, @@ -263,6 +265,27 @@ public sealed class BlogsWebServerFixture : WebHostFixture await Task.CompletedTask; return app; } +/// Reset the in-memory database to a known empty state. + /// UseInMemoryDatabase shares its store across the + /// lifetime of the instance, + /// so without a per-test reset the test order would leak + /// state between tests. + public void ResetDatabase() + { + using var scope = Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.EnsureDeleted(); + db.Database.EnsureCreated(); + } + public void CleanupAcl() + { + using var scope = Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + db.CircleAuthorizationToBlogPost + .Where(a => a.CircleId == CircleId + && a.BlogPostId == PostId) + .ExecuteDelete(); + } public override void Dispose() { @@ -310,7 +333,8 @@ public sealed class BlogsWebServerFixture : WebHostFixture /// Optional hook to fill in fields /// like FullName / Avatar / EmailConfirmed /// that downstream tests assert on. - public ApplicationUser SeedUser(string userName, Action? configure = null) + public ApplicationUser SeedUser(string userName, + Action? configure = null) { using var scope = Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); @@ -351,20 +375,31 @@ public sealed class BlogsWebServerFixture : WebHostFixture /// Create a circle owned by /// directly in the SQLite store and return its server-assigned /// id. - private long SeedCircle(string ownerId, string name, bool isPublic = false) + public long SeedCircle(string ownerId, string name, bool isPublic = false, + ICollection members = null + ) { using var scope = Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var circle = new Circle { OwnerId = ownerId, Name = name, Public = isPublic }; db.Circle.Add(circle); db.SaveChanges(); + if (members != null && members.Count > 0) + { + foreach (String memberId in members) + { + var member = new CircleMember { CircleId = circle.Id, MemberId = memberId }; + db.CircleMembers.Add(member); + } + db.SaveChanges(); + } return circle.Id; } /// Create a blog post owned by /// directly in the SQLite store and return its server-assigned /// id. - private long SeedBlogPost(string authorId, string title) + public long SeedBlogPost(string authorId, string title) { using var scope = Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); diff --git a/src/Yavsc.Blogs/Controllers/BlogApiController.cs b/src/Yavsc.Blogs/Controllers/BlogApiController.cs index fcd3a336..8c76f7ee 100644 --- a/src/Yavsc.Blogs/Controllers/BlogApiController.cs +++ b/src/Yavsc.Blogs/Controllers/BlogApiController.cs @@ -9,7 +9,7 @@ namespace Yavsc.Blogs.Controllers { [Authorize("BlogScope")] [Produces("application/json")] - [Route(APIPrefix + "/blog")] + [Route(APIPrefix + "/" + BlogSpotPath)] public class BlogApiController : Controller { private readonly BlogSpotService blogSpotService; @@ -19,14 +19,14 @@ namespace Yavsc.Blogs.Controllers this.blogSpotService = blogSpotService; } - // GET: api/BlogApi + // GET: api/v1/blogspot [HttpGet] public async Task> GetBlogspot(int start = 0, int take = 25) { return await blogSpotService.Index(User, null, start, take); } - // GET: api/BlogApi/5 + // GET: api/v1/blogspot/5 [HttpGet("{id}", Name = "GetBlog")] public async Task GetBlog([FromRoute] long id) { @@ -43,7 +43,7 @@ namespace Yavsc.Blogs.Controllers return NotFound(); } - return Ok(blog); + return Ok(blog.GetPayload()); } catch (AuthorizationFailureException) { @@ -51,7 +51,7 @@ namespace Yavsc.Blogs.Controllers } } - // PUT: api/BlogApi/5 + // PUT: api/v1/blogspot/5 [HttpPut("{id}")] public async Task PutBlog(long id, [FromBody] Models.Blog.BlogPost blog) { @@ -83,7 +83,7 @@ namespace Yavsc.Blogs.Controllers return new StatusCodeResult(StatusCodes.Status204NoContent); } - // POST: api/v1/blog + // POST: api/v1/blogspot [HttpPost] public IActionResult PostBlog([FromBody] Models.Blog.BlogPost blog) { @@ -116,7 +116,8 @@ namespace Yavsc.Blogs.Controllers : (IFormFileCollection)new FormFileCollection(); var uid = User.GetUserId(); var post = blogSpotService.Create(uid, blog, files); - return CreatedAtRoute("GetBlog", new { id = post.Id }, post); + return CreatedAtRoute("GetBlog", new { id = post.Id }, + post.GetPayload()); } // DELETE: api/BlogApi/5 @@ -135,7 +136,7 @@ namespace Yavsc.Blogs.Controllers } await blogSpotService.Delete(User, id); - return Ok(blog); + return Ok(blog.GetPayload()); } /// diff --git a/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs b/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs index ad6a0893..533e5594 100644 --- a/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs +++ b/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs @@ -6,7 +6,7 @@ using static Yavsc.Constants; namespace Yavsc.Blogs.Controllers { [Produces("application/json")] - [Route(APIPrefix + "/blogtags")] + [Route(APIPrefix + "/" + BlogTagPath )] public class BlogTagsApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Blogs/Controllers/CircleApiController.cs b/src/Yavsc.Blogs/Controllers/CircleApiController.cs index 524f0471..fafd00ac 100644 --- a/src/Yavsc.Blogs/Controllers/CircleApiController.cs +++ b/src/Yavsc.Blogs/Controllers/CircleApiController.cs @@ -8,7 +8,7 @@ using static Yavsc.Constants; namespace Yavsc.Blogs.Controllers { [Produces("application/json")] - [Route(APIPrefix +"/circle")] + [Route(APIPrefix +"/" + CirclePath)] public class CircleApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Blogs/Controllers/CommentsApiController.cs b/src/Yavsc.Blogs/Controllers/CommentsApiController.cs index d4c80f99..d0747c29 100644 --- a/src/Yavsc.Blogs/Controllers/CommentsApiController.cs +++ b/src/Yavsc.Blogs/Controllers/CommentsApiController.cs @@ -11,7 +11,7 @@ namespace Yavsc.Blogs.Controllers { [Authorize] [Produces("application/json")] - [Route(APIPrefix + "/blogcomments")] + [Route(APIPrefix + "/" + CommentsPath)] public class CommentsApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Server/Helpers/PayloadHelpers.cs b/src/Yavsc.Server/Helpers/PayloadHelpers.cs new file mode 100644 index 00000000..293cab02 --- /dev/null +++ b/src/Yavsc.Server/Helpers/PayloadHelpers.cs @@ -0,0 +1,21 @@ +using Yavsc.Models.Blog; + +public static class PayloadHelpers +{ + public static object GetPayload(this BlogPost post) + { + return new + { + post.Id, + post.Title, + post.Article, + post.DateCreated, + post.UserCreated, + post.DateModified, + post.UserModified, + post.AuthorId, + ACL = post.GetACL(), + Tags = post.GetTags() + }; + } +} \ No newline at end of file diff --git a/src/Yavsc.Server/Models/Blog/BlogPost.cs b/src/Yavsc.Server/Models/Blog/BlogPost.cs index 5a2dc58d..e5fd615d 100644 --- a/src/Yavsc.Server/Models/Blog/BlogPost.cs +++ b/src/Yavsc.Server/Models/Blog/BlogPost.cs @@ -85,7 +85,7 @@ namespace Yavsc.Models.Blog public string[] GetTags() { - return Tags.Select(t => t.Tag.Name).ToArray(); + return Tags?.Select(t => t.Tag.Name).ToArray() ?? Array.Empty(); } [InverseProperty("Post")] @@ -106,6 +106,7 @@ namespace Yavsc.Models.Blog [NotMapped] public bool IsPublished { get; set; } + [JsonIgnore] /// /// Explicit interface implementation of /// . The underlying diff --git a/src/Yavsc.Server/Models/Blog/BlogTag.cs b/src/Yavsc.Server/Models/Blog/BlogTag.cs index 69d428de..1f15a081 100644 --- a/src/Yavsc.Server/Models/Blog/BlogTag.cs +++ b/src/Yavsc.Server/Models/Blog/BlogTag.cs @@ -1,14 +1,17 @@ using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; using Yavsc.Models.Relationship; namespace Yavsc.Models.Blog { public partial class BlogTag { + [JsonIgnore] [ForeignKey("PostId")] public virtual BlogPost Post { get; set; } public long PostId { get; set; } + [JsonIgnore] [ForeignKey("TagId")] public virtual Tag Tag{ get; set; } public long TagId { get; set; } diff --git a/src/Yavsc.Server/Models/Blog/Comment.cs b/src/Yavsc.Server/Models/Blog/Comment.cs index acb5e003..4d39d0c2 100644 --- a/src/Yavsc.Server/Models/Blog/Comment.cs +++ b/src/Yavsc.Server/Models/Blog/Comment.cs @@ -13,15 +13,17 @@ namespace Yavsc.Models.Blog [YaStringLength(1024)] public string Article { get; set; } - - [ForeignKeyAttribute(nameof(ReceiverId))][JsonIgnore] + + [JsonIgnore] + [ForeignKeyAttribute(nameof(ReceiverId))] public virtual BlogPost Post { get; set; } [Required] public long ReceiverId { get; set; } public bool Visible { get; set; } - [ForeignKeyAttribute("AuthorId")][JsonIgnore] + [ForeignKeyAttribute("AuthorId")] + [JsonIgnore] public virtual ApplicationUser Author { get; set; } -- 2.47.3 From 05db88f345441c50883180d8121f38d4c4414eba Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 20:57:52 +0100 Subject: [PATCH 3/6] fixes the 404 on the Details button --- src/Yavsc.Org/Views/Blogspot/Index.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Yavsc.Org/Views/Blogspot/Index.cshtml b/src/Yavsc.Org/Views/Blogspot/Index.cshtml index 52cf3b88..1582041c 100644 --- a/src/Yavsc.Org/Views/Blogspot/Index.cshtml +++ b/src/Yavsc.Org/Views/Blogspot/Index.cshtml @@ -70,7 +70,7 @@
@if ((await AuthorizationService.AuthorizeAsync(User, post, new ReadPermission())).Succeeded) { - Details + Details } else { -- 2.47.3 From e56bdd4329a4aa8fe4d9460b136899c8c2f81509 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 20:58:10 +0100 Subject: [PATCH 4/6] dead code cleanup --- src/PostIt/PostIt/Services/UiDispatcher.cs | 72 ---------------------- 1 file changed, 72 deletions(-) delete mode 100644 src/PostIt/PostIt/Services/UiDispatcher.cs diff --git a/src/PostIt/PostIt/Services/UiDispatcher.cs b/src/PostIt/PostIt/Services/UiDispatcher.cs deleted file mode 100644 index e935ac1a..00000000 --- a/src/PostIt/PostIt/Services/UiDispatcher.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Threading.Tasks; -using Avalonia.Threading; - -namespace PostIt.Services; - -/// -/// Tiny marshalling helper around so -/// the rest of the codebase does not have to import Avalonia.Threading -/// directly. We want exactly one place that decides "is the current -/// thread the Avalonia UI thread, and if not, post there" so that -/// -derived types (Settings, the various -/// ViewModels) can fire PropertyChanged safely from background -/// work — which is exactly the cross-thread case that previously blew -/// up inside DataValidationErrors.SetErrors on Avalonia 11. -/// -/// The helper is intentionally tiny: a sync post when we are off the -/// UI thread, a no-op when we are already on it, and an async fire- -/// and-forget variant for places where awaiting would deadlock the -/// caller (e.g. Settings.Load continuation paths). -/// -public static class UiDispatcher -{ - /// - /// True when the calling thread is the Avalonia UI thread. Property - /// setters that touch bindings should check this before mutating - /// state; the safe path is . - /// - public static bool IsOnUiThread => Dispatcher.UIThread.CheckAccess(); - - /// - /// Run on the UI thread. If the caller is - /// already on the UI thread, run synchronously to preserve stack - /// traces and ordering; otherwise post to the dispatcher and wait. - /// Never throws on shutdown — a missing dispatcher is treated as - /// "best-effort skipped", matching Avalonia's own behaviour when - /// the application lifetime has been torn down. - /// - public static void InvokeIfNeeded(Action action) - { - if (action is null) return; - if (IsOnUiThread) { action(); return; } - try { Dispatcher.UIThread.Post(action, DispatcherPriority.Normal); } - catch (InvalidOperationException) { /* dispatcher gone, nothing to do */ } - } - - /// - /// Fire-and-forget variant: schedules on - /// the UI thread but does not block the caller. Use this from - /// background workers (OIDC discovery, HTTP callbacks, file I/O) - /// where awaiting the dispatcher would deadlock the calling sync - /// context. - /// - public static void Post(Action action) - { - if (action is null) return; - try { Dispatcher.UIThread.Post(action, DispatcherPriority.Normal); } - catch (InvalidOperationException) { /* dispatcher gone */ } - } - - /// - /// Awaitable variant. Useful inside async ViewModel methods - /// that must touch bindings only after the dispatcher has processed - /// a queued update (e.g. "load file then refresh observable state"). - /// - public static Task InvokeAsync(Action action) - { - if (action is null) return Task.CompletedTask; - if (IsOnUiThread) { action(); return Task.CompletedTask; } - return Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Normal).GetTask(); - } -} -- 2.47.3 From 1c2e1760b002f1607acda52081639c64f78de471 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 21:33:11 +0100 Subject: [PATCH 5/6] refacto + test fixes --- .vscode/settings.json | 3 +- src/Yavsc.Blogs.Tests/BlogAclApiTests.cs | 3 +- .../BlogApiMappedClaimsTests.cs | 14 +++++-- src/Yavsc.Blogs.Tests/BlogApiTests.cs | 41 ++++++++----------- .../BlogsWebServerFixture.cs | 3 +- src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs | 11 +++++ .../Fixtures/IBackendFixture.cs | 15 +++++++ src/Yavsc.Blogs/Constants.cs | 2 +- 8 files changed, 60 insertions(+), 32 deletions(-) create mode 100644 src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs create mode 100644 src/Yavsc.Blogs.Tests/Fixtures/IBackendFixture.cs diff --git a/.vscode/settings.json b/.vscode/settings.json index 915683fc..1cb334e0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,5 +42,6 @@ "copilotcli/gpt-5.3-codex" ] } - } + }, + "dotnet.defaultSolution": "yavsc.sln" } diff --git a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs index c140c290..4aef805f 100644 --- a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs @@ -131,7 +131,8 @@ public sealed class BlogAclApiTests : IClassFixture BlogPostId = _fixture.PostId }; - var response = await http.PostAsJsonAsync(BlogAclUrl(), payload, + var response = await http.PostAsJsonAsync( + BlogAclUrl(), payload, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, response.StatusCode); diff --git a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs index f4878860..7c5747d0 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs @@ -12,10 +12,16 @@ using Yavsc.Tests.Shared; namespace Yavsc.Blogs.Tests; [Collection("JwtClaimMapping")] -public sealed class BlogApiMappedClaimsTests : IClassFixture +public sealed class BlogApiMappedClaimsTests : +IClassFixture, +IBackendFixture { private readonly MappedClaimsBlogsWebServerFixture _fixture; + public IReadOnlyList Addresses => throw new NotImplementedException(); + + public IServiceProvider Services => throw new NotImplementedException(); + public BlogApiMappedClaimsTests(MappedClaimsBlogsWebServerFixture fixture) { _fixture = fixture; @@ -79,7 +85,7 @@ public sealed class BlogApiMappedClaimsTests : IClassFixture(TestContext.Current.CancellationToken); @@ -93,7 +99,7 @@ public sealed class BlogApiMappedClaimsTests : IClassFixture _fixture.SeedUser("tester"); } - /// The fixture's WebApplication is bound to - /// https://localhost:<random> via - /// . We pick the first - /// https URL and append the controller route - /// (/api/v1/blog, matching the production - /// [Route(APIPrefix + "/blog")]). - private string BlogsUrl => - _fixture.Addresses.First(a => a.StartsWith("https://")) + "/api/v1/blog"; - + /// Build an authenticated client: a real /// Authorization: Bearer <jwt> header where the JWT /// is signed by and carries @@ -101,7 +93,8 @@ public sealed class BlogApiTests : IClassFixture _fixture.ResetDatabase(); using var http = NewClient(); - var response = await http.GetAsync("/api/v1/blog", + var response = await http.GetAsync( + _fixture.BlogUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -134,7 +127,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -147,7 +140,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("/api/v1/blog", + var listResponse = await http.GetAsync(_fixture.BlogUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); @@ -175,7 +168,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -185,7 +178,7 @@ public sealed class BlogApiTests : IClassFixture Assert.NotNull(created); Assert.Equal("tester", created!.AuthorId); - var listResponse = await http.GetAsync("/api/v1/blog", + var listResponse = await http.GetAsync(_fixture.BlogUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); @@ -213,7 +206,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -262,7 +255,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("/api/v1/blog", + var response = await http.GetAsync(_fixture.BlogUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } @@ -290,7 +283,7 @@ public sealed class BlogApiTests : IClassFixture DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -309,13 +302,13 @@ public sealed class BlogApiTests : IClassFixture DateCreated = created.DateCreated, DateModified = DateTime.UtcNow }; - var putResponse = await http.PutAsJsonAsync($"/api/v1/blog/{created.Id}", + var putResponse = await http.PutAsJsonAsync(_fixture.BlogUrl()+$"/{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("/api/v1/blog", + var listResponse = await http.GetAsync(_fixture.BlogUrl(), TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); using var doc = JsonDocument.Parse( @@ -343,19 +336,19 @@ public sealed class BlogApiTests : IClassFixture DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, + var postResponse = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); var created = (await postResponse.Content.ReadFromJsonAsync( TestContext.Current.CancellationToken ))!; - var deleteResponse = await http.DeleteAsync($"/api/v1/blog/{created.Id}", + var deleteResponse = await http.DeleteAsync(_fixture.BlogUrl()+$"/{created.Id}", TestContext.Current.CancellationToken ); Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode); // The list should now be empty. - var listResponse = await http.GetAsync("/api/v1/blog", + var listResponse = await http.GetAsync(_fixture.BlogUrl(), TestContext.Current.CancellationToken); String response = await listResponse.Content.ReadAsStringAsync( TestContext.Current.CancellationToken @@ -398,7 +391,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync("/api/v1/blog", draft, + var response = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); // Dump the body on failure so the test name + the response @@ -443,7 +436,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync("/api/v1/blog", draft, + var response = await http.PostAsJsonAsync(_fixture.BlogUrl(), draft, TestContext.Current.CancellationToken); if (response.StatusCode != HttpStatusCode.BadRequest) diff --git a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs index 695af4c5..6e8ae31f 100644 --- a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs +++ b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs @@ -10,7 +10,7 @@ using Yavsc.Models.Blog; using Yavsc.Models.Relationship; using Yavsc.Services; using Yavsc.Tests.Shared; - +using static Yavsc.Constants; namespace Yavsc.Blogs.Tests; /// @@ -415,4 +415,5 @@ public sealed class BlogsWebServerFixture : WebHostFixture db.SaveChanges(); return post.Id; } + } diff --git a/src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs b/src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs new file mode 100644 index 00000000..06e9a5af --- /dev/null +++ b/src/Yavsc.Blogs.Tests/Fixtures/BlogHelpers.cs @@ -0,0 +1,11 @@ +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/Fixtures/IBackendFixture.cs b/src/Yavsc.Blogs.Tests/Fixtures/IBackendFixture.cs new file mode 100644 index 00000000..61202a0a --- /dev/null +++ b/src/Yavsc.Blogs.Tests/Fixtures/IBackendFixture.cs @@ -0,0 +1,15 @@ + +using Yavsc.Blogs.Tests; + +public interface IBackendFixture +{ + /// + /// The addresses the fixture bound to. + /// + IReadOnlyList Addresses { get; } + + /// + /// The service provider for the fixture host. + /// + IServiceProvider Services { get; } +} \ No newline at end of file diff --git a/src/Yavsc.Blogs/Constants.cs b/src/Yavsc.Blogs/Constants.cs index 3e499da4..9d400032 100644 --- a/src/Yavsc.Blogs/Constants.cs +++ b/src/Yavsc.Blogs/Constants.cs @@ -1,6 +1,6 @@ namespace Yavsc.Blogs; -public static class Constants +public static class BlogConstants { public const string AdminRole = "Admin"; public const string ModeratorRole = "Moderator"; -- 2.47.3 From e689dbfe354274d7ad0d219769e44c3be4590bdf Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 28 Aug 2026 22:41:05 +0100 Subject: [PATCH 6/6] 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 + + + + -- 2.47.3