fixes the tests
All checks were successful
Dotnet build and test / build (pull_request) Successful in 7m35s

This commit is contained in:
Paul Schneider 2026-08-28 22:41:05 +01:00
commit e689dbfe35
Signed by: notazof
GPG key ID: 1DD5D838E5343B06
11 changed files with 79 additions and 55 deletions

View file

@ -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<MappedClaimsBlogsWebServerFixture>,
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<BlogPost>(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<BlogPost>(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",

View file

@ -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<BlogsWebServerFixture>
_fixture.SeedUser("tester");
}
/// <summary>Build an authenticated client: a real
/// <c>Authorization: Bearer &lt;jwt&gt;</c> header where the JWT
/// is signed by <see cref="TestTokenIssuer"/> and carries
@ -94,7 +95,7 @@ public sealed class BlogApiTests : IClassFixture<BlogsWebServerFixture>
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<BlogsWebServerFixture>
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<BlogsWebServerFixture>
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<BlogsWebServerFixture>
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<BlogsWebServerFixture>
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<BlogsWebServerFixture>
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<BlogsWebServerFixture>
// 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<BlogsWebServerFixture>
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<BlogsWebServerFixture>
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<BlogsWebServerFixture>
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<BlogPost>(
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<BlogsWebServerFixture>
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<BlogsWebServerFixture>
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)

View file

@ -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}";
}

View file

@ -21,7 +21,7 @@ namespace Yavsc.Blogs.Tests;
/// This is the closest in-process reproduction of the production
/// authentication surface for the blog API.
/// </summary>
public sealed class MappedClaimsBlogsWebServerFixture : IDisposable
public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFixture
{
private readonly InMemoryDatabaseRoot _inMemoryRoot = new();
private readonly Dictionary<string, string> _savedInboundMap;

View file

@ -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<BlogsWebServerFixture>
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<BlogsWebServerFixture>
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<BlogsWebServerFixture>
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<BlogsWebServerFixture>
{
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<BlogsWebServerFixture>
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".

View file

@ -15,7 +15,8 @@ public static class PayloadHelpers
post.UserModified,
post.AuthorId,
ACL = post.GetACL(),
Tags = post.GetTags()
Tags = post.GetTags(),
post.IsPublished
};
}
}
}

View file

@ -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";
}

View file

@ -1,7 +1,5 @@
using Yavsc.Blogs.Tests;
public interface IBackendFixture
public interface IBackendFixture
{
/// <summary>
/// The addresses the fixture bound to.
@ -12,4 +10,4 @@ public interface IBackendFixture
/// The service provider for the fixture host.
/// </summary>
IServiceProvider Services { get; }
}
}

View file

@ -30,7 +30,7 @@ namespace Yavsc.Tests.Shared;
/// mapping are the responsibility of the subclass, through
/// <see cref="BuildApp"/>.
/// </summary>
public abstract class WebHostFixture : IDisposable
public abstract class WebHostFixture : IDisposable, IBackendFixture
{
private static readonly Lazy<X509Certificate2> _selfSignedCertificate =
new Lazy<X509Certificate2>(CreateSelfSignedCertificate);

View file

@ -23,4 +23,7 @@
<PackageReference Include="Microsoft.IdentityModel.Tokens" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
</ItemGroup>
</Project>
<ItemGroup>
<ProjectReference Include="../Yavsc.Abstract/Yavsc.Abstract.csproj" />
</ItemGroup>
</Project>