using System.Net; using System.Security.Cryptography; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.Features; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using RadicadeServer.Admin; using RadicadeServer.DB; using Xunit; namespace RadicadeServer.Tests; [Collection(DbFactoryCollection.Name)] public class AdminBundleUploadTests { [Theory] [InlineData("/admin/panel/upload/datablob")] [InlineData("/admin/rooms/upload/datablob")] public async Task LargeBundleUploadsPreserveBytesAndStillRequireTheKey(string route) { var root = Directory.CreateTempSubdirectory("radicade-upload-test-").FullName; var oldStorage = DBConfig.CDNStorage; var oldKey = DBConfig.AdminApiKey; DBConfig.CDNStorage = root; DBConfig.AdminApiKey = "bundle-upload-test-key"; var builder = WebApplication.CreateBuilder(); builder.WebHost.UseKestrel().UseUrls("http://127.0.0.1:0"); builder.Logging.ClearProviders(); builder.Services.AddControllers().AddApplicationPart(typeof(AdminPanelAPI).Assembly); builder.Services.AddCors(options => options.AddPolicy("AdminPanel", policy => policy.AllowAnyOrigin())); builder.Services.Configure(options => options.MultipartBodyLengthLimit = 256L * 1024 * 1024); builder.Services.AddDbContext(options => options.UseInMemoryDatabase(Guid.NewGuid().ToString())); await using var app = builder.Build(); app.UseCors(); app.MapControllers(); try { await app.StartAsync(); using var client = new HttpClient { BaseAddress = new Uri(app.Urls.Single()) }; // Exercise real Kestrel and MVC model binding, beyond the default 30 MB ceiling. var bytes = new byte[39_000_000]; RandomNumberGenerator.Fill(bytes); using var form = new MultipartFormDataContent(); form.Add(new ByteArrayContent(bytes), "file", "bundle.assetbundle"); using var response = await client.PostAsync(route + "?key=bundle-upload-test-key&name=bundle.assetbundle", form); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(await File.ReadAllBytesAsync(Path.Join(root, "bundle.assetbundle")))); using var unauthorizedForm = new MultipartFormDataContent(); unauthorizedForm.Add(new ByteArrayContent(new byte[] { 1, 2, 3 }), "file", "unauthorized.assetbundle"); using var unauthorized = await client.PostAsync(route + "?key=wrong&name=unauthorized.assetbundle", unauthorizedForm); Assert.Equal(HttpStatusCode.Unauthorized, unauthorized.StatusCode); Assert.False(File.Exists(Path.Join(root, "unauthorized.assetbundle"))); using var malformed = new ByteArrayContent(new byte[] { 1, 2, 3 }); malformed.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("multipart/form-data"); using var badRequest = await client.PostAsync(route + "?key=bundle-upload-test-key&name=broken.assetbundle", malformed); Assert.Equal(HttpStatusCode.BadRequest, badRequest.StatusCode); Assert.DoesNotContain("Invalid secret", await badRequest.Content.ReadAsStringAsync()); Assert.False(File.Exists(Path.Join(root, "broken.assetbundle"))); } finally { await app.StopAsync(); DBConfig.CDNStorage = oldStorage; DBConfig.AdminApiKey = oldKey; Directory.Delete(root, recursive: true); } } }