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