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>
71 lines
3.3 KiB
C#
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();
|
|
}
|
|
}
|