using CentronNexus.Configuration; namespace CentronNexusTests.Configuration; public class UploadConfigTests { [Fact] public void Defaults_AreApplied_WhenNothingIsConfigured() { var config = new UploadConfig(); Assert.Equal(UploadConfig.DefaultMaxEmployeeUploadSizeInMb, config.MaxEmployeeUploadSizeInMb); Assert.Equal(UploadConfig.DefaultMaxCustomerPortalUploadSizeInMb, config.MaxCustomerPortalUploadSizeInMb); Assert.True(config.HasEmployeeUploadLimit); Assert.True(config.HasCustomerPortalUploadLimit); } [Fact] public void MegabytesAreConvertedToBytes() { var config = new UploadConfig { MaxEmployeeUploadSizeInMb = 100, MaxCustomerPortalUploadSizeInMb = 25 }; Assert.Equal(100 * 1024 * 1024, config.MaxEmployeeUploadSizeInBytes); Assert.Equal(25 * 1024 * 1024, config.MaxCustomerPortalUploadSizeInBytes); } [Theory] [InlineData(0)] [InlineData(-1)] public void ZeroOrLess_MeansNoLimit(int sizeInMb) { var config = new UploadConfig { MaxEmployeeUploadSizeInMb = sizeInMb, MaxCustomerPortalUploadSizeInMb = sizeInMb }; Assert.False(config.HasEmployeeUploadLimit); Assert.False(config.HasCustomerPortalUploadLimit); Assert.Equal(int.MaxValue, config.MaxEmployeeUploadSizeInBytes); Assert.Equal(int.MaxValue, config.MaxCustomerPortalUploadSizeInBytes); } [Fact] public void OversizedValues_AreClamped_InsteadOfOverflowing() { // 4096 MB would exceed int.MaxValue bytes, which is the upper bound of the // DevExpress MaxFileSize parameter. var config = new UploadConfig { MaxEmployeeUploadSizeInMb = 4096, MaxCustomerPortalUploadSizeInMb = int.MaxValue }; Assert.Equal(int.MaxValue, config.MaxEmployeeUploadSizeInBytes); Assert.Equal(int.MaxValue, config.MaxCustomerPortalUploadSizeInBytes); Assert.True(config.HasEmployeeUploadLimit); Assert.True(config.HasCustomerPortalUploadLimit); } [Fact] public void SizeExceededMessage_NamesTheConfiguredLimit() { Assert.Equal( "Die Datei ist größer als 25 MB und kann nicht hochgeladen werden.", UploadConfig.BuildSizeExceededMessage(25)); } [Fact] public void MaxRequestBodySize_AddsOverhead_ToTheLargerConfiguredLimit() { var config = new UploadConfig(); // 100 MB employee limit + 4 MB multipart headroom. Assert.Equal(104L * 1024 * 1024, config.MaxRequestBodySizeInBytes); } [Fact] public void MaxRequestBodySize_UsesTheCustomerPortalLimit_WhenItIsTheLarger() { var config = new UploadConfig { MaxEmployeeUploadSizeInMb = 10, MaxCustomerPortalUploadSizeInMb = 50 }; Assert.Equal(54L * 1024 * 1024, config.MaxRequestBodySizeInBytes); } [Theory] [InlineData(0, 25)] [InlineData(-1, 25)] [InlineData(100, 0)] [InlineData(100, -5)] public void MaxRequestBodySize_FallsBackToTheCeiling_WhenUploadsAreUnlimited( int employeeSizeInMb, int customerPortalSizeInMb) { var config = new UploadConfig { MaxEmployeeUploadSizeInMb = employeeSizeInMb, MaxCustomerPortalUploadSizeInMb = customerPortalSizeInMb }; Assert.Equal(256L * 1024 * 1024, config.MaxRequestBodySizeInBytes); } [Theory] [InlineData(4096)] [InlineData(int.MaxValue)] public void MaxRequestBodySize_IsClampedToTheCeiling_InsteadOfOverflowing(int sizeInMb) { var config = new UploadConfig { MaxEmployeeUploadSizeInMb = sizeInMb, MaxCustomerPortalUploadSizeInMb = sizeInMb }; Assert.Equal(256L * 1024 * 1024, config.MaxRequestBodySizeInBytes); Assert.True(config.MaxRequestBodySizeInBytes > 0); } [Theory] [InlineData(1, 1)] [InlineData(25, 100)] [InlineData(100, 25)] [InlineData(200, 50)] public void MaxRequestBodySize_IsNeverSmallerThanTheConfiguredUploadLimits( int employeeSizeInMb, int customerPortalSizeInMb) { var config = new UploadConfig { MaxEmployeeUploadSizeInMb = employeeSizeInMb, MaxCustomerPortalUploadSizeInMb = customerPortalSizeInMb }; // Core invariant: the request body limit must never reject an upload the UI permits. // Only holds below the ceiling, where the clamp intentionally breaks it. Assert.True(config.MaxRequestBodySizeInBytes >= config.MaxEmployeeUploadSizeInBytes); Assert.True(config.MaxRequestBodySizeInBytes >= config.MaxCustomerPortalUploadSizeInBytes); } [Fact] public void MaxMultipartUploadSize_FollowsTheCustomerPortalLimit_WhenItFitsTheRequestBody() { var config = new UploadConfig(); Assert.Equal(25 * 1024 * 1024, config.MaxMultipartUploadSizeInBytes); } [Theory] [InlineData(0)] [InlineData(4096)] public void MaxMultipartUploadSize_NeverExceedsTheRequestBodyLimit(int customerPortalSizeInMb) { var config = new UploadConfig { MaxCustomerPortalUploadSizeInMb = customerPortalSizeInMb }; // 256 MB ceiling minus the 4 MB multipart headroom. Assert.Equal(252 * 1024 * 1024, config.MaxMultipartUploadSizeInBytes); Assert.True(config.MaxMultipartUploadSizeInBytes < config.MaxRequestBodySizeInBytes); } [Theory] [InlineData(1, 1)] [InlineData(25, 100)] [InlineData(100, 25)] [InlineData(0, 0)] [InlineData(4096, 4096)] public void MaxMultipartUploadSize_AlwaysFitsInsideTheRequestBodyLimit( int employeeSizeInMb, int customerPortalSizeInMb) { var config = new UploadConfig { MaxEmployeeUploadSizeInMb = employeeSizeInMb, MaxCustomerPortalUploadSizeInMb = customerPortalSizeInMb }; // Core invariant: a multipart upload the UI accepts must never be rejected by the server. Assert.True(config.MaxMultipartUploadSizeInBytes > 0); Assert.True(config.MaxMultipartUploadSizeInBytes <= config.MaxRequestBodySizeInBytes); } [Fact] public void MaxRequestBodySize_StaysWellAboveTheSignalRMessageSize_ForTheSmallestLimit() { var config = new UploadConfig { MaxEmployeeUploadSizeInMb = 1, MaxCustomerPortalUploadSizeInMb = 1 }; // Even the smallest sane configuration leaves the Blazor long polling send channel // (SignalR messages are 32 KB) and the OIDC form_post body orders of magnitude of headroom. Assert.Equal(5L * 1024 * 1024, config.MaxRequestBodySizeInBytes); } }