diff --git a/src/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs
new file mode 100644
index 00000000..fcb5099a
--- /dev/null
+++ b/src/Yavsc.Blogs.Tests/BlogApiTests.cs
@@ -0,0 +1,65 @@
+using System.Net;
+using System.Text.Json;
+
+namespace Yavsc.Blogs.Tests;
+
+///
+/// Behavioural tests for BlogApiController. Built on the
+/// scaffold: in-memory
+/// ApplicationDbContext, real BlogSpotService,
+/// X-Test-Role for the [Authorize("BlogScope")]
+/// attribute.
+///
+public sealed class BlogApiTests : IClassFixture
+{
+ private readonly BlogsWebServerFixture _fixture;
+
+ public BlogApiTests(BlogsWebServerFixture fixture)
+ {
+ _fixture = fixture;
+ }
+
+ /// 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";
+
+ private HttpClient NewClient()
+ {
+ // The fixture's self-signed certificate is not in the user's
+ // trust store, so we accept anything (same pattern as
+ // Yavsc.Org.Tests' BypassSslValidationHandler).
+ var handler = new HttpClientHandler
+ {
+ ServerCertificateCustomValidationCallback = (_, _, _, _) => true
+ };
+ var http = new HttpClient(handler)
+ {
+ BaseAddress = new Uri(_fixture.Addresses.First(a => a.StartsWith("https://")))
+ };
+ http.DefaultRequestHeaders.Add(TestAuthPolicyProvider.HeaderName, TestAuthPolicyProvider.AdminRole);
+ return http;
+ }
+
+ [Fact]
+ public async Task GetBlogs_returns_200_with_empty_list_when_no_posts()
+ {
+ using var http = NewClient();
+
+ var response = await http.GetAsync("/api/v1/blog");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var body = await response.Content.ReadAsStringAsync();
+ // Empty table → empty JSON array. We compare as a JsonDocument
+ // so a future change in formatting (whitespace, indentation)
+ // doesn't break the assertion.
+ using var doc = JsonDocument.Parse(body);
+ Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind);
+ Assert.Equal(0, doc.RootElement.GetArrayLength());
+ }
+}
diff --git a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs
index 608c4fdc..e40d0740 100644
--- a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs
+++ b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs
@@ -1,13 +1,10 @@
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.Blogs.Controllers;
using Yavsc.Models;
-using Yavsc.Server.Services;
using Yavsc.Services;
using Yavsc.Tests.Shared;
@@ -59,9 +56,12 @@ public sealed class BlogsWebServerFixture : WebHostFixture
// IFileSystemAuthManager).
builder.Services.AddScoped();
- // The BlogApiController is reached through MVC, so register
- // MVC + the BlogScope authorization policy.
- builder.Services.AddControllers();
+ // The BlogApiController is reached through MVC. AddControllers()
+ // by default scans the test assembly only; we explicitly add the
+ // Yavsc.Blogs application part so the controller is discovered
+ // and routed.
+ builder.Services.AddControllers()
+ .AddApplicationPart(typeof(BlogApiController).Assembly);
builder.Services.AddAuthorization(opt =>
{
// Mirror the production "BlogScope" policy: any