using System; using System.Collections.Generic; using System.Text; using MediaBrowser.Model.Dlna; using Xunit; namespace Jellyfin.Model.Tests.Dlna; public class StreamInfoTests { private const string BaseUrl = "/test/"; private const int RandomSeed = 298347823; /// /// Returns a random float. /// /// The instance. /// A random . private static float RandomFloat(Random random) { var buffer = new byte[4]; random.NextBytes(buffer); return BitConverter.ToSingle(buffer, 0); } /// /// Creates a random array. /// /// The instance. /// The element of the array. /// An of . private static object? RandomArray(Random random, Type? elementType) { if (elementType is null) { return null; } if (elementType == typeof(string)) { return RandomStringArray(random); } if (elementType == typeof(int)) { return RandomIntArray(random); } if (elementType.IsEnum) { var values = Enum.GetValues(elementType); return RandomIntArray(random, 0, values.Length - 1); } throw new ArgumentException("Unsupported array type " + elementType.ToString()); } /// /// Creates a random length string. /// /// The instance. /// The minimum length of the string. /// The maximum length of the string. /// The string. private static string RandomString(Random random, int minLength = 0, int maxLength = 256) { var len = random.Next(minLength, maxLength); var sb = new StringBuilder(len); while (len > 0) { sb.Append((char)random.Next(65, 97)); len--; } return sb.ToString(); } /// /// Creates a random long. /// /// The instance. /// Min value. /// Max value. /// A random between and . private static long RandomLong(Random random, long min = -9223372036854775808, long max = 9223372036854775807) { long result = random.Next((int)(min >> 32), (int)(max >> 32)); result <<= 32; result |= (long)random.Next((int)(min >> 32) << 32, (int)(max >> 32) << 32); return result; } /// /// Creates a random string array containing between and . /// /// The instance. /// The minimum number of elements. /// The maximum number of elements. /// A random instance. private static string[] RandomStringArray(Random random, int minLength = 0, int maxLength = 9) { var len = random.Next(minLength, maxLength); var arr = new List(len); while (len > 0) { arr.Add(RandomString(random, 1, 30)); len--; } return arr.ToArray(); } /// /// Creates a random int array containing between and . /// /// The instance. /// The minimum number of elements. /// The maximum number of elements. /// A random instance. private static int[] RandomIntArray(Random random, int minLength = 0, int maxLength = 9) { var len = random.Next(minLength, maxLength); var arr = new List(len); while (len > 0) { arr.Add(random.Next()); len--; } return arr.ToArray(); } /// /// Fills most properties with random data. /// /// The instance to fill with data. private static void FillAllProperties(T destination) { var random = new Random(RandomSeed); var objectType = destination!.GetType(); foreach (var property in objectType.GetProperties()) { if (!(property.CanRead && property.CanWrite)) { continue; } var type = property.PropertyType; // If nullable, then set it to null, 25% of the time. if (Nullable.GetUnderlyingType(type) is not null) { if (random.Next(0, 4) == 0) { // Set it to null. property.SetValue(destination, null); continue; } } if (type == typeof(Guid)) { property.SetValue(destination, Guid.NewGuid()); continue; } if (type.IsEnum) { Array values = Enum.GetValues(property.PropertyType); property.SetValue(destination, values.GetValue(random.Next(0, values.Length - 1))); continue; } if (type == typeof(long)) { property.SetValue(destination, RandomLong(random)); continue; } if (type == typeof(string)) { property.SetValue(destination, RandomString(random)); continue; } if (type == typeof(bool)) { property.SetValue(destination, random.Next(0, 1) == 1); continue; } if (type == typeof(float)) { property.SetValue(destination, RandomFloat(random)); continue; } if (type.IsArray) { property.SetValue(destination, RandomArray(random, type.GetElementType())); continue; } } } [InlineData(DlnaProfileType.Audio)] [InlineData(DlnaProfileType.Video)] [InlineData(DlnaProfileType.Photo)] [Theory] public void Test_Blank_Url_Method(DlnaProfileType type) { var streamInfo = new LegacyStreamInfo(Guid.Empty, type) { DeviceProfile = new DeviceProfile() }; string legacyUrl = streamInfo.ToUrl_Original(BaseUrl, "123"); // New version will return and & after the ? due to optional parameters. string newUrl = streamInfo.ToUrl(BaseUrl, "123", null).Replace("?&", "?", StringComparison.OrdinalIgnoreCase); Assert.Equal(legacyUrl, newUrl, ignoreCase: true); } [Fact] public void Fuzzy_Comparison() { var streamInfo = new LegacyStreamInfo(Guid.Empty, DlnaProfileType.Video) { DeviceProfile = new DeviceProfile() }; for (int i = 0; i < 100000; i++) { FillAllProperties(streamInfo); string legacyUrl = streamInfo.ToUrl_Original(BaseUrl, "123"); // New version will return and & after the ? due to optional parameters. string newUrl = streamInfo.ToUrl(BaseUrl, "123", null).Replace("?&", "?", StringComparison.OrdinalIgnoreCase); Assert.Equal(legacyUrl, newUrl, ignoreCase: true); } } }