postIt #2
7 changed files with 223 additions and 2 deletions
feat(tests): scaffold Yavsc.Blogs.Tests with BlogsWebServerFixture
Adds the test project that the next commit will use to assert the
blog API endpoints. The fixture inherits from the shared
WebHostFixture (commit "refactor: extract WebHostFixture…") and
wires up only the bits the blog API needs:
* In-memory ApplicationDbContext — BlogSpotService is used as-is,
no mock. The first tests will exercise the real service against
an empty table.
* Trivial IFileSystemAuthManager stub (the GET index path never
reads the file system).
* TestAuthPolicyProvider swapped in, so X-Test-Role satisfies
[Authorize("BlogScope")].
Two smoke tests verify the fixture boots and the controller
pipeline is reachable. The first behavioural test
(GET /api/v1/blog → 200) lands in the next commit.
Also promotes two xunit.v3.* package versions to the root
Directory.Packages.props so future test projects can share them.
commit
8c38bab45a
|
|
@ -21,6 +21,8 @@
|
||||||
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" />
|
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" />
|
||||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||||
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
||||||
|
<PackageVersion Include="xunit.v3.common" Version="3.2.2" />
|
||||||
|
<PackageVersion Include="xunit.v3.extensibility.core" Version="3.2.2" />
|
||||||
<PackageVersion Include="YamlDotNet" Version="18.1.0" />
|
<PackageVersion Include="YamlDotNet" Version="18.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
42
src/Yavsc.Blogs.Tests/BlogApiSmokeTests.cs
Normal file
42
src/Yavsc.Blogs.Tests/BlogApiSmokeTests.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Yavsc.Tests.Shared;
|
||||||
|
|
||||||
|
namespace Yavsc.Blogs.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Smoke tests for the Yavsc.Blogs API host. These tests only assert
|
||||||
|
/// that the fixture boots and the test HTTP client reaches the
|
||||||
|
/// controller pipeline — they do not yet exercise the controller
|
||||||
|
/// surface. The first behavioural test (GET /api/v1/blog returns
|
||||||
|
/// 200) lands in a follow-up commit.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class BlogApiSmokeTests : IClassFixture<BlogsWebServerFixture>
|
||||||
|
{
|
||||||
|
private readonly BlogsWebServerFixture _fixture;
|
||||||
|
|
||||||
|
public BlogApiSmokeTests(BlogsWebServerFixture fixture)
|
||||||
|
{
|
||||||
|
_fixture = fixture;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Fixture_Binds_At_Least_One_Https_Address()
|
||||||
|
{
|
||||||
|
Assert.NotEmpty(_fixture.Addresses);
|
||||||
|
Assert.Contains(_fixture.Addresses, a => a.StartsWith("https://"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Fixture_Exposes_Resolving_ServiceProvider()
|
||||||
|
{
|
||||||
|
// If the host built correctly, the service provider should
|
||||||
|
// be available and resolvable. We don't need to assert a
|
||||||
|
// specific service here — the GET 200 test will exercise
|
||||||
|
// the BlogSpotService indirectly.
|
||||||
|
Assert.NotNull(_fixture.Services);
|
||||||
|
using var scope = _fixture.Services.CreateScope();
|
||||||
|
Assert.NotNull(scope.ServiceProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
104
src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs
Normal file
104
src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Yavsc;
|
||||||
|
using Yavsc.Models;
|
||||||
|
using Yavsc.Server.Services;
|
||||||
|
using Yavsc.Services;
|
||||||
|
using Yavsc.Tests.Shared;
|
||||||
|
|
||||||
|
namespace Yavsc.Blogs.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test host for the Yavsc.Blogs API surface. Specialisation of
|
||||||
|
/// <see cref="WebHostFixture"/> that wires up only the bits the
|
||||||
|
/// blog API actually depends on:
|
||||||
|
///
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item><description>An in-memory <see cref="ApplicationDbContext"/>
|
||||||
|
/// (the real one — no mock) so <c>BlogSpotService.Index</c> can run
|
||||||
|
/// against an empty table and return an empty list.</description></item>
|
||||||
|
/// <item><description>A trivial <see cref="IFileSystemAuthManager"/>
|
||||||
|
/// stub: the GET index path doesn't read the file system, so any
|
||||||
|
/// implementation is fine.</description></item>
|
||||||
|
/// <item><description>The default <see cref="IAuthorizationService"/>
|
||||||
|
/// from <c>Microsoft.AspNetCore.Authorization</c>.</description></item>
|
||||||
|
/// <item><description>The test auth bypass from
|
||||||
|
/// <c>Yavsc.Tests.Shared</c> so the <c>[Authorize("BlogScope")]</c>
|
||||||
|
/// attribute on <c>BlogApiController</c> is satisfied when the
|
||||||
|
/// test sends the <c>X-Test-Role</c> header.</description></item>
|
||||||
|
/// </list>
|
||||||
|
///
|
||||||
|
/// No IdentityServer, no SMTP, no static assets — the Org fixture
|
||||||
|
/// owns all of that and we don't need any of it for blog integration
|
||||||
|
/// tests.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class BlogsWebServerFixture : WebHostFixture
|
||||||
|
{
|
||||||
|
protected override WebApplication BuildApp(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
// Use the real ApplicationDbContext with an in-memory store.
|
||||||
|
// BlogSpotService reads _context.BlogSpot directly, so any
|
||||||
|
// attempt to mock it would be wasted work; the real service
|
||||||
|
// against an empty table returns an empty list, which is
|
||||||
|
// exactly what the first test wants to assert.
|
||||||
|
builder.Services.AddDbContext<ApplicationDbContext>(opt =>
|
||||||
|
opt.UseInMemoryDatabase("Yavsc.Blogs.Tests"));
|
||||||
|
|
||||||
|
// Trivial file-system auth: the GET index path never calls
|
||||||
|
// into it, but the DI container needs an instance.
|
||||||
|
builder.Services.AddSingleton<IFileSystemAuthManager>(
|
||||||
|
new NoopFileSystemAuthManager());
|
||||||
|
|
||||||
|
// Real BlogSpotService — same instance the production host
|
||||||
|
// builds (ApplicationDbContext, IAuthorizationService,
|
||||||
|
// IFileSystemAuthManager).
|
||||||
|
builder.Services.AddScoped<BlogSpotService>();
|
||||||
|
|
||||||
|
// The BlogApiController is reached through MVC, so register
|
||||||
|
// MVC + the BlogScope authorization policy.
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
builder.Services.AddAuthorization(opt =>
|
||||||
|
{
|
||||||
|
// Mirror the production "BlogScope" policy: any
|
||||||
|
// authenticated user. The TestAuthPolicyProvider we
|
||||||
|
// register below short-circuits the role check via the
|
||||||
|
// X-Test-Role header.
|
||||||
|
opt.AddPolicy("BlogScope", p => p.RequireAssertion(_ => true));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test auth bypass — swapped in BEFORE the host builds the
|
||||||
|
// service collection, so it overrides any production
|
||||||
|
// policy provider registered by AddAuthorization above.
|
||||||
|
builder.Services.AddSingleton<IAuthorizationPolicyProvider, TestAuthPolicyProvider>();
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<WebApplication> ConfigurePipelineAsync(WebApplication app)
|
||||||
|
{
|
||||||
|
app.UseRouting();
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
app.MapControllers();
|
||||||
|
await Task.CompletedTask;
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Trivial <see cref="IFileSystemAuthManager"/> stub. The
|
||||||
|
/// blog API endpoints exercised by the first tests don't read the
|
||||||
|
/// file system, so the implementation can be a no-op.</summary>
|
||||||
|
private sealed class NoopFileSystemAuthManager : IFileSystemAuthManager
|
||||||
|
{
|
||||||
|
public FileAccessRight GetFilePathAccess(ClaimsPrincipal user, string fileRelativePath)
|
||||||
|
=> FileAccessRight.None;
|
||||||
|
|
||||||
|
public void SetAccess(long circleId, string normalizedFullPath, FileAccessRight access)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/Yavsc.Blogs.Tests/Directory.Packages.props
Normal file
7
src/Yavsc.Blogs.Tests/Directory.Packages.props
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<Project>
|
||||||
|
<!--
|
||||||
|
Yavsc.Blogs.Tests has no project-specific package versions. All
|
||||||
|
package versions are declared at the repository root.
|
||||||
|
-->
|
||||||
|
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Packages.props', '$(MSBuildThisFileDirectory)../'))" />
|
||||||
|
</Project>
|
||||||
37
src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj
Normal file
37
src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<RootNamespace>Yavsc.Blogs.Tests</RootNamespace>
|
||||||
|
<UserSecretsId>b1a9d0d6-3f5e-4a07-9f0a-7e4d5b6c1a82</UserSecretsId>
|
||||||
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
|
<AssemblyVersion>1.0.1.0</AssemblyVersion>
|
||||||
|
<FileVersion>1.0.1.0</FileVersion>
|
||||||
|
<InformationalVersion>1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f</InformationalVersion>
|
||||||
|
<Version>1.0.1-5</Version>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
|
||||||
|
<PackageReference Include="xunit.v3" />
|
||||||
|
<PackageReference Include="xunit.v3.common" />
|
||||||
|
<PackageReference Include="xunit.v3.extensibility.core" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Yavsc.Abstract\Yavsc.Abstract.csproj" />
|
||||||
|
<ProjectReference Include="..\Yavsc.Server\Yavsc.Server.csproj" />
|
||||||
|
<ProjectReference Include="..\Yavsc.Blogs\Yavsc.Blogs.csproj" />
|
||||||
|
<ProjectReference Include="..\Yavsc.Tests.Shared\Yavsc.Tests.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="GitVersion.MsBuild" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
|
|
@ -13,9 +13,8 @@
|
||||||
inherit from the shared base classes.
|
inherit from the shared base classes.
|
||||||
-->
|
-->
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" />
|
<PackageReference Include="Microsoft.AspNetCore.Hosting" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
30
yavsc.sln
30
yavsc.sln
|
|
@ -33,6 +33,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostIt.Desktop", "src\PostI
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostIt.Tests", "src\PostIt.Tests\PostIt.Tests.csproj", "{4D283324-6DD3-4CD1-9893-8C317772C6B5}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostIt.Tests", "src\PostIt.Tests\PostIt.Tests.csproj", "{4D283324-6DD3-4CD1-9893-8C317772C6B5}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Blogs.Tests", "src\Yavsc.Blogs.Tests\Yavsc.Blogs.Tests.csproj", "{0E471075-DABF-40E9-98B7-1630BEF19145}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Tests.Shared", "src\Yavsc.Tests.Shared\Yavsc.Tests.Shared.csproj", "{34D1F73D-BF74-47CC-9358-9F4F221C75D7}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -187,6 +191,30 @@ Global
|
||||||
{4D283324-6DD3-4CD1-9893-8C317772C6B5}.Release|x64.Build.0 = Release|Any CPU
|
{4D283324-6DD3-4CD1-9893-8C317772C6B5}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{4D283324-6DD3-4CD1-9893-8C317772C6B5}.Release|x86.ActiveCfg = Release|Any CPU
|
{4D283324-6DD3-4CD1-9893-8C317772C6B5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{4D283324-6DD3-4CD1-9893-8C317772C6B5}.Release|x86.Build.0 = Release|Any CPU
|
{4D283324-6DD3-4CD1-9893-8C317772C6B5}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{0E471075-DABF-40E9-98B7-1630BEF19145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{0E471075-DABF-40E9-98B7-1630BEF19145}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{0E471075-DABF-40E9-98B7-1630BEF19145}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{0E471075-DABF-40E9-98B7-1630BEF19145}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{0E471075-DABF-40E9-98B7-1630BEF19145}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{0E471075-DABF-40E9-98B7-1630BEF19145}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{0E471075-DABF-40E9-98B7-1630BEF19145}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{0E471075-DABF-40E9-98B7-1630BEF19145}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{0E471075-DABF-40E9-98B7-1630BEF19145}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{0E471075-DABF-40E9-98B7-1630BEF19145}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{0E471075-DABF-40E9-98B7-1630BEF19145}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{0E471075-DABF-40E9-98B7-1630BEF19145}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -205,5 +233,7 @@ Global
|
||||||
{AF96C1C4-D128-4CD7-A8BB-D194E6D270F0} = {E13D107F-4053-D0DE-6394-453609595BFE}
|
{AF96C1C4-D128-4CD7-A8BB-D194E6D270F0} = {E13D107F-4053-D0DE-6394-453609595BFE}
|
||||||
{EFE24256-9335-44C5-8B77-E180C2DB3C0B} = {E13D107F-4053-D0DE-6394-453609595BFE}
|
{EFE24256-9335-44C5-8B77-E180C2DB3C0B} = {E13D107F-4053-D0DE-6394-453609595BFE}
|
||||||
{4D283324-6DD3-4CD1-9893-8C317772C6B5} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2}
|
{4D283324-6DD3-4CD1-9893-8C317772C6B5} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2}
|
||||||
|
{0E471075-DABF-40E9-98B7-1630BEF19145} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2}
|
||||||
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue