Files
ersatztv/ErsatzTV.Tests/Support/GeneratedOpenApiDocument.cs
T
timothyandClaude Opus 4.8 171364d0e8 feat(api): #287 OpenAPI contract honesty by construction
ApiKey security scheme + per-op security/401 via shared EndpointRequiresKey
predicate (no drift from enforcement); synthesized stable operationIds;
400 ValidationProblemDetails on binding ops; DayOfWeek as string enum.

Refs #287 #197

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 02:08:12 +02:00

71 lines
3.3 KiB
C#

using ErsatzTV;
using ErsatzTV.Controllers.Api;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi;
using NSubstitute;
using NUnit.Framework;
namespace ErsatzTV.Tests.Support;
/// <summary>
/// Builds the "v1" OpenAPI document in-process from the real <see cref="Startup.ConfigureServices" />
/// registrations, so contract tests validate the document the running app would actually serve —
/// including every registered document/operation/schema transformer — rather than a stale committed
/// <c>wwwroot/openapi/v1.json</c> that is regenerated out-of-band. Mirrors the DI-only bootstrap that
/// <c>ApiControllerSecurityTests</c> uses to inspect <c>MvcOptions</c>.
/// </summary>
public static class GeneratedOpenApiDocument
{
public static async Task<OpenApiDocument> BuildV1Async()
{
var settings = new Dictionary<string, string?>
{
["provider"] = "sqlite",
["ConnectionStrings:Data"] = "Data Source=:memory:"
};
IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(settings).Build();
var environment = Substitute.For<IWebHostEnvironment>();
environment.ApplicationName.Returns("ErsatzTV");
environment.EnvironmentName.Returns("Development");
environment.ContentRootPath.Returns(TestContext.CurrentContext.TestDirectory);
environment.WebRootPath.Returns(TestContext.CurrentContext.TestDirectory);
environment.ContentRootFileProvider.Returns(new NullFileProvider());
environment.WebRootFileProvider.Returns(new NullFileProvider());
var services = new ServiceCollection();
// The host normally provides these; supply them so the OpenAPI document service and the
// API-key provider (an operation-transformer dependency) can be constructed.
services.AddSingleton(configuration);
services.AddLogging();
services.AddSingleton(environment);
services.AddSingleton<IHostEnvironment>(environment);
new Startup(configuration, environment).ConfigureServices(services);
// In the running app ErsatzTV is the entry assembly and its controllers are discovered
// automatically; under the test runner it is not, so register it as an application part.
ApplicationPartManager? partManager = services
.FirstOrDefault(d => d.ServiceType == typeof(ApplicationPartManager))?.ImplementationInstance
as ApplicationPartManager;
if (partManager is not null &&
!partManager.ApplicationParts.OfType<AssemblyPart>()
.Any(p => p.Assembly == typeof(CollectionController).Assembly))
{
partManager.ApplicationParts.Add(new AssemblyPart(typeof(CollectionController).Assembly));
}
await using ServiceProvider provider = services.BuildServiceProvider();
IOpenApiDocumentProvider documentProvider = provider.GetRequiredKeyedService<IOpenApiDocumentProvider>("v1");
return await documentProvider.GetOpenApiDocumentAsync();
}
}