From 9f5a1505e370e79d5c7b882c3fbb54bb3ee2aaf9 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 6 Jul 2026 21:58:14 +0100 Subject: [PATCH] test(blogs): GET /api/v1/blog returns 200 with empty list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First behavioural test for the Yavsc.Blogs API surface. Sends a GET on the blog index with the X-Test-Role auth bypass and asserts the response is 200 with an empty JSON array — the in-memory ApplicationDbContext has no rows, and BlogSpotService.Index returns an empty enumeration. While here, fix a routing miss: AddControllers() in the test fixture was only scanning the test assembly, so BlogApiController was never registered. Add the Yavsc.Blogs application part explicitly. Without this, every request to /api/v1/blog came back as 404 — the same symptom PostIt was seeing in production. The POST flow lands in the next commit, once BlogApiController is made to accept JSON (it currently requires multipart/form-data because of Request.Form.Files). --- src/Yavsc.Blogs.Tests/BlogApiTests.cs | 65 +++++++++++++++++++ .../BlogsWebServerFixture.cs | 14 ++-- 2 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 src/Yavsc.Blogs.Tests/BlogApiTests.cs 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