diff --git a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs index 646f44ca..91e0064f 100644 --- a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs +++ b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs @@ -389,11 +389,23 @@ namespace Kyoo.Abstractions.Controllers /// Edit a resource /// /// The resource to edit, it's ID can't change. - /// Should old properties of the resource be discarded or should null values considered as not changed? /// The type of resources /// If the item is not found /// The resource edited and completed by database's information (related items and so on) - Task Edit(T item, bool resetOld) + Task Edit(T item) + where T : class, IResource; + + /// + /// Edit only specific properties of a resource + /// + /// The id of the resource to edit + /// + /// A method that will be called when you need to update every properties that you want to + /// persist. It can return false to abort the process via an ArgumentException + /// + /// If the item is not found + /// The resource edited and completed by database's information (related items and so on) + Task Patch(int id, Func> patch) where T : class, IResource; /// diff --git a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs index ead00dbf..fb00b768 100644 --- a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs +++ b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs @@ -129,13 +129,24 @@ namespace Kyoo.Abstractions.Controllers event ResourceEventHandler OnCreated; /// - /// Edit a resource + /// Edit a resource and replace every property /// /// The resource to edit, it's ID can't change. - /// Should old properties of the resource be discarded or should null values considered as not changed? /// If the item is not found /// The resource edited and completed by database's information (related items and so on) - Task Edit(T edited, bool resetOld); + Task Edit(T edited); + + /// + /// Edit only specific properties of a resource + /// + /// The id of the resource to edit + /// + /// A method that will be called when you need to update every properties that you want to + /// persist. It can return false to abort the process via an ArgumentException + /// + /// If the item is not found + /// The resource edited and completed by database's information (related items and so on) + Task Patch(int id, Func> patch); /// /// Called when a resource has been edited. diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/GenreTests.cs b/back/src/Kyoo.Abstractions/Models/PartialResource.cs similarity index 51% rename from back/tests/Kyoo.Tests/Database/SpecificTests/GenreTests.cs rename to back/src/Kyoo.Abstractions/Models/PartialResource.cs index b4456756..7e9b0089 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/GenreTests.cs +++ b/back/src/Kyoo.Abstractions/Models/PartialResource.cs @@ -16,33 +16,11 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System.Diagnostics.CodeAnalysis; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Xunit; -using Xunit.Abstractions; +namespace Kyoo.Models; -namespace Kyoo.Tests.Database +public class PartialResource { - namespace PostgreSQL - { - [Collection(nameof(Postgresql))] - public class GenreTests : AGenreTests - { - public GenreTests(PostgresFixture postgres, ITestOutputHelper output) - : base(new RepositoryActivator(output, postgres)) { } - } - } + public int? Id { get; set; } - public abstract class AGenreTests : RepositoryTests - { - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - private readonly IGenreRepository _repository; - - protected AGenreTests(RepositoryActivator repositories) - : base(repositories) - { - _repository = Repositories.LibraryManager.GenreRepository; - } - } + public string? Slug { get; set; } } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index eda05306..786f2572 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -43,7 +43,7 @@ namespace Kyoo.Abstractions.Models { if (ShowSlug != null || Show?.Slug != null) return GetSlug(ShowSlug ?? Show.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber); - return GetSlug(ShowID.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber); + return GetSlug(ShowId.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber); } [UsedImplicitly] @@ -81,17 +81,17 @@ namespace Kyoo.Abstractions.Models /// /// The ID of the Show containing this episode. /// - [SerializeIgnore] public int ShowID { get; set; } + [SerializeIgnore] public int ShowId { get; set; } /// /// The show that contains this episode. This must be explicitly loaded via a call to . /// - [LoadableRelation(nameof(ShowID))] public Show? Show { get; set; } + [LoadableRelation(nameof(ShowId))] public Show? Show { get; set; } /// /// The ID of the Season containing this episode. /// - [SerializeIgnore] public int? SeasonID { get; set; } + [SerializeIgnore] public int? SeasonId { get; set; } /// /// The season that contains this episode. @@ -101,7 +101,7 @@ namespace Kyoo.Abstractions.Models /// This can be null if the season is unknown and the episode is only identified /// by it's . /// - [LoadableRelation(nameof(SeasonID))] public Season? Season { get; set; } + [LoadableRelation(nameof(SeasonId))] public Season? Season { get; set; } /// /// The season in witch this episode is in. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs index b1eacec4..6cd21c3c 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs @@ -42,7 +42,7 @@ namespace Kyoo.Abstractions.Models get { if (ShowSlug == null && Show == null) - return $"{ShowID}-s{SeasonNumber}"; + return $"{ShowId}-s{SeasonNumber}"; return $"{ShowSlug ?? Show?.Slug}-s{SeasonNumber}"; } @@ -67,13 +67,13 @@ namespace Kyoo.Abstractions.Models /// /// The ID of the Show containing this season. /// - [SerializeIgnore] public int ShowID { get; set; } + [SerializeIgnore] public int ShowId { get; set; } /// /// The show that contains this season. /// This must be explicitly loaded via a call to . /// - [LoadableRelation(nameof(ShowID))] public Show? Show { get; set; } + [LoadableRelation(nameof(ShowId))] public Show? Show { get; set; } /// /// The number of this season. This can be set to 0 to indicate specials. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs index 38a4a42e..aeba5c68 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs @@ -51,7 +51,7 @@ namespace Kyoo.Abstractions.Models /// /// The list of alternative titles of this show. /// - public string[] Aliases { get; set; } = Array.Empty(); + public List Aliases { get; set; } = new(); /// /// The summary of this show. @@ -61,12 +61,12 @@ namespace Kyoo.Abstractions.Models /// /// A list of tags that match this movie. /// - public string[] Tags { get; set; } = Array.Empty(); + public List Tags { get; set; } = new(); /// /// The list of genres (themes) this show has. /// - public Genre[] Genres { get; set; } = Array.Empty(); + public List Genres { get; set; } = new(); /// /// Is this show airing, not aired yet or finished? @@ -106,13 +106,13 @@ namespace Kyoo.Abstractions.Models /// /// The ID of the Studio that made this show. /// - [SerializeIgnore] public int? StudioID { get; set; } + [SerializeIgnore] public int? StudioId { get; set; } /// /// The Studio that made this show. /// This must be explicitly loaded via a call to . /// - [LoadableRelation(nameof(StudioID))][EditableRelation] public Studio? Studio { get; set; } + [LoadableRelation(nameof(StudioId))][EditableRelation] public Studio? Studio { get; set; } /// /// The list of people that made this show. diff --git a/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs b/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs index fd1e4f4d..5d3d9038 100644 --- a/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs +++ b/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs @@ -17,9 +17,7 @@ // along with Kyoo. If not, see . using System; -using System.Collections; using System.Collections.Generic; -using System.Threading.Tasks; using JetBrains.Annotations; namespace Kyoo.Utils @@ -29,125 +27,6 @@ namespace Kyoo.Utils /// public static class EnumerableExtensions { - /// - /// A Select where the index of the item can be used. - /// - /// The IEnumerable to map. If self is null, an empty list is returned - /// The function that will map each items - /// The type of items in - /// The type of items in the returned list - /// The list mapped. - /// The list or the mapper can't be null - [LinqTunnel] - public static IEnumerable Map(this IEnumerable self, - Func mapper) - { - if (self == null) - throw new ArgumentNullException(nameof(self)); - if (mapper == null) - throw new ArgumentNullException(nameof(mapper)); - - static IEnumerable Generator(IEnumerable self, Func mapper) - { - using IEnumerator enumerator = self.GetEnumerator(); - int index = 0; - - while (enumerator.MoveNext()) - { - yield return mapper(enumerator.Current, index); - index++; - } - } - return Generator(self, mapper); - } - - /// - /// A map where the mapping function is asynchronous. - /// Note: might interest you. - /// - /// The IEnumerable to map. - /// The asynchronous function that will map each items. - /// The type of items in . - /// The type of items in the returned list. - /// The list mapped as an AsyncEnumerable. - /// The list or the mapper can't be null. - [LinqTunnel] - public static IAsyncEnumerable MapAsync(this IEnumerable self, - Func> mapper) - { - if (self == null) - throw new ArgumentNullException(nameof(self)); - if (mapper == null) - throw new ArgumentNullException(nameof(mapper)); - - static async IAsyncEnumerable Generator(IEnumerable self, Func> mapper) - { - using IEnumerator enumerator = self.GetEnumerator(); - int index = 0; - - while (enumerator.MoveNext()) - { - yield return await mapper(enumerator.Current, index); - index++; - } - } - - return Generator(self, mapper); - } - - /// - /// An asynchronous version of Select. - /// - /// The IEnumerable to map - /// The asynchronous function that will map each items - /// The type of items in - /// The type of items in the returned list - /// The list mapped as an AsyncEnumerable - /// The list or the mapper can't be null - [LinqTunnel] - public static IAsyncEnumerable SelectAsync(this IEnumerable self, - Func> mapper) - { - if (self == null) - throw new ArgumentNullException(nameof(self)); - if (mapper == null) - throw new ArgumentNullException(nameof(mapper)); - - static async IAsyncEnumerable Generator(IEnumerable self, Func> mapper) - { - using IEnumerator enumerator = self.GetEnumerator(); - - while (enumerator.MoveNext()) - yield return await mapper(enumerator.Current); - } - - return Generator(self, mapper); - } - - /// - /// Convert an AsyncEnumerable to a List by waiting for every item. - /// - /// The async list - /// The type of items in the async list and in the returned list. - /// A task that will return a simple list - /// The list can't be null - [LinqTunnel] - public static Task> ToListAsync(this IAsyncEnumerable self) - { - if (self == null) - throw new ArgumentNullException(nameof(self)); - - static async Task> ToList(IAsyncEnumerable self) - { - List ret = new(); - await foreach (T i in self) - ret.Add(i); - return ret; - } - - return ToList(self); - } - /// /// If the enumerable is empty, execute an action. /// @@ -197,104 +76,5 @@ namespace Kyoo.Utils foreach (T i in self) action(i); } - - /// - /// A foreach used as a function with a little specificity: the list can be null. - /// - /// The list to enumerate. If this is null, the function result in a no-op - /// The action to execute for each arguments - public static void ForEach(this IEnumerable? self, Action action) - { - if (self == null) - return; - foreach (object i in self) - action(i); - } - - /// - /// A foreach used as a function with a little specificity: the list can be null. - /// - /// The list to enumerate. If this is null, the function result in a no-op - /// The action to execute for each arguments - /// A representing the asynchronous operation. - public static async Task ForEachAsync(this IEnumerable? self, Func action) - { - if (self == null) - return; - foreach (object i in self) - await action(i); - } - - /// - /// A foreach used as a function with a little specificity: the list can be null. - /// - /// The list to enumerate. If this is null, the function result in a no-op - /// The asynchronous action to execute for each arguments - /// The type of items in the list. - /// A representing the asynchronous operation. - public static async Task ForEachAsync(this IEnumerable? self, Func action) - { - if (self == null) - return; - foreach (T i in self) - await action(i); - } - - /// - /// A foreach used as a function with a little specificity: the list can be null. - /// - /// The async list to enumerate. If this is null, the function result in a no-op - /// The action to execute for each arguments - /// The type of items in the list. - /// A representing the asynchronous operation. - public static async Task ForEachAsync(this IAsyncEnumerable? self, Action action) - { - if (self == null) - return; - await foreach (T i in self) - action(i); - } - - /// - /// Split a list in a small chunk of data. - /// - /// The list to split - /// The number of items in each chunk - /// The type of data in the initial list. - /// A list of chunks - [LinqTunnel] - public static IEnumerable> BatchBy(this List list, int countPerList) - { - for (int i = 0; i < list.Count; i += countPerList) - yield return list.GetRange(i, Math.Min(list.Count - i, countPerList)); - } - - /// - /// Split a list in a small chunk of data. - /// - /// The list to split - /// The number of items in each chunk - /// The type of data in the initial list. - /// A list of chunks - [LinqTunnel] - public static IEnumerable BatchBy(this IEnumerable list, int countPerList) - { - T[] ret = new T[countPerList]; - int i = 0; - - using IEnumerator enumerator = list.GetEnumerator(); - while (enumerator.MoveNext()) - { - ret[i] = enumerator.Current; - i++; - if (i < countPerList) - continue; - i = 0; - yield return ret; - } - - Array.Resize(ref ret, i); - yield return ret; - } } } diff --git a/back/src/Kyoo.Abstractions/Utility/Merger.cs b/back/src/Kyoo.Abstractions/Utility/Merger.cs index 951f379b..267f18fb 100644 --- a/back/src/Kyoo.Abstractions/Utility/Merger.cs +++ b/back/src/Kyoo.Abstractions/Utility/Merger.cs @@ -17,13 +17,10 @@ // along with Kyoo. If not, see . using System; -using System.Collections; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Reflection; using JetBrains.Annotations; -using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Attributes; namespace Kyoo.Utils @@ -33,99 +30,9 @@ namespace Kyoo.Utils /// public static class Merger { - /// - /// Merge two lists, can keep duplicates or remove them. - /// - /// The first enumerable to merge - /// The second enumerable to merge, if items from this list are equals to one from the first, they are not kept - /// Equality function to compare items. If this is null, duplicated elements are kept - /// The type of items in the lists to merge. - /// The two list merged as an array - [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static T[] MergeLists(IEnumerable? first, - IEnumerable? second, - Func? isEqual = null) - { - if (first == null) - return second?.ToArray(); - if (second == null) - return first.ToArray(); - if (isEqual == null) - return first.Concat(second).ToArray(); - List list = first.ToList(); - return list.Concat(second.Where(x => !list.Any(y => isEqual(x, y)))).ToArray(); - } - - /// - /// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept. - /// - /// The first dictionary to merge - /// The second dictionary to merge - /// The type of the keys in dictionaries - /// The type of values in the dictionaries - /// The first dictionary with the missing elements of . - /// - [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static IDictionary MergeDictionaries(IDictionary? first, - IDictionary? second) - { - return MergeDictionaries(first, second, out bool _); - } - - /// - /// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept. - /// - /// The first dictionary to merge - /// The second dictionary to merge - /// - /// true if a new items has been added to the dictionary, false otherwise. - /// - /// The type of the keys in dictionaries - /// The type of values in the dictionaries - /// The first dictionary with the missing elements of . - [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static IDictionary MergeDictionaries(IDictionary? first, - IDictionary? second, - out bool hasChanged) - { - if (first == null) - { - hasChanged = true; - return second; - } - - hasChanged = false; - if (second == null) - return first; - foreach ((T key, T2 value) in second) - { - bool success = first.TryAdd(key, value); - hasChanged |= success; - - if (success || first[key]?.Equals(default) == false || value?.Equals(default) != false) - continue; - first[key] = value; - hasChanged = true; - } - - return first; - } - /// /// Merge two dictionary, if the same key is found on both dictionary, the values of the second one is kept. /// - /// - /// The only difference in this function compared to - /// - /// is the way is calculated and the order of the arguments. - /// - /// MergeDictionaries(first, second); - /// - /// will do the same thing as - /// - /// CompleteDictionaries(second, first, out bool _); - /// - /// /// The first dictionary to merge /// The second dictionary to merge /// @@ -138,7 +45,7 @@ namespace Kyoo.Utils /// set to those of . /// [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static IDictionary CompleteDictionaries(IDictionary? first, + public static IDictionary? CompleteDictionaries(IDictionary? first, IDictionary? second, out bool hasChanged) { @@ -160,14 +67,8 @@ namespace Kyoo.Utils /// /// Set every non-default values of seconds to the corresponding property of second. /// Dictionaries are handled like anonymous objects with a property per key/pair value - /// (see - /// - /// for more details). /// At the end, the OnMerge method of first will be called if first is a /// - /// - /// This does the opposite of . - /// /// /// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "foo"} /// @@ -182,19 +83,16 @@ namespace Kyoo.Utils /// /// Fields of T will be completed /// - /// If first is null - public static T Complete([NotNull] T first, + public static T Complete(T first, T? second, [InstantHandle] Func? where = null) { - if (first == null) - throw new ArgumentNullException(nameof(first)); if (second == null) return first; Type type = typeof(T); IEnumerable properties = type.GetProperties() - .Where(x => x.CanRead && x.CanWrite + .Where(x => x is { CanRead: true, CanWrite: true } && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); if (where != null) @@ -202,17 +100,16 @@ namespace Kyoo.Utils foreach (PropertyInfo property in properties) { - object value = property.GetValue(second); - object defaultValue = property.GetCustomAttribute()?.Value - ?? property.PropertyType.GetClrDefault(); + object? value = property.GetValue(second); - if (value?.Equals(defaultValue) != false || value.Equals(property.GetValue(first))) + if (value?.Equals(property.GetValue(first)) == true) continue; + if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>))) { Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) .GenericTypeArguments; - object[] parameters = + object?[] parameters = { property.GetValue(first), value, @@ -222,8 +119,8 @@ namespace Kyoo.Utils typeof(Merger), nameof(CompleteDictionaries), dictionaryTypes, - parameters); - if ((bool)parameters[2]) + parameters)!; + if ((bool)parameters[2]!) property.SetValue(first, newDictionary); } else @@ -234,109 +131,5 @@ namespace Kyoo.Utils merge.OnMerge(second); return first; } - - /// - /// This will set missing values of to the corresponding values of . - /// Enumerable will be merged (concatenated) and Dictionaries too. - /// At the end, the OnMerge method of first will be called if first is a . - /// - /// - /// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "test"} - /// - /// - /// The object to complete - /// - /// - /// Missing fields of first will be completed by fields of this item. If second is null, the function no-op. - /// - /// - /// Filter fields that will be merged - /// - /// Fields of T will be merged - /// - [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static T Merge(T? first, - T? second, - [InstantHandle] Func? where = null) - { - if (first == null) - return second; - if (second == null) - return first; - - Type type = typeof(T); - IEnumerable properties = type.GetProperties() - .Where(x => x.CanRead && x.CanWrite - && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); - - if (where != null) - properties = properties.Where(where); - - foreach (PropertyInfo property in properties) - { - object oldValue = property.GetValue(first); - object newValue = property.GetValue(second); - object defaultValue = property.PropertyType.GetClrDefault(); - - if (oldValue?.Equals(defaultValue) != false) - property.SetValue(first, newValue); - else if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>))) - { - Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) - .GenericTypeArguments; - object[] parameters = - { - oldValue, - newValue, - false - }; - object newDictionary = Utility.RunGenericMethod( - typeof(Merger), - nameof(MergeDictionaries), - dictionaryTypes, - parameters); - if ((bool)parameters[2]) - property.SetValue(first, newDictionary); - } - else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType) - && property.PropertyType != typeof(string)) - { - Type enumerableType = Utility.GetGenericDefinition(property.PropertyType, typeof(IEnumerable<>)) - .GenericTypeArguments - .First(); - Func equalityComparer = enumerableType.IsAssignableTo(typeof(IResource)) - ? (x, y) => x.Slug == y.Slug - : null; - property.SetValue(first, Utility.RunGenericMethod( - typeof(Merger), - nameof(MergeLists), - enumerableType, - oldValue, newValue, equalityComparer)); - } - } - - if (first is IOnMerge merge) - merge.OnMerge(second); - return first; - } - - /// - /// Set every fields of to the default value. - /// - /// The object to nullify - /// Fields of T will be nullified - /// - public static T Nullify(T obj) - { - Type type = typeof(T); - foreach (PropertyInfo property in type.GetProperties()) - { - if (!property.CanWrite || property.GetCustomAttribute() != null) - continue; - property.SetValue(obj, property.PropertyType.GetClrDefault()); - } - - return obj; - } } } diff --git a/back/src/Kyoo.Abstractions/Utility/TaskUtils.cs b/back/src/Kyoo.Abstractions/Utility/TaskUtils.cs index 42c01027..973ec3ea 100644 --- a/back/src/Kyoo.Abstractions/Utility/TaskUtils.cs +++ b/back/src/Kyoo.Abstractions/Utility/TaskUtils.cs @@ -18,7 +18,6 @@ using System; using System.Threading.Tasks; -using JetBrains.Annotations; namespace Kyoo.Utils { @@ -49,37 +48,5 @@ namespace Kyoo.Utils return x.Result; }, TaskContinuationOptions.ExecuteSynchronously); } - - /// - /// Map the result of a task to another result. - /// - /// The task to map. - /// The mapper method, it take the task's result as a parameter and should return the new result. - /// The type of returns of the given task - /// The resulting task after the mapping method - /// A task wrapping the initial task and mapping the initial result. - /// The source task has been canceled. - public static Task Map(this Task task, Func map) - { - return task.ContinueWith(x => - { - if (x.IsFaulted) - x.Exception!.InnerException!.ReThrow(); - if (x.IsCanceled) - throw new TaskCanceledException(); - return map(x.Result); - }, TaskContinuationOptions.ExecuteSynchronously); - } - - /// - /// A method to return the a default value from a task if the initial task is null. - /// - /// The initial task - /// The type that the task will return - /// A non-null task. - public static Task DefaultIfNull(Task? value) - { - return value ?? Task.FromResult(default); - } } } diff --git a/back/src/Kyoo.Abstractions/Utility/Utility.cs b/back/src/Kyoo.Abstractions/Utility/Utility.cs index 57e261df..ca9c513e 100644 --- a/back/src/Kyoo.Abstractions/Utility/Utility.cs +++ b/back/src/Kyoo.Abstractions/Utility/Utility.cs @@ -41,8 +41,6 @@ namespace Kyoo.Utils /// True if the expression is a member, false otherwise public static bool IsPropertyExpression(LambdaExpression ex) { - if (ex == null) - return false; return ex.Body is MemberExpression || (ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression); } @@ -57,7 +55,7 @@ namespace Kyoo.Utils { if (!IsPropertyExpression(ex)) throw new ArgumentException($"{ex} is not a property expression."); - MemberExpression member = ex.Body.NodeType == ExpressionType.Convert + MemberExpression? member = ex.Body.NodeType == ExpressionType.Convert ? ((UnaryExpression)ex.Body).Operand as MemberExpression : ex.Body as MemberExpression; return member!.Member.Name; @@ -92,18 +90,6 @@ namespace Kyoo.Utils return str; } - /// - /// Get the default value of a type. - /// - /// The type to get the default value - /// The default value of the given type. - public static object GetClrDefault(this Type type) - { - return type.IsValueType - ? Activator.CreateInstance(type) - : null; - } - /// /// Return every in the inheritance tree of the parameter (interfaces are not returned) /// @@ -194,13 +180,6 @@ namespace Kyoo.Utils Type[] generics, object[] args) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - if (generics == null) - throw new ArgumentNullException(nameof(generics)); - if (args == null) - throw new ArgumentNullException(nameof(args)); - MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public) .Where(x => x.Name == name) .Where(x => x.GetGenericArguments().Length == generics.Length) @@ -295,12 +274,11 @@ namespace Kyoo.Utils /// The return of the method you wanted to run. /// /// - [PublicAPI] - public static T RunGenericMethod( + public static T? RunGenericMethod( Type owner, string methodName, Type[] types, - params object[] args) + params object?[] args) { if (owner == null) throw new ArgumentNullException(nameof(owner)); @@ -311,7 +289,7 @@ namespace Kyoo.Utils if (types.Length < 1) throw new ArgumentException($"The {nameof(types)} array is empty. At least one type is needed."); MethodInfo method = GetMethod(owner, BindingFlags.Static, methodName, types, args); - return (T)method.MakeGenericMethod(types).Invoke(null, args); + return (T?)method.MakeGenericMethod(types).Invoke(null, args); } /// diff --git a/back/src/Kyoo.Authentication/Views/AuthApi.cs b/back/src/Kyoo.Authentication/Views/AuthApi.cs index a3b8135c..2040ac13 100644 --- a/back/src/Kyoo.Authentication/Views/AuthApi.cs +++ b/back/src/Kyoo.Authentication/Views/AuthApi.cs @@ -27,6 +27,7 @@ using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Utils; using Kyoo.Authentication.Models; using Kyoo.Authentication.Models.DTO; +using Kyoo.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; @@ -229,7 +230,7 @@ namespace Kyoo.Authentication.Views try { user.Id = userID; - return await _users.Edit(user, true); + return await _users.Edit(user); } catch (ItemNotFoundException) { @@ -252,14 +253,15 @@ namespace Kyoo.Authentication.Views [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))] - public async Task> PatchMe(User user) + public async Task> PatchMe(PartialResource user) { if (!int.TryParse(User.FindFirstValue(Claims.Id), out int userID)) return Unauthorized(new RequestError("User not authenticated or token invalid.")); try { - user.Id = userID; - return await _users.Edit(user, false); + if (user.Id.HasValue && user.Id != userID) + throw new ArgumentException("Can't edit your user id."); + return await _users.Patch(userID, TryUpdateModelAsync); } catch (ItemNotFoundException) { diff --git a/back/src/Kyoo.Core/Controllers/LibraryManager.cs b/back/src/Kyoo.Core/Controllers/LibraryManager.cs index b134e76b..0cab6155 100644 --- a/back/src/Kyoo.Core/Controllers/LibraryManager.cs +++ b/back/src/Kyoo.Core/Controllers/LibraryManager.cs @@ -284,12 +284,12 @@ namespace Kyoo.Core.Controllers (Show s, nameof(Show.Seasons)) => _SetRelation(s, SeasonRepository.GetAll(x => x.Show.Id == obj.Id), (x, y) => x.Seasons = y, - (x, y) => { x.Show = y; x.ShowID = y.Id; }), + (x, y) => { x.Show = y; x.ShowId = y.Id; }), (Show s, nameof(Show.Episodes)) => _SetRelation(s, EpisodeRepository.GetAll(x => x.Show.Id == obj.Id), (x, y) => x.Episodes = y, - (x, y) => { x.Show = y; x.ShowID = y.Id; }), + (x, y) => { x.Show = y; x.ShowId = y.Id; }), (Show s, nameof(Show.Collections)) => CollectionRepository .GetAll(x => x.Shows.Any(y => y.Id == obj.Id)) @@ -300,21 +300,21 @@ namespace Kyoo.Core.Controllers .Then(x => { s.Studio = x; - s.StudioID = x?.Id ?? 0; + s.StudioId = x?.Id ?? 0; }), (Season s, nameof(Season.Episodes)) => _SetRelation(s, EpisodeRepository.GetAll(x => x.Season.Id == obj.Id), (x, y) => x.Episodes = y, - (x, y) => { x.Season = y; x.SeasonID = y.Id; }), + (x, y) => { x.Season = y; x.SeasonId = y.Id; }), (Season s, nameof(Season.Show)) => ShowRepository .GetOrDefault(x => x.Seasons.Any(y => y.Id == obj.Id)) .Then(x => { s.Show = x; - s.ShowID = x?.Id ?? 0; + s.ShowId = x?.Id ?? 0; }), @@ -323,7 +323,7 @@ namespace Kyoo.Core.Controllers .Then(x => { e.Show = x; - e.ShowID = x?.Id ?? 0; + e.ShowId = x?.Id ?? 0; }), (Episode e, nameof(Episode.Season)) => SeasonRepository @@ -331,18 +331,18 @@ namespace Kyoo.Core.Controllers .Then(x => { e.Season = x; - e.SeasonID = x?.Id ?? 0; + e.SeasonId = x?.Id ?? 0; }), (Episode e, nameof(Episode.PreviousEpisode)) => EpisodeRepository .GetAll( - where: x => x.ShowID == e.ShowID, + where: x => x.ShowId == e.ShowId, limit: new Pagination(1, e.Id, true) ).Then(x => e.PreviousEpisode = x.FirstOrDefault()), (Episode e, nameof(Episode.NextEpisode)) => EpisodeRepository .GetAll( - where: x => x.ShowID == e.ShowID, + where: x => x.ShowId == e.ShowId, limit: new Pagination(1, e.Id) ).Then(x => e.NextEpisode = x.FirstOrDefault()), @@ -438,10 +438,17 @@ namespace Kyoo.Core.Controllers } /// - public Task Edit(T item, bool resetOld) + public Task Edit(T item) where T : class, IResource { - return GetRepository().Edit(item, resetOld); + return GetRepository().Edit(item); + } + + /// + public Task Patch(int id, Func> patch) + where T : class, IResource + { + return GetRepository().Patch(id, patch); } /// diff --git a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs index fe4e7412..f1a49d41 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs @@ -64,7 +64,7 @@ namespace Kyoo.Core.Controllers // Edit episode slugs when the show's slug changes. shows.OnEdited += (show) => { - List episodes = _database.Episodes.AsTracking().Where(x => x.ShowID == show.Id).ToList(); + List episodes = _database.Episodes.AsTracking().Where(x => x.ShowId == show.Id).ToList(); foreach (Episode ep in episodes) { ep.ShowSlug = show.Slug; @@ -77,7 +77,7 @@ namespace Kyoo.Core.Controllers /// public Task GetOrDefault(int showID, int seasonNumber, int episodeNumber) { - return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + return _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == showID && x.SeasonNumber == seasonNumber && x.EpisodeNumber == episodeNumber); } @@ -111,7 +111,7 @@ namespace Kyoo.Core.Controllers /// public Task GetAbsolute(int showID, int absoluteNumber) { - return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + return _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == showID && x.AbsoluteNumber == absoluteNumber); } @@ -142,12 +142,12 @@ namespace Kyoo.Core.Controllers public override async Task Create(Episode obj) { await base.Create(obj); - obj.ShowSlug = obj.Show?.Slug ?? _database.Shows.First(x => x.Id == obj.ShowID).Slug; + obj.ShowSlug = obj.Show?.Slug ?? _database.Shows.First(x => x.Id == obj.ShowId).Slug; _database.Entry(obj).State = EntityState.Added; await _database.SaveChangesAsync(() => obj.SeasonNumber != null && obj.EpisodeNumber != null - ? Get(obj.ShowID, obj.SeasonNumber.Value, obj.EpisodeNumber.Value) - : GetAbsolute(obj.ShowID, obj.AbsoluteNumber.Value)); + ? Get(obj.ShowId, obj.SeasonNumber.Value, obj.EpisodeNumber.Value) + : GetAbsolute(obj.ShowId, obj.AbsoluteNumber.Value)); OnResourceCreated(obj); return obj; } @@ -156,14 +156,14 @@ namespace Kyoo.Core.Controllers protected override async Task Validate(Episode resource) { await base.Validate(resource); - if (resource.ShowID <= 0) + if (resource.ShowId <= 0) { if (resource.Show == null) { throw new ArgumentException($"Can't store an episode not related " + - $"to any show (showID: {resource.ShowID})."); + $"to any show (showID: {resource.ShowId})."); } - resource.ShowID = resource.Show.Id; + resource.ShowId = resource.Show.Id; } } @@ -173,12 +173,12 @@ namespace Kyoo.Core.Controllers if (obj == null) throw new ArgumentNullException(nameof(obj)); - int epCount = await _database.Episodes.Where(x => x.ShowID == obj.ShowID).Take(2).CountAsync(); + int epCount = await _database.Episodes.Where(x => x.ShowId == obj.ShowId).Take(2).CountAsync(); _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); await base.Delete(obj); if (epCount == 1) - await _shows.Delete(obj.ShowID); + await _shows.Delete(obj.ShowId); } } } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs index 33dd9b8b..0a6350f8 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs @@ -100,7 +100,11 @@ namespace Kyoo.Core.Controllers => throw new InvalidOperationException(); /// - public override Task Edit(ILibraryItem obj, bool resetOld) + public override Task Edit(ILibraryItem obj) + => throw new InvalidOperationException(); + + /// + public override Task Patch(int id, Func> patch) => throw new InvalidOperationException(); /// diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index 8e421c7c..0ced12d0 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -144,7 +144,7 @@ namespace Kyoo.Core.Controllers /// The reference item (the AfterID query) /// True if the following page should be returned, false for the previous. /// An expression ready to be added to a Where close of a sorted query to handle the AfterID - protected Expression> KeysetPaginatate( + protected Expression> KeysetPaginate( Sort sort, T reference, bool next = true) @@ -155,22 +155,22 @@ namespace Kyoo.Core.Controllers ParameterExpression x = Expression.Parameter(typeof(T), "x"); ConstantExpression referenceC = Expression.Constant(reference, typeof(T)); - IEnumerable.By> _GetSortsBy(Sort sort) + IEnumerable.By> GetSortsBy(Sort sort) { return sort switch { - Sort.Default => _GetSortsBy(DefaultSort), + Sort.Default => GetSortsBy(DefaultSort), Sort.By @sortBy => new[] { sortBy }, - Sort.Conglomerate(var list) => list.SelectMany(_GetSortsBy), + Sort.Conglomerate(var list) => list.SelectMany(GetSortsBy), _ => Array.Empty.By>(), }; } - // Don't forget that every sorts must end with a ID sort (to differenciate equalities). + // Don't forget that every sorts must end with a ID sort (to differentiate equalities). Sort.By id = new(x => x.Id); - IEnumerable.By> sorts = _GetSortsBy(sort).Append(id); + IEnumerable.By> sorts = GetSortsBy(sort).Append(id); - BinaryExpression filter = null; + BinaryExpression? filter = null; List.By> previousSteps = new(); // TODO: Add an outer query >= for perf // PERF: See https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-equivalent-logic @@ -180,9 +180,9 @@ namespace Kyoo.Core.Controllers PropertyInfo property = typeof(T).GetProperty(key); // Comparing a value with null always return false so we short opt < > comparisons with null. - if (property.GetValue(reference) == null) + if (property!.GetValue(reference) == null) { - previousSteps.Add(new(key, desc)); + previousSteps.Add(new Sort.By(key, desc)); continue; } @@ -206,7 +206,7 @@ namespace Kyoo.Core.Controllers // Comparing a value with null always return false for nulls so we must add nulls to the results manually. // Postgres sorts them after values so we will do the same - // We only add this condition if the collumn type is nullable + // We only add this condition if the column type is nullable if (Nullable.GetUnderlyingType(property.PropertyType) != null) { BinaryExpression equalNull = Expression.Equal(xkey, Expression.Constant(null)); @@ -223,7 +223,7 @@ namespace Kyoo.Core.Controllers previousSteps.Add(new(key, desc)); } - return Expression.Lambda>(filter, x); + return Expression.Lambda>(filter!, x); } /// @@ -316,7 +316,7 @@ namespace Kyoo.Core.Controllers if (limit?.AfterID != null) { T reference = await Get(limit.AfterID.Value); - query = query.Where(KeysetPaginatate(sort, reference, !limit.Reverse)); + query = query.Where(KeysetPaginate(sort, reference, !limit.Reverse)); } if (limit?.Reverse == true) query = query.Reverse(); @@ -343,12 +343,9 @@ namespace Kyoo.Core.Controllers await Validate(obj); if (obj is IThumbnails thumbs) { - if (thumbs.Poster != null) - Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry.State = EntityState.Added; - if (thumbs.Thumbnail != null) - Database.Entry(thumbs).Reference(x => x.Thumbnail).TargetEntry.State = EntityState.Added; - if (thumbs.Logo != null) - Database.Entry(thumbs).Reference(x => x.Logo).TargetEntry.State = EntityState.Added; + Database.Entry(thumbs).Reference(x => x.Poster).IsModified = thumbs.Poster != null; + Database.Entry(thumbs).Reference(x => x.Thumbnail).IsModified = thumbs.Thumbnail != null; + Database.Entry(thumbs).Reference(x => x.Logo).IsModified = thumbs.Logo != null; } return obj; } @@ -376,9 +373,6 @@ namespace Kyoo.Core.Controllers { try { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - T old = await GetOrDefault(obj.Slug); if (old != null) return old; @@ -392,21 +386,16 @@ namespace Kyoo.Core.Controllers } /// - public virtual async Task Edit(T edited, bool resetOld) + public virtual async Task Edit(T edited) { - if (edited == null) - throw new ArgumentNullException(nameof(edited)); - bool lazyLoading = Database.ChangeTracker.LazyLoadingEnabled; Database.ChangeTracker.LazyLoadingEnabled = false; try { T old = await GetWithTracking(edited.Id); - if (resetOld) - old = Merger.Nullify(old); Merger.Complete(old, edited, x => x.GetCustomAttribute() == null); - await EditRelations(old, edited, resetOld); + await EditRelations(old, edited, true); await Database.SaveChangesAsync(); OnEdited?.Invoke(old); return old; @@ -418,6 +407,28 @@ namespace Kyoo.Core.Controllers } } + /// + public virtual async Task Patch(int id, Func> patch) + { + bool lazyLoading = Database.ChangeTracker.LazyLoadingEnabled; + Database.ChangeTracker.LazyLoadingEnabled = false; + try + { + T resource = await GetWithTracking(id); + + if (!await patch(resource)) + throw new ArgumentException("Could not patch resource"); + await Database.SaveChangesAsync(); + OnEdited?.Invoke(resource); + return resource; + } + finally + { + Database.ChangeTracker.LazyLoadingEnabled = lazyLoading; + Database.ChangeTracker.Clear(); + } + } + /// /// An overridable method to edit relation of a resource. /// @@ -434,12 +445,11 @@ namespace Kyoo.Core.Controllers /// A representing the asynchronous operation. protected virtual Task EditRelations(T resource, T changed, bool resetOld) { - if (resource is IThumbnails thumbs) + if (resource is IThumbnails thumbs && changed is IThumbnails chng) { - // FIXME: I think this throws if poster is set to null. - Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry.State = EntityState.Modified; - Database.Entry(thumbs).Reference(x => x.Thumbnail).TargetEntry.State = EntityState.Modified; - Database.Entry(thumbs).Reference(x => x.Logo).TargetEntry.State = EntityState.Modified; + Database.Entry(thumbs).Reference(x => x.Poster).IsModified = thumbs.Poster != chng.Poster; + Database.Entry(thumbs).Reference(x => x.Thumbnail).IsModified = thumbs.Thumbnail != chng.Thumbnail; + Database.Entry(thumbs).Reference(x => x.Logo).IsModified = thumbs.Logo != chng.Logo; } return Validate(resource); } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index 37a2e820..0265fc4a 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -55,7 +55,7 @@ namespace Kyoo.Core.Controllers // Edit seasons slugs when the show's slug changes. shows.OnEdited += (show) => { - List seasons = _database.Seasons.AsTracking().Where(x => x.ShowID == show.Id).ToList(); + List seasons = _database.Seasons.AsTracking().Where(x => x.ShowId == show.Id).ToList(); foreach (Season season in seasons) { season.ShowSlug = show.Slug; @@ -86,7 +86,7 @@ namespace Kyoo.Core.Controllers /// public Task GetOrDefault(int showID, int seasonNumber) { - return _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID + return _database.Seasons.FirstOrDefaultAsync(x => x.ShowId == showID && x.SeasonNumber == seasonNumber); } @@ -112,9 +112,9 @@ namespace Kyoo.Core.Controllers public override async Task Create(Season obj) { await base.Create(obj); - obj.ShowSlug = _database.Shows.First(x => x.Id == obj.ShowID).Slug; + obj.ShowSlug = _database.Shows.First(x => x.Id == obj.ShowId).Slug; _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync(() => Get(obj.ShowID, obj.SeasonNumber)); + await _database.SaveChangesAsync(() => Get(obj.ShowId, obj.SeasonNumber)); OnResourceCreated(obj); return obj; } @@ -123,14 +123,14 @@ namespace Kyoo.Core.Controllers protected override async Task Validate(Season resource) { await base.Validate(resource); - if (resource.ShowID <= 0) + if (resource.ShowId <= 0) { if (resource.Show == null) { throw new ArgumentException($"Can't store a season not related to any show " + - $"(showID: {resource.ShowID})."); + $"(showID: {resource.ShowId})."); } - resource.ShowID = resource.Show.Id; + resource.ShowId = resource.Show.Id; } } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs index 2bfb8fda..810300a3 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs @@ -97,7 +97,7 @@ namespace Kyoo.Core.Controllers if (resource.Studio != null) { resource.Studio = await _studios.CreateIfNotExists(resource.Studio); - resource.StudioID = resource.Studio.Id; + resource.StudioId = resource.Studio.Id; } if (resource.People != null) diff --git a/back/src/Kyoo.Core/Views/Helper/CrudApi.cs b/back/src/Kyoo.Core/Views/Helper/CrudApi.cs index 2a786bd8..a593ac9a 100644 --- a/back/src/Kyoo.Core/Views/Helper/CrudApi.cs +++ b/back/src/Kyoo.Core/Views/Helper/CrudApi.cs @@ -16,12 +16,14 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . +using System; using System.Collections.Generic; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Utils; +using Kyoo.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -162,11 +164,11 @@ namespace Kyoo.Core.Api public async Task> Edit([FromBody] T resource) { if (resource.Id > 0) - return await Repository.Edit(resource, true); + return await Repository.Edit(resource); T old = await Repository.Get(resource.Slug); resource.Id = old.Id; - return await Repository.Edit(resource, true); + return await Repository.Edit(resource); } /// @@ -185,14 +187,15 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> Patch([FromBody] T resource) + public async Task> Patch([FromBody] PartialResource resource) { - if (resource.Id > 0) - return await Repository.Edit(resource, false); + if (resource.Id.HasValue) + return await Repository.Patch(resource.Id.Value, TryUpdateModelAsync); + if (resource.Slug == null) + throw new ArgumentException("Either the Id or the slug of the resource has to be defined to edit it."); T old = await Repository.Get(resource.Slug); - resource.Id = old.Id; - return await Repository.Edit(resource, false); + return await Repository.Patch(old.Id, TryUpdateModelAsync); } /// diff --git a/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs b/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs index 873c398f..90c72539 100644 --- a/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs +++ b/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs @@ -83,7 +83,7 @@ namespace Kyoo.Core.Api [FromQuery] Pagination pagination) { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.Matcher(x => x.StudioID, x => x.Studio.Slug)), + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.StudioId, x => x.Studio.Slug)), Sort.From(sortBy), pagination ); diff --git a/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs b/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs index d336e22c..f26bbe30 100644 --- a/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs @@ -84,7 +84,7 @@ namespace Kyoo.Core.Api [FromQuery] Pagination pagination) { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.Matcher(x => x.SeasonID, x => x.Season.Slug)), + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.SeasonId, x => x.Season.Slug)), Sort.From(sortBy), pagination ); diff --git a/back/src/Kyoo.Core/Views/Resources/ShowApi.cs b/back/src/Kyoo.Core/Views/Resources/ShowApi.cs index 68d44eb1..2084291e 100644 --- a/back/src/Kyoo.Core/Views/Resources/ShowApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/ShowApi.cs @@ -86,7 +86,7 @@ namespace Kyoo.Core.Api [FromQuery] Pagination pagination) { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowID, x => x.Show.Slug)), + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowId, x => x.Show.Slug)), Sort.From(sortBy), pagination ); @@ -121,7 +121,7 @@ namespace Kyoo.Core.Api [FromQuery] Pagination pagination) { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowID, x => x.Show.Slug)), + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowId, x => x.Show.Slug)), Sort.From(sortBy), pagination ); diff --git a/back/src/Kyoo.Postgresql/DatabaseContext.cs b/back/src/Kyoo.Postgresql/DatabaseContext.cs index 3d794f2c..aa9e5ca5 100644 --- a/back/src/Kyoo.Postgresql/DatabaseContext.cs +++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs @@ -306,13 +306,13 @@ namespace Kyoo.Postgresql .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() - .HasIndex(x => new { x.ShowID, x.SeasonNumber }) + .HasIndex(x => new { ShowID = x.ShowId, x.SeasonNumber }) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() - .HasIndex(x => new { x.ShowID, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber }) + .HasIndex(x => new { ShowID = x.ShowId, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber }) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) diff --git a/back/tests/Kyoo.Tests/Database/RepositoryTests.cs b/back/tests/Kyoo.Tests/Database/RepositoryTests.cs index f6aa7de4..383d8fbe 100644 --- a/back/tests/Kyoo.Tests/Database/RepositoryTests.cs +++ b/back/tests/Kyoo.Tests/Database/RepositoryTests.cs @@ -140,16 +140,10 @@ namespace Kyoo.Tests.Database KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(TestSample.Get())); } - [Fact] - public async Task EditNullTest() - { - await Assert.ThrowsAsync(() => _repository.Edit(null!, false)); - } - [Fact] public async Task EditNonExistingTest() { - await Assert.ThrowsAsync(() => _repository.Edit(new T { Id = 56 }, false)); + await Assert.ThrowsAsync(() => _repository.Edit(new T { Id = 56 })); } [Fact] @@ -170,12 +164,6 @@ namespace Kyoo.Tests.Database await Assert.ThrowsAsync(() => _repository.Get(x => x.Slug == "non-existing")); } - [Fact] - public async Task GetExpressionNullTest() - { - await Assert.ThrowsAsync(() => _repository.Get((Expression>)null!)); - } - [Fact] public async Task GetOrDefaultTest() { diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs index ef38efb1..2094afdb 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs @@ -66,29 +66,19 @@ namespace Kyoo.Tests.Database Assert.Equal("2!", ret.Slug); } - [Fact] - public async Task CreateWithoutNameTest() - { - Collection collection = TestSample.GetNew(); - collection.Name = null; - await Assert.ThrowsAsync(() => _repository.Create(collection)); - } - [Fact] public async Task CreateWithExternalIdTest() { Collection collection = TestSample.GetNew(); - collection.ExternalId = new[] + collection.ExternalId = new Dictionary { - new MetadataId + ["1"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, - new MetadataId + ["2"] = new() { - Provider = TestSample.GetNew(), Link = "new-provider-link", DataId = "new-id" } @@ -96,7 +86,6 @@ namespace Kyoo.Tests.Database await _repository.Create(collection); Collection retrieved = await _repository.Get(2); - await Repositories.LibraryManager.Load(retrieved, x => x.ExternalId); Assert.Equal(2, retrieved.ExternalId.Count); KAssert.DeepEqual(collection.ExternalId.First(), retrieved.ExternalId.First()); KAssert.DeepEqual(collection.ExternalId.Last(), retrieved.ExternalId.Last()); @@ -107,11 +96,8 @@ namespace Kyoo.Tests.Database { Collection value = await _repository.Get(TestSample.Get().Slug); value.Name = "New Title"; - value.Images = new Dictionary - { - [Images.Poster] = "new-poster" - }; - await _repository.Edit(value, false); + value.Poster = new Image("new-poster"); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections.FirstAsync(); @@ -123,21 +109,19 @@ namespace Kyoo.Tests.Database public async Task EditMetadataTest() { Collection value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new[] + value.ExternalId = new Dictionary { - new MetadataId + ["test"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) .FirstAsync(); KAssert.DeepEqual(value, retrieved); @@ -147,40 +131,36 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { Collection value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new List + value.ExternalId = new Dictionary { - new() + ["toto"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) .FirstAsync(); KAssert.DeepEqual(value, retrieved); } - value.ExternalId.Add(new MetadataId + value.ExternalId.Add("test", new MetadataId { - Provider = TestSample.GetNew(), Link = "link", DataId = "id" }); - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) .FirstAsync(); KAssert.DeepEqual(value, retrieved); diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs index 8ee85408..dfb91af4 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs @@ -57,10 +57,10 @@ namespace Kyoo.Tests.Database Assert.Equal($"{TestSample.Get().Slug}-s1e1", episode.Slug); Show show = new() { - Id = episode.ShowID, + Id = episode.ShowId, Slug = "new-slug" }; - await Repositories.LibraryManager.ShowRepository.Edit(show, false); + await Repositories.LibraryManager.ShowRepository.Edit(show); episode = await _repository.Get(1); Assert.Equal("new-slug-s1e1", episode.Slug); } @@ -74,8 +74,8 @@ namespace Kyoo.Tests.Database { Id = 1, SeasonNumber = 2, - ShowID = 1 - }, false); + ShowId = 1 + }); Assert.Equal($"{TestSample.Get().Slug}-s2e1", episode.Slug); episode = await _repository.Get(1); Assert.Equal($"{TestSample.Get().Slug}-s2e1", episode.Slug); @@ -90,8 +90,8 @@ namespace Kyoo.Tests.Database { Id = 1, EpisodeNumber = 2, - ShowID = 1 - }, false); + ShowId = 1 + }); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); episode = await _repository.Get(1); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); @@ -102,7 +102,7 @@ namespace Kyoo.Tests.Database { Episode episode = await _repository.Create(new Episode { - ShowID = TestSample.Get().Id, + ShowId = TestSample.Get().Id, SeasonNumber = 2, EpisodeNumber = 4 }); @@ -129,10 +129,10 @@ namespace Kyoo.Tests.Database Episode episode = await _repository.Create(TestSample.GetAbsoluteEpisode()); Show show = new() { - Id = episode.ShowID, + Id = episode.ShowId, Slug = "new-slug" }; - await Repositories.LibraryManager.ShowRepository.Edit(show, false); + await Repositories.LibraryManager.ShowRepository.Edit(show); episode = await _repository.Get(2); Assert.Equal($"new-slug-3", episode.Slug); } @@ -145,8 +145,8 @@ namespace Kyoo.Tests.Database { Id = 2, AbsoluteNumber = 56, - ShowID = 1 - }, false); + ShowId = 1 + }); Assert.Equal($"{TestSample.Get().Slug}-56", episode.Slug); episode = await _repository.Get(2); Assert.Equal($"{TestSample.Get().Slug}-56", episode.Slug); @@ -161,8 +161,8 @@ namespace Kyoo.Tests.Database Id = 2, SeasonNumber = 1, EpisodeNumber = 2, - ShowID = 1 - }, false); + ShowId = 1 + }); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); episode = await _repository.Get(2); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); @@ -174,49 +174,25 @@ namespace Kyoo.Tests.Database Episode episode = await _repository.Get(1); episode.SeasonNumber = null; episode.AbsoluteNumber = 12; - episode = await _repository.Edit(episode, true); + episode = await _repository.Edit(episode); Assert.Equal($"{TestSample.Get().Slug}-12", episode.Slug); episode = await _repository.Get(1); Assert.Equal($"{TestSample.Get().Slug}-12", episode.Slug); } - [Fact] - public async Task MovieEpisodeTest() - { - Episode episode = await _repository.Create(TestSample.GetMovieEpisode()); - Assert.Equal(TestSample.Get().Slug, episode.Slug); - episode = await _repository.Get(3); - Assert.Equal(TestSample.Get().Slug, episode.Slug); - } - - [Fact] - public async Task MovieEpisodeEditTest() - { - await _repository.Create(TestSample.GetMovieEpisode()); - await Repositories.LibraryManager.Edit(new Show - { - Id = 1, - Slug = "john-wick" - }, false); - Episode episode = await _repository.Get(3); - Assert.Equal("john-wick", episode.Slug); - } - [Fact] public async Task CreateWithExternalIdTest() { Episode value = TestSample.GetNew(); - value.ExternalId = new[] + value.ExternalId = new Dictionary { - new MetadataId + ["2"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, - new MetadataId + ["3"] = new() { - Provider = TestSample.GetNew(), Link = "new-provider-link", DataId = "new-id" } @@ -224,7 +200,6 @@ namespace Kyoo.Tests.Database await _repository.Create(value); Episode retrieved = await _repository.Get(2); - await Repositories.LibraryManager.Load(retrieved, x => x.ExternalId); Assert.Equal(2, retrieved.ExternalId.Count); KAssert.DeepEqual(value.ExternalId.First(), retrieved.ExternalId.First()); KAssert.DeepEqual(value.ExternalId.Last(), retrieved.ExternalId.Last()); @@ -235,11 +210,8 @@ namespace Kyoo.Tests.Database { Episode value = await _repository.Get(TestSample.Get().Slug); value.Name = "New Title"; - value.Images = new Dictionary - { - [Images.Poster] = "new-poster" - }; - await _repository.Edit(value, false); + value.Poster = new Image("poster"); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); Episode retrieved = await database.Episodes.FirstAsync(); @@ -251,22 +223,18 @@ namespace Kyoo.Tests.Database public async Task EditMetadataTest() { Episode value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new[] + value.ExternalId = new Dictionary { - new MetadataId + ["1"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); - Episode retrieved = await database.Episodes - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Episode retrieved = await database.Episodes.FirstAsync(); KAssert.DeepEqual(value, retrieved); } @@ -275,41 +243,33 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { Episode value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new List + value.ExternalId = new Dictionary { - new() + ["toto"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); - Episode retrieved = await database.Episodes - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Episode retrieved = await database.Episodes.FirstAsync(); KAssert.DeepEqual(value, retrieved); } - value.ExternalId.Add(new MetadataId + value.ExternalId.Add("test", new MetadataId { - Provider = TestSample.GetNew(), Link = "link", DataId = "id" }); - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); - Episode retrieved = await database.Episodes - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Episode retrieved = await database.Episodes.FirstAsync(); KAssert.DeepEqual(value, retrieved); } @@ -326,7 +286,7 @@ namespace Kyoo.Tests.Database Episode value = new() { Name = "This is a test super title", - ShowID = 1, + ShowId = 1, AbsoluteNumber = 2 }; await _repository.Create(value); @@ -343,8 +303,8 @@ namespace Kyoo.Tests.Database Episode expected = TestSample.Get(); expected.Id = 0; - expected.ShowID = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get())).Id; - expected.SeasonID = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get())).Id; + expected.ShowId = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get())).Id; + expected.SeasonId = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get())).Id; await _repository.Create(expected); KAssert.DeepEqual(expected, await _repository.Get(expected.Slug)); } @@ -355,8 +315,8 @@ namespace Kyoo.Tests.Database Episode expected = TestSample.Get(); KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(TestSample.Get())); await _repository.Delete(TestSample.Get()); - expected.ShowID = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get())).Id; - expected.SeasonID = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get())).Id; + expected.ShowId = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get())).Id; + expected.SeasonId = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get())).Id; KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(expected)); } } diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs deleted file mode 100644 index db572ea1..00000000 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Kyoo - A portable and vast media library solution. -// Copyright (c) Kyoo. -// -// See AUTHORS.md and LICENSE file in the project root for full license information. -// -// Kyoo is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// Kyoo is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Kyoo. If not, see . - -using System; -using System.Threading.Tasks; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Xunit; -using Xunit.Abstractions; - -namespace Kyoo.Tests.Database -{ - namespace PostgreSQL - { - [Collection(nameof(Postgresql))] - public class LibraryItemTest : ALibraryItemTest - { - public LibraryItemTest(PostgresFixture postgres, ITestOutputHelper output) - : base(new RepositoryActivator(output, postgres)) { } - } - } - - public abstract class ALibraryItemTest - { - private readonly ILibraryItemRepository _repository; - private readonly RepositoryActivator _repositories; - - protected ALibraryItemTest(RepositoryActivator repositories) - { - _repositories = repositories; - _repository = repositories.LibraryManager.LibraryItemRepository; - } - - [Fact] - public async Task CountTest() - { - Assert.Equal(2, await _repository.GetCount()); - } - - [Fact] - public async Task GetShowTests() - { - LibraryItem expected = new(TestSample.Get()); - LibraryItem actual = await _repository.Get(1); - KAssert.DeepEqual(expected, actual); - } - - [Fact] - public async Task GetCollectionTests() - { - LibraryItem expected = new(TestSample.Get()); - LibraryItem actual = await _repository.Get(-1); - KAssert.DeepEqual(expected, actual); - } - - [Fact] - public async Task GetShowSlugTests() - { - LibraryItem expected = new(TestSample.Get()); - LibraryItem actual = await _repository.Get(TestSample.Get().Slug); - KAssert.DeepEqual(expected, actual); - } - - [Fact] - public async Task GetCollectionSlugTests() - { - LibraryItem expected = new(TestSample.Get()); - LibraryItem actual = await _repository.Get(TestSample.Get().Slug); - KAssert.DeepEqual(expected, actual); - } - - [Fact] - public async Task GetDuplicatedSlugTests() - { - await _repositories.LibraryManager.Create(new Collection - { - Slug = TestSample.Get().Slug, - Name = "name" - }); - await Assert.ThrowsAsync(() => _repository.Get(TestSample.Get().Slug)); - } - } -} diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs deleted file mode 100644 index 57a7deea..00000000 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs +++ /dev/null @@ -1,169 +0,0 @@ -// Kyoo - A portable and vast media library solution. -// Copyright (c) Kyoo. -// -// See AUTHORS.md and LICENSE file in the project root for full license information. -// -// Kyoo is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// Kyoo is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Kyoo. If not, see . - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Kyoo.Postgresql; -using Kyoo.Utils; -using Microsoft.EntityFrameworkCore; -using Xunit; -using Xunit.Abstractions; - -namespace Kyoo.Tests.Database -{ - namespace PostgreSQL - { - [Collection(nameof(Postgresql))] - public class LibraryTests : ALibraryTests - { - public LibraryTests(PostgresFixture postgres, ITestOutputHelper output) - : base(new RepositoryActivator(output, postgres)) { } - } - } - - public abstract class ALibraryTests : RepositoryTests - { - private readonly ILibraryRepository _repository; - - protected ALibraryTests(RepositoryActivator repositories) - : base(repositories) - { - _repository = Repositories.LibraryManager.LibraryRepository; - } - - [Fact] - public async Task CreateWithoutPathTest() - { - Library library = TestSample.GetNew(); - library.Paths = null; - await Assert.ThrowsAsync(() => _repository.Create(library)); - } - - [Fact] - public async Task CreateWithEmptySlugTest() - { - Library library = TestSample.GetNew(); - library.Slug = string.Empty; - await Assert.ThrowsAsync(() => _repository.Create(library)); - } - - [Fact] - public async Task CreateWithNumberSlugTest() - { - Library library = TestSample.GetNew(); - library.Slug = "2"; - Library ret = await _repository.Create(library); - Assert.Equal("2!", ret.Slug); - } - - [Fact] - public async Task CreateWithoutNameTest() - { - Library library = TestSample.GetNew(); - library.Name = null; - await Assert.ThrowsAsync(() => _repository.Create(library)); - } - - [Fact] - public async Task CreateWithProvider() - { - Library library = TestSample.GetNew(); - library.Providers = new[] { TestSample.Get() }; - await _repository.Create(library); - Library retrieved = await _repository.Get(2); - await Repositories.LibraryManager.Load(retrieved, x => x.Providers); - Assert.Single(retrieved.Providers); - Assert.Equal(TestSample.Get().Slug, retrieved.Providers.First().Slug); - } - - [Fact] - public async Task EditTest() - { - Library value = await _repository.Get(TestSample.Get().Slug); - value.Paths = new[] { "/super", "/test" }; - value.Name = "New Title"; - Library edited = await _repository.Edit(value, false); - - await using DatabaseContext database = Repositories.Context.New(); - Library show = await database.Libraries.FirstAsync(); - - KAssert.DeepEqual(show, edited); - } - - [Fact] - public async Task EditProvidersTest() - { - Library value = await _repository.Get(TestSample.Get().Slug); - value.Providers = new[] - { - TestSample.GetNew() - }; - Library edited = await _repository.Edit(value, false); - - await using DatabaseContext database = Repositories.Context.New(); - Library show = await database.Libraries - .Include(x => x.Providers) - .FirstAsync(); - - show.Providers.ForEach(x => x.Libraries = null); - edited.Providers.ForEach(x => x.Libraries = null); - KAssert.DeepEqual(show, edited); - } - - [Fact] - public async Task AddProvidersTest() - { - Library value = await _repository.Get(TestSample.Get().Slug); - await Repositories.LibraryManager.Load(value, x => x.Providers); - value.Providers.Add(TestSample.GetNew()); - Library edited = await _repository.Edit(value, false); - - await using DatabaseContext database = Repositories.Context.New(); - Library show = await database.Libraries - .Include(x => x.Providers) - .FirstAsync(); - - show.Providers.ForEach(x => x.Libraries = null); - edited.Providers.ForEach(x => x.Libraries = null); - KAssert.DeepEqual(show, edited); - } - - [Theory] - [InlineData("test")] - [InlineData("super")] - [InlineData("title")] - [InlineData("TiTlE")] - [InlineData("SuPeR")] - public async Task SearchTest(string query) - { - Library value = new() - { - Slug = "super-test", - Name = "This is a test title", - Paths = new[] { "path" } - }; - await _repository.Create(value); - ICollection ret = await _repository.Search(query); - KAssert.DeepEqual(value, ret.First()); - } - } -} diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs index e6b58242..8bf3b9da 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs @@ -52,17 +52,15 @@ namespace Kyoo.Tests.Database public async Task CreateWithExternalIdTest() { People value = TestSample.GetNew(); - value.ExternalId = new[] + value.ExternalId = new Dictionary { - new MetadataId + ["2"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, - new MetadataId + ["1"] = new() { - Provider = TestSample.GetNew(), Link = "new-provider-link", DataId = "new-id" } @@ -70,7 +68,6 @@ namespace Kyoo.Tests.Database await _repository.Create(value); People retrieved = await _repository.Get(2); - await Repositories.LibraryManager.Load(retrieved, x => x.ExternalId); Assert.Equal(2, retrieved.ExternalId.Count); KAssert.DeepEqual(value.ExternalId.First(), retrieved.ExternalId.First()); KAssert.DeepEqual(value.ExternalId.Last(), retrieved.ExternalId.Last()); @@ -81,11 +78,8 @@ namespace Kyoo.Tests.Database { People value = await _repository.Get(TestSample.Get().Slug); value.Name = "New Name"; - value.Images = new Dictionary - { - [Images.Poster] = "new-poster" - }; - await _repository.Edit(value, false); + value.Poster = new Image("poster"); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); People retrieved = await database.People.FirstAsync(); @@ -97,22 +91,18 @@ namespace Kyoo.Tests.Database public async Task EditMetadataTest() { People value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new[] + value.ExternalId = new Dictionary { - new MetadataId + ["toto"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); - People retrieved = await database.People - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + People retrieved = await database.People.FirstAsync(); KAssert.DeepEqual(value, retrieved); } @@ -121,41 +111,32 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { People value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new List + value.ExternalId = new Dictionary { - new() + ["1"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); - People retrieved = await database.People - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); - + People retrieved = await database.People.FirstAsync(); KAssert.DeepEqual(value, retrieved); } - value.ExternalId.Add(new MetadataId + value.ExternalId.Add("toto", new MetadataId { - Provider = TestSample.GetNew(), Link = "link", DataId = "id" }); - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); - People retrieved = await database.People - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + People retrieved = await database.People.FirstAsync(); KAssert.DeepEqual(value, retrieved); } diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/ProviderTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/ProviderTests.cs deleted file mode 100644 index 49340367..00000000 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/ProviderTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Kyoo - A portable and vast media library solution. -// Copyright (c) Kyoo. -// -// See AUTHORS.md and LICENSE file in the project root for full license information. -// -// Kyoo is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// Kyoo is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Kyoo. If not, see . - -using System.Diagnostics.CodeAnalysis; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Xunit; -using Xunit.Abstractions; - -namespace Kyoo.Tests.Database -{ - namespace PostgreSQL - { - [Collection(nameof(Postgresql))] - public class ProviderTests : AProviderTests - { - public ProviderTests(PostgresFixture postgres, ITestOutputHelper output) - : base(new RepositoryActivator(output, postgres)) { } - } - } - - public abstract class AProviderTests : RepositoryTests - { - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - private readonly IProviderRepository _repository; - - protected AProviderTests(RepositoryActivator repositories) - : base(repositories) - { - _repository = Repositories.LibraryManager.ProviderRepository; - } - } -} diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs index 886cbade..85a5c3d0 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs @@ -55,10 +55,10 @@ namespace Kyoo.Tests.Database Assert.Equal("anohana-s1", season.Slug); Show show = new() { - Id = season.ShowID, + Id = season.ShowId, Slug = "new-slug" }; - await Repositories.LibraryManager.ShowRepository.Edit(show, false); + await Repositories.LibraryManager.ShowRepository.Edit(show); season = await _repository.Get(1); Assert.Equal("new-slug-s1", season.Slug); } @@ -72,8 +72,8 @@ namespace Kyoo.Tests.Database { Id = 1, SeasonNumber = 2, - ShowID = 1 - }, false); + ShowId = 1 + }); season = await _repository.Get(1); Assert.Equal("anohana-s2", season.Slug); } @@ -83,7 +83,7 @@ namespace Kyoo.Tests.Database { Season season = await _repository.Create(new Season { - ShowID = TestSample.Get().Id, + ShowId = TestSample.Get().Id, SeasonNumber = 2 }); Assert.Equal($"{TestSample.Get().Slug}-s2", season.Slug); @@ -93,17 +93,15 @@ namespace Kyoo.Tests.Database public async Task CreateWithExternalIdTest() { Season season = TestSample.GetNew(); - season.ExternalId = new[] + season.ExternalId = new Dictionary { - new MetadataId + ["2"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, - new MetadataId + ["1"] = new() { - Provider = TestSample.GetNew(), Link = "new-provider-link", DataId = "new-id" } @@ -111,7 +109,6 @@ namespace Kyoo.Tests.Database await _repository.Create(season); Season retrieved = await _repository.Get(2); - await Repositories.LibraryManager.Load(retrieved, x => x.ExternalId); Assert.Equal(2, retrieved.ExternalId.Count); KAssert.DeepEqual(season.ExternalId.First(), retrieved.ExternalId.First()); KAssert.DeepEqual(season.ExternalId.Last(), retrieved.ExternalId.Last()); @@ -122,11 +119,8 @@ namespace Kyoo.Tests.Database { Season value = await _repository.Get(TestSample.Get().Slug); value.Name = "New Title"; - value.Images = new Dictionary - { - [Images.Poster] = "new-poster" - }; - await _repository.Edit(value, false); + value.Poster = new Image("test"); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); Season retrieved = await database.Seasons.FirstAsync(); @@ -138,22 +132,18 @@ namespace Kyoo.Tests.Database public async Task EditMetadataTest() { Season value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new[] + value.ExternalId = new Dictionary { - new MetadataId + ["toto"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); - Season retrieved = await database.Seasons - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Season retrieved = await database.Seasons.FirstAsync(); KAssert.DeepEqual(value, retrieved); } @@ -162,41 +152,33 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { Season value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new List + value.ExternalId = new Dictionary { - new() + ["1"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); - Season retrieved = await database.Seasons - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Season retrieved = await database.Seasons.FirstAsync(); KAssert.DeepEqual(value, retrieved); } - value.ExternalId.Add(new MetadataId + value.ExternalId.Add("toto", new MetadataId { - Provider = TestSample.GetNew(), Link = "link", DataId = "id" }); - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); - Season retrieved = await database.Seasons - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Season retrieved = await database.Seasons.FirstAsync(); KAssert.DeepEqual(value, retrieved); } @@ -213,7 +195,7 @@ namespace Kyoo.Tests.Database Season value = new() { Name = "This is a test super title", - ShowID = 1 + ShowId = 1 }; await _repository.Create(value); ICollection ret = await _repository.Search(query); diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index ac806a28..534e58b8 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -55,9 +54,8 @@ namespace Kyoo.Tests.Database public async Task EditTest() { Show value = await _repository.Get(TestSample.Get().Slug); - value.Path = "/super"; value.Name = "New Title"; - Show edited = await _repository.Edit(value, false); + Show edited = await _repository.Edit(value); KAssert.DeepEqual(value, edited); await using DatabaseContext database = Repositories.Context.New(); @@ -70,11 +68,11 @@ namespace Kyoo.Tests.Database public async Task EditGenreTest() { Show value = await _repository.Get(TestSample.Get().Slug); - value.Genres = new[] { new Genre("test") }; - Show edited = await _repository.Edit(value, false); + value.Genres = new List { Genre.Action }; + Show edited = await _repository.Edit(value); Assert.Equal(value.Slug, edited.Slug); - Assert.Equal(value.Genres.Select(x => new { x.Slug, x.Name }), edited.Genres.Select(x => new { x.Slug, x.Name })); + Assert.Equal(value.Genres, edited.Genres); await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows @@ -82,19 +80,18 @@ namespace Kyoo.Tests.Database .FirstAsync(); Assert.Equal(value.Slug, show.Slug); - Assert.Equal(value.Genres.Select(x => new { x.Slug, x.Name }), show.Genres.Select(x => new { x.Slug, x.Name })); + Assert.Equal(value.Genres, show.Genres); } [Fact] public async Task AddGenreTest() { Show value = await _repository.Get(TestSample.Get().Slug); - await Repositories.LibraryManager.Load(value, x => x.Genres); - value.Genres.Add(new Genre("test")); - Show edited = await _repository.Edit(value, false); + value.Genres.Add(Genre.Drama); + Show edited = await _repository.Edit(value); Assert.Equal(value.Slug, edited.Slug); - Assert.Equal(value.Genres.Select(x => new { x.Slug, x.Name }), edited.Genres.Select(x => new { x.Slug, x.Name })); + Assert.Equal(value.Genres, edited.Genres); await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows @@ -102,7 +99,7 @@ namespace Kyoo.Tests.Database .FirstAsync(); Assert.Equal(value.Slug, show.Slug); - Assert.Equal(value.Genres.Select(x => new { x.Slug, x.Name }), show.Genres.Select(x => new { x.Slug, x.Name })); + Assert.Equal(value.Genres, show.Genres); } [Fact] @@ -110,10 +107,10 @@ namespace Kyoo.Tests.Database { Show value = await _repository.Get(TestSample.Get().Slug); value.Studio = new Studio("studio"); - Show edited = await _repository.Edit(value, false); + Show edited = await _repository.Edit(value); Assert.Equal(value.Slug, edited.Slug); - Assert.Equal("studio", edited.Studio.Slug); + Assert.Equal("studio", edited.Studio!.Slug); await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows @@ -121,15 +118,15 @@ namespace Kyoo.Tests.Database .FirstAsync(); Assert.Equal(value.Slug, show.Slug); - Assert.Equal("studio", show.Studio.Slug); + Assert.Equal("studio", show.Studio!.Slug); } [Fact] public async Task EditAliasesTest() { Show value = await _repository.Get(TestSample.Get().Slug); - value.Aliases = new[] { "NiceNewAlias", "SecondAlias" }; - Show edited = await _repository.Edit(value, false); + value.Aliases = new List() { "NiceNewAlias", "SecondAlias" }; + Show edited = await _repository.Edit(value); Assert.Equal(value.Slug, edited.Slug); Assert.Equal(value.Aliases, edited.Aliases); @@ -156,10 +153,10 @@ namespace Kyoo.Tests.Database Role = "NiceCharacter" } }; - Show edited = await _repository.Edit(value, false); + Show edited = await _repository.Edit(value); Assert.Equal(value.Slug, edited.Slug); - Assert.Equal(edited.People.First().ShowID, value.Id); + Assert.Equal(edited.People!.First().ShowID, value.Id); Assert.Equal( value.People.Select(x => new { x.Role, x.Slug, x.People.Name }), edited.People.Select(x => new { x.Role, x.Slug, x.People.Name })); @@ -173,62 +170,30 @@ namespace Kyoo.Tests.Database Assert.Equal(value.Slug, show.Slug); Assert.Equal( value.People.Select(x => new { x.Role, x.Slug, x.People.Name }), - show.People.Select(x => new { x.Role, x.Slug, x.People.Name })); + show.People!.Select(x => new { x.Role, x.Slug, x.People.Name })); } [Fact] public async Task EditExternalIDsTest() { Show value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new[] + value.ExternalId = new Dictionary() { - new MetadataId + ["test"] = new() { - Provider = new Provider("test", "test.png"), DataId = "1234" } }; - Show edited = await _repository.Edit(value, false); + Show edited = await _repository.Edit(value); Assert.Equal(value.Slug, edited.Slug); - Assert.Equal( - value.ExternalId.Select(x => new { x.DataID, x.Provider.Slug }), - edited.ExternalId.Select(x => new { x.DataID, x.Provider.Slug })); + Assert.Equal(value.ExternalId, edited.ExternalId); await using DatabaseContext database = Repositories.Context.New(); - Show show = await database.Shows - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Show show = await database.Shows.FirstAsync(); Assert.Equal(value.Slug, show.Slug); - Assert.Equal( - value.ExternalId.Select(x => new { x.DataID, x.Provider.Slug }), - show.ExternalId.Select(x => new { x.DataID, x.Provider.Slug })); - } - - [Fact] - public async Task EditResetOldTest() - { - Show value = await _repository.Get(TestSample.Get().Slug); - Show newValue = new() - { - Id = value.Id, - Slug = "reset", - Name = "Reset" - }; - - Show edited = await _repository.Edit(newValue, true); - - Assert.Equal(value.Id, edited.Id); - Assert.Null(edited.Overview); - Assert.Equal("reset", edited.Slug); - Assert.Equal("Reset", edited.Name); - Assert.Null(edited.Aliases); - Assert.Null(edited.ExternalId); - Assert.Null(edited.People); - Assert.Null(edited.Genres); - Assert.Null(edited.Studio); + Assert.Equal(value.ExternalId, show.ExternalId); } [Fact] @@ -237,22 +202,14 @@ namespace Kyoo.Tests.Database Show expected = TestSample.Get(); expected.Id = 0; expected.Slug = "created-relation-test"; - expected.ExternalId = new[] + expected.ExternalId = new Dictionary { - new MetadataId + ["test"] = new() { - Provider = new Provider("provider", "provider.png"), DataId = "ID" } }; - expected.Genres = new[] - { - new Genre - { - Name = "Genre", - Slug = "genre" - } - }; + expected.Genres = new List() { Genre.Action }; expected.People = new[] { new PeopleRole @@ -270,7 +227,6 @@ namespace Kyoo.Tests.Database await using DatabaseContext context = Repositories.Context.New(); Show retrieved = await context.Shows .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) .Include(x => x.Genres) .Include(x => x.People) .ThenInclude(x => x.People) @@ -281,10 +237,7 @@ namespace Kyoo.Tests.Database x.Show = null; x.People.Roles = null; }); - retrieved.Studio.Shows = null; - retrieved.Genres.ForEach(x => x.Shows = null); - - expected.Genres.ForEach(x => x.Shows = null); + retrieved.Studio!.Shows = null; expected.People.ForEach(x => { x.Show = null; @@ -300,11 +253,10 @@ namespace Kyoo.Tests.Database Show expected = TestSample.Get(); expected.Id = 0; expected.Slug = "created-relation-test"; - expected.ExternalId = new[] + expected.ExternalId = new Dictionary { - new MetadataId + ["test"] = new() { - Provider = TestSample.Get(), DataId = "ID" } }; @@ -313,11 +265,10 @@ namespace Kyoo.Tests.Database await using DatabaseContext context = Repositories.Context.New(); Show retrieved = await context.Shows .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) .FirstAsync(x => x.Id == created.Id); KAssert.DeepEqual(expected, retrieved); Assert.Single(retrieved.ExternalId); - Assert.Equal("ID", retrieved.ExternalId.First().DataID); + Assert.Equal("ID", retrieved.ExternalId["test"].DataId); } [Fact] @@ -362,25 +313,12 @@ namespace Kyoo.Tests.Database await Repositories.LibraryManager.Load(show, x => x.Seasons); await Repositories.LibraryManager.Load(show, x => x.Episodes); Assert.Equal(1, await _repository.GetCount()); - Assert.Single(show.Seasons); - Assert.Single(show.Episodes); + Assert.Single(show.Seasons!); + Assert.Single(show.Episodes!); await _repository.Delete(show); Assert.Equal(0, await Repositories.LibraryManager.ShowRepository.GetCount()); Assert.Equal(0, await Repositories.LibraryManager.SeasonRepository.GetCount()); Assert.Equal(0, await Repositories.LibraryManager.EpisodeRepository.GetCount()); } - - [Fact] - public async Task AddShowLinkTest() - { - await Repositories.LibraryManager.Create(TestSample.GetNew()); - await _repository.AddShowLink(1, 2, null); - - await using DatabaseContext context = Repositories.Context.New(); - Show show = context.Shows - .Include(x => x.Libraries) - .First(x => x.ID == 1); - Assert.Contains(2, show.Libraries.Select(x => x.ID)); - } } } diff --git a/back/tests/Kyoo.Tests/Database/TestSample.cs b/back/tests/Kyoo.Tests/Database/TestSample.cs index 380830e0..889bb234 100644 --- a/back/tests/Kyoo.Tests/Database/TestSample.cs +++ b/back/tests/Kyoo.Tests/Database/TestSample.cs @@ -35,10 +35,7 @@ namespace Kyoo.Tests Slug = "new-collection", Name = "New Collection", Overview = "A collection created by new sample", - Images = new Dictionary - { - [Images.Thumbnail] = "thumbnail" - } + Thumbnail = new Image("thumbnail") } }, { @@ -52,13 +49,9 @@ namespace Kyoo.Tests Status = Status.Planned, StartAir = new DateTime(2011, 1, 1).ToUniversalTime(), EndAir = new DateTime(2011, 1, 1).ToUniversalTime(), - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, - IsMovie = false, + Poster = new Image("Poster"), + Logo = new Image("Logo"), + Thumbnail = new Image("Thumbnail"), Studio = null } }, @@ -67,17 +60,14 @@ namespace Kyoo.Tests () => new Season { Id = 2, - ShowID = 1, + ShowId = 1, ShowSlug = Get().Slug, Name = "New season", Overview = "New overview", EndDate = new DateTime(2000, 10, 10).ToUniversalTime(), SeasonNumber = 2, StartDate = new DateTime(2010, 10, 10).ToUniversalTime(), - Images = new Dictionary - { - [Images.Logo] = "logo" - } + Logo = new Image("logo") } }, { @@ -85,9 +75,9 @@ namespace Kyoo.Tests () => new Episode { Id = 2, - ShowID = 1, + ShowId = 1, ShowSlug = Get().Slug, - SeasonID = 1, + SeasonId = 1, SeasonNumber = Get().SeasonNumber, EpisodeNumber = 3, AbsoluteNumber = 4, @@ -95,23 +85,7 @@ namespace Kyoo.Tests Name = "New Episode Title", ReleaseDate = new DateTime(2000, 10, 10).ToUniversalTime(), Overview = "new episode overview", - Images = new Dictionary - { - [Images.Logo] = "new episode logo" - } - } - }, - { - typeof(Provider), - () => new Provider - { - ID = 2, - Slug = "new-provider", - Name = "Provider NewSample", - Images = new Dictionary - { - [Images.Logo] = "logo" - } + Logo = new Image("new episode logo") } }, { @@ -121,27 +95,14 @@ namespace Kyoo.Tests Id = 2, Slug = "new-person-name", Name = "New person name", - Images = new Dictionary - { - [Images.Logo] = "Old Logo", - [Images.Poster] = "Old poster" - } + Logo = new Image("Old Logo"), + Poster = new Image("Old poster") } } }; private static readonly Dictionary> Samples = new() { - { - typeof(Library), - () => new Library - { - ID = 1, - Slug = "deck", - Name = "Deck", - Paths = new[] { "/path/to/deck" } - } - }, { typeof(Collection), () => new Collection @@ -150,10 +111,7 @@ namespace Kyoo.Tests Slug = "collection", Name = "Collection", Overview = "A nice collection for tests", - Images = new Dictionary - { - [Images.Poster] = "Poster" - } + Poster = new Image("Poster") } }, { @@ -163,7 +121,7 @@ namespace Kyoo.Tests Id = 1, Slug = "anohana", Name = "Anohana: The Flower We Saw That Day", - Aliases = new[] + Aliases = new List { "Ano Hi Mita Hana no Namae o Bokutachi wa Mada Shiranai.", "AnoHana", @@ -173,16 +131,12 @@ namespace Kyoo.Tests "In time, however, these childhood friends drifted apart, and when they became high " + "school students, they had long ceased to think of each other as friends.", Status = Status.Finished, - StudioID = 1, + StudioId = 1, StartAir = new DateTime(2011, 1, 1).ToUniversalTime(), EndAir = new DateTime(2011, 1, 1).ToUniversalTime(), - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, - IsMovie = false, + Poster = new Image("Poster"), + Logo = new Image("Logo"), + Thumbnail = new Image("Thumbnail"), Studio = null } }, @@ -192,18 +146,15 @@ namespace Kyoo.Tests { Id = 1, ShowSlug = "anohana", - ShowID = 1, + ShowId = 1, SeasonNumber = 1, Name = "Season 1", Overview = "The first season", StartDate = new DateTime(2020, 06, 05).ToUniversalTime(), EndDate = new DateTime(2020, 07, 05).ToUniversalTime(), - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, + Poster = new Image("Poster"), + Logo = new Image("Logo"), + Thumbnail = new Image("Thumbnail") } }, { @@ -212,18 +163,15 @@ namespace Kyoo.Tests { Id = 1, ShowSlug = "anohana", - ShowID = 1, - SeasonID = 1, + ShowId = 1, + SeasonId = 1, SeasonNumber = 1, EpisodeNumber = 1, AbsoluteNumber = 1, Path = "/home/kyoo/anohana-s1e1", - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, + Poster = new Image("Poster"), + Logo = new Image("Logo"), + Thumbnail = new Image("Thumbnail"), Name = "Episode 1", Overview = "Summary of the first episode", ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime() @@ -236,12 +184,9 @@ namespace Kyoo.Tests Id = 1, Slug = "the-actor", Name = "The Actor", - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, + Poster = new Image("Poster"), + Logo = new Image("Logo"), + Thumbnail = new Image("Thumbnail") } }, { @@ -253,30 +198,6 @@ namespace Kyoo.Tests Name = "Hyper studio", } }, - { - typeof(Genre), - () => new Genre - { - ID = 1, - Slug = "action", - Name = "Action" - } - }, - { - typeof(Provider), - () => new Provider - { - ID = 1, - Slug = "tvdb", - Name = "The TVDB", - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "path/tvdb.svg", - [Images.Thumbnail] = "Thumbnail" - } - } - }, { typeof(User), () => new User @@ -309,20 +230,20 @@ namespace Kyoo.Tests Show show = Get(); show.Id = 0; - show.StudioID = 0; + show.StudioId = 0; context.Shows.Add(show); Season season = Get(); season.Id = 0; - season.ShowID = 0; + season.ShowId = 0; season.Show = show; context.Seasons.Add(season); Episode episode = Get(); episode.Id = 0; - episode.ShowID = 0; + episode.ShowId = 0; episode.Show = show; - episode.SeasonID = 0; + episode.SeasonId = 0; episode.Season = season; context.Episodes.Add(episode); @@ -331,20 +252,10 @@ namespace Kyoo.Tests studio.Shows = new List { show }; context.Studios.Add(studio); - Genre genre = Get(); - genre.ID = 0; - genre.Shows = new List { show }; - context.Genres.Add(genre); - People people = Get(); people.Id = 0; context.People.Add(people); - Library library = Get(); - library.ID = 0; - library.Collections = new List { collection }; - context.Libraries.Add(library); - User user = Get(); user.Id = 0; context.Users.Add(user); @@ -358,41 +269,18 @@ namespace Kyoo.Tests { Id = 2, ShowSlug = "anohana", - ShowID = 1, + ShowId = 1, SeasonNumber = null, EpisodeNumber = null, AbsoluteNumber = 3, Path = "/home/kyoo/anohana-3", - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, + Poster = new Image("Poster"), + Logo = new Image("Logo"), + Thumbnail = new Image("Thumbnail"), Name = "Episode 3", Overview = "Summary of the third absolute episode", ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime() }; } - - public static Episode GetMovieEpisode() - { - return new() - { - Id = 3, - ShowSlug = "anohana", - ShowID = 1, - Path = "/home/kyoo/john-wick", - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, - Name = "John wick", - Overview = "A movie episode test", - ReleaseDate = new DateTime(1595, 05, 12).ToUniversalTime() - }; - } } } diff --git a/back/tests/Kyoo.Tests/Utility/EnumerableTests.cs b/back/tests/Kyoo.Tests/Utility/EnumerableTests.cs index b2a444ad..02184acb 100644 --- a/back/tests/Kyoo.Tests/Utility/EnumerableTests.cs +++ b/back/tests/Kyoo.Tests/Utility/EnumerableTests.cs @@ -17,9 +17,7 @@ // along with Kyoo. If not, see . using System; -using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Kyoo.Utils; using Xunit; @@ -27,53 +25,6 @@ namespace Kyoo.Tests.Utility { public class EnumerableTests { - [Fact] - public void MapTest() - { - int[] list = { 1, 2, 3, 4 }; - Assert.All(list.Map((x, i) => (x, i)), x => Assert.Equal(x.x - 1, x.i)); - Assert.Throws(() => list.Map(((Func)null)!)); - list = null; - Assert.Throws(() => list!.Map((x, _) => x + 1)); - } - - [Fact] - public async Task MapAsyncTest() - { - int[] list = { 1, 2, 3, 4 }; - await foreach ((int x, int i) in list.MapAsync((x, i) => Task.FromResult((x, i)))) - { - Assert.Equal(x - 1, i); - } - Assert.Throws(() => list.MapAsync(((Func>)null)!)); - list = null; - Assert.Throws(() => list!.MapAsync((x, _) => Task.FromResult(x + 1))); - } - - [Fact] - public async Task SelectAsyncTest() - { - int[] list = { 1, 2, 3, 4 }; - int i = 2; - await foreach (int x in list.SelectAsync(x => Task.FromResult(x + 1))) - { - Assert.Equal(i++, x); - } - Assert.Throws(() => list.SelectAsync(((Func>)null)!)); - list = null; - Assert.Throws(() => list!.SelectAsync(x => Task.FromResult(x + 1))); - } - - [Fact] - public async Task ToListAsyncTest() - { - int[] expected = { 1, 2, 3, 4 }; - IAsyncEnumerable list = expected.SelectAsync(Task.FromResult); - Assert.Equal(expected, await list.ToListAsync()); - list = null; - await Assert.ThrowsAsync(() => list!.ToListAsync()); - } - [Fact] public void IfEmptyTest() { diff --git a/back/tests/Kyoo.Tests/Utility/MergerTests.cs b/back/tests/Kyoo.Tests/Utility/MergerTests.cs index a6528875..f8610d57 100644 --- a/back/tests/Kyoo.Tests/Utility/MergerTests.cs +++ b/back/tests/Kyoo.Tests/Utility/MergerTests.cs @@ -16,13 +16,9 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; using JetBrains.Annotations; using Kyoo.Abstractions.Models; -using Kyoo.Abstractions.Models.Attributes; using Kyoo.Utils; using Xunit; @@ -30,318 +26,21 @@ namespace Kyoo.Tests.Utility { public class MergerTests { - [Fact] - public void NullifyTest() - { - Genre genre = new("test") - { - ID = 5 - }; - Merger.Nullify(genre); - Assert.Equal(0, genre.ID); - Assert.Null(genre.Name); - Assert.Null(genre.Slug); - } - - [Fact] - public void MergeTest() - { - Genre genre = new() - { - ID = 5 - }; - Genre genre2 = new() - { - Name = "test" - }; - Genre ret = Merger.Merge(genre, genre2); - Assert.True(ReferenceEquals(genre, ret)); - Assert.Equal(5, ret.ID); - Assert.Equal("test", genre.Name); - Assert.Null(genre.Slug); - } - - [Fact] - [SuppressMessage("ReSharper", "ExpressionIsAlwaysNull")] - public void MergeNullTests() - { - Genre genre = new() - { - ID = 5 - }; - Assert.True(ReferenceEquals(genre, Merger.Merge(genre, null))); - Assert.True(ReferenceEquals(genre, Merger.Merge(null, genre))); - Assert.Null(Merger.Merge(null, null)); - } - - private class TestIOnMerge : IOnMerge - { - public void OnMerge(object other) - { - Exception exception = new(); - exception.Data[0] = other; - throw exception; - } - } - - [Fact] - public void OnMergeTest() - { - TestIOnMerge test = new(); - TestIOnMerge test2 = new(); - Assert.Throws(() => Merger.Merge(test, test2)); - try - { - Merger.Merge(test, test2); - } - catch (Exception ex) - { - Assert.True(ReferenceEquals(test2, ex.Data[0])); - } - } - - private class Test - { - public int ID { get; set; } - - public int[] Numbers { get; set; } - } - - [Fact] - public void GlobalMergeListTest() - { - Test test = new() - { - ID = 5, - Numbers = new[] { 1 } - }; - Test test2 = new() - { - Numbers = new[] { 3 } - }; - Test ret = Merger.Merge(test, test2); - Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.ID); - - Assert.Equal(2, ret.Numbers.Length); - Assert.Equal(1, ret.Numbers[0]); - Assert.Equal(3, ret.Numbers[1]); - } - - [Fact] - public void GlobalMergeListDuplicatesTest() - { - Test test = new() - { - ID = 5, - Numbers = new[] { 1 } - }; - Test test2 = new() - { - Numbers = new[] - { - 1, - 3, - 3 - } - }; - Test ret = Merger.Merge(test, test2); - Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.ID); - - Assert.Equal(4, ret.Numbers.Length); - Assert.Equal(1, ret.Numbers[0]); - Assert.Equal(1, ret.Numbers[1]); - Assert.Equal(3, ret.Numbers[2]); - Assert.Equal(3, ret.Numbers[3]); - } - - private class MergeDictionaryTest - { - public int ID { get; set; } - - public Dictionary Dictionary { get; set; } - } - - [Fact] - public void GlobalMergeDictionariesTest() - { - MergeDictionaryTest test = new() - { - ID = 5, - Dictionary = new Dictionary - { - [2] = "two" - } - }; - MergeDictionaryTest test2 = new() - { - Dictionary = new Dictionary - { - [3] = "third" - } - }; - MergeDictionaryTest ret = Merger.Merge(test, test2); - Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.ID); - - Assert.Equal(2, ret.Dictionary.Count); - Assert.Equal("two", ret.Dictionary[2]); - Assert.Equal("third", ret.Dictionary[3]); - } - - [Fact] - public void GlobalMergeDictionariesDuplicatesTest() - { - MergeDictionaryTest test = new() - { - ID = 5, - Dictionary = new Dictionary - { - [2] = "two" - } - }; - MergeDictionaryTest test2 = new() - { - Dictionary = new Dictionary - { - [2] = "nope", - [3] = "third" - } - }; - MergeDictionaryTest ret = Merger.Merge(test, test2); - Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.ID); - - Assert.Equal(2, ret.Dictionary.Count); - Assert.Equal("two", ret.Dictionary[2]); - Assert.Equal("third", ret.Dictionary[3]); - } - - [Fact] - public void GlobalMergeListDuplicatesResourcesTest() - { - Show test = new() - { - Id = 5, - Genres = new[] { new Genre("test") } - }; - Show test2 = new() - { - Genres = new[] - { - new Genre("test"), - new Genre("test2") - } - }; - Show ret = Merger.Merge(test, test2); - Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.Id); - - Assert.Equal(2, ret.Genres.Count); - Assert.Equal("test", ret.Genres.ToArray()[0].Slug); - Assert.Equal("test2", ret.Genres.ToArray()[1].Slug); - } - - [Fact] - public void MergeListTest() - { - int[] first = { 1 }; - int[] second = { 3, 3 }; - int[] ret = Merger.MergeLists(first, second); - - Assert.Equal(3, ret.Length); - Assert.Equal(1, ret[0]); - Assert.Equal(3, ret[1]); - Assert.Equal(3, ret[2]); - } - - [Fact] - public void MergeListDuplicateTest() - { - int[] first = { 1 }; - int[] second = { - 1, - 3, - 3 - }; - int[] ret = Merger.MergeLists(first, second); - - Assert.Equal(4, ret.Length); - Assert.Equal(1, ret[0]); - Assert.Equal(1, ret[1]); - Assert.Equal(3, ret[2]); - Assert.Equal(3, ret[3]); - } - - [Fact] - public void MergeListDuplicateCustomEqualityTest() - { - int[] first = { 1 }; - int[] second = { 3, 2 }; - int[] ret = Merger.MergeLists(first, second, (x, y) => x % 2 == y % 2); - - Assert.Equal(2, ret.Length); - Assert.Equal(1, ret[0]); - Assert.Equal(2, ret[1]); - } - - [Fact] - public void MergeDictionariesTest() - { - Dictionary first = new() - { - [1] = "test", - [5] = "value" - }; - Dictionary second = new() - { - [3] = "third", - }; - IDictionary ret = Merger.MergeDictionaries(first, second); - - Assert.Equal(3, ret.Count); - Assert.Equal("test", ret[1]); - Assert.Equal("value", ret[5]); - Assert.Equal("third", ret[3]); - } - - [Fact] - public void MergeDictionariesDuplicateTest() - { - Dictionary first = new() - { - [1] = "test", - [5] = "value" - }; - Dictionary second = new() - { - [3] = "third", - [5] = "new-value", - }; - IDictionary ret = Merger.MergeDictionaries(first, second); - - Assert.Equal(3, ret.Count); - Assert.Equal("test", ret[1]); - Assert.Equal("value", ret[5]); - Assert.Equal("third", ret[3]); - } - [Fact] public void CompleteTest() { - Genre genre = new() + Studio genre = new() { - ID = 5, + Id = 5, Name = "merged" }; - Genre genre2 = new() + Studio genre2 = new() { Name = "test" }; - Genre ret = Merger.Complete(genre, genre2); + Studio ret = Merger.Complete(genre, genre2); Assert.True(ReferenceEquals(genre, ret)); - Assert.Equal(5, ret.ID); + Assert.Equal(5, ret.Id); Assert.Equal("test", genre.Name); Assert.Null(genre.Slug); } @@ -437,64 +136,6 @@ namespace Kyoo.Tests.Utility // This should no call the setter of first so the test should pass. } - [Fact] - public void MergeDictionaryNoChangeNoSetTest() - { - TestMergeSetter first = new() - { - Backing = new Dictionary - { - [2] = 3 - } - }; - TestMergeSetter second = new() - { - Backing = new Dictionary() - }; - Merger.Merge(first, second); - // This should no call the setter of first so the test should pass. - } - - [Fact] - public void MergeDictionaryNullValue() - { - Dictionary first = new() - { - ["logo"] = "logo", - ["poster"] = null - }; - Dictionary second = new() - { - ["poster"] = "new-poster", - ["thumbnail"] = "thumbnails" - }; - IDictionary ret = Merger.MergeDictionaries(first, second, out bool changed); - Assert.True(changed); - Assert.Equal(3, ret.Count); - Assert.Equal("new-poster", ret["poster"]); - Assert.Equal("thumbnails", ret["thumbnail"]); - Assert.Equal("logo", ret["logo"]); - } - - [Fact] - public void MergeDictionaryNullValueNoChange() - { - Dictionary first = new() - { - ["logo"] = "logo", - ["poster"] = null - }; - Dictionary second = new() - { - ["poster"] = null, - }; - IDictionary ret = Merger.MergeDictionaries(first, second, out bool changed); - Assert.False(changed); - Assert.Equal(2, ret.Count); - Assert.Null(ret["poster"]); - Assert.Equal("logo", ret["logo"]); - } - [Fact] public void CompleteDictionaryNullValue() { diff --git a/back/tests/Kyoo.Tests/Utility/TaskTests.cs b/back/tests/Kyoo.Tests/Utility/TaskTests.cs index e00ca321..7151871a 100644 --- a/back/tests/Kyoo.Tests/Utility/TaskTests.cs +++ b/back/tests/Kyoo.Tests/Utility/TaskTests.cs @@ -26,13 +26,6 @@ namespace Kyoo.Tests.Utility { public class TaskTests { - [Fact] - public async Task DefaultIfNullTest() - { - Assert.Equal(0, await TaskUtils.DefaultIfNull(null)); - Assert.Equal(1, await TaskUtils.DefaultIfNull(Task.FromResult(1))); - } - [Fact] public async Task ThenTest() { @@ -59,37 +52,5 @@ namespace Kyoo.Tests.Utility await Assert.ThrowsAsync(() => Task.Run(Infinite, token.Token) .Then(_ => { })); } - - [Fact] - public async Task MapTest() - { - await Assert.ThrowsAsync(() => Task.FromResult(1) - .Map(_ => throw new ArgumentException())); - Assert.Equal(2, await Task.FromResult(1) - .Map(x => x + 1)); - - static async Task Faulted() - { - await Task.Delay(1); - throw new ArgumentException(); - } - await Assert.ThrowsAsync(() => Faulted() - .Map(x => - { - KAssert.Fail(); - return x; - })); - - static async Task Infinite() - { - await Task.Delay(100000); - return 1; - } - - CancellationTokenSource token = new(); - token.Cancel(); - await Assert.ThrowsAsync(() => Task.Run(Infinite, token.Token) - .Map(x => x)); - } } } diff --git a/back/tests/Kyoo.Tests/Utility/UtilityTests.cs b/back/tests/Kyoo.Tests/Utility/UtilityTests.cs index f7de596e..944c6571 100644 --- a/back/tests/Kyoo.Tests/Utility/UtilityTests.cs +++ b/back/tests/Kyoo.Tests/Utility/UtilityTests.cs @@ -20,7 +20,6 @@ using System; using System.Linq.Expressions; using System.Reflection; using Kyoo.Abstractions.Models; -using Kyoo.Utils; using Xunit; using KUtility = Kyoo.Utils.Utility; @@ -35,7 +34,6 @@ namespace Kyoo.Tests.Utility Expression> member = x => x.Id; Expression> memberCast = x => x.Id; - Assert.False(KUtility.IsPropertyExpression(null)); Assert.True(KUtility.IsPropertyExpression(member)); Assert.True(KUtility.IsPropertyExpression(memberCast)); @@ -51,7 +49,6 @@ namespace Kyoo.Tests.Utility Assert.Equal("ID", KUtility.GetPropertyName(member)); Assert.Equal("ID", KUtility.GetPropertyName(memberCast)); - Assert.Throws(() => KUtility.GetPropertyName(null)); } [Fact] @@ -84,16 +81,5 @@ namespace Kyoo.Tests.Utility Array.Empty(), new object[] { this })); } - - [Fact] - public void GetMethodTest2() - { - MethodInfo method = KUtility.GetMethod(typeof(Merger), - BindingFlags.Static | BindingFlags.Public, - nameof(Merger.MergeLists), - new[] { typeof(string) }, - new object[] { "string", "string2", null }); - Assert.Equal(nameof(Merger.MergeLists), method.Name); - } } }