Fix tests compilation errors

This commit is contained in:
Zoe Roux 2023-08-07 14:49:42 +09:00
parent 93b36f1bd4
commit 5446dbce83
No known key found for this signature in database
36 changed files with 326 additions and 1840 deletions

View File

@ -389,11 +389,23 @@ namespace Kyoo.Abstractions.Controllers
/// Edit a resource /// Edit a resource
/// </summary> /// </summary>
/// <param name="item">The resource to edit, it's ID can't change.</param> /// <param name="item">The resource to edit, it's ID can't change.</param>
/// <param name="resetOld">Should old properties of the resource be discarded or should null values considered as not changed?</param>
/// <typeparam name="T">The type of resources</typeparam> /// <typeparam name="T">The type of resources</typeparam>
/// <exception cref="ItemNotFoundException">If the item is not found</exception> /// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The resource edited and completed by database's information (related items and so on)</returns> /// <returns>The resource edited and completed by database's information (related items and so on)</returns>
Task<T> Edit<T>(T item, bool resetOld) Task<T> Edit<T>(T item)
where T : class, IResource;
/// <summary>
/// Edit only specific properties of a resource
/// </summary>
/// <param name="id">The id of the resource to edit</param>
/// <param name="patch">
/// 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
/// </param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The resource edited and completed by database's information (related items and so on)</returns>
Task<T> Patch<T>(int id, Func<T, Task<bool>> patch)
where T : class, IResource; where T : class, IResource;
/// <summary> /// <summary>

View File

@ -129,13 +129,24 @@ namespace Kyoo.Abstractions.Controllers
event ResourceEventHandler OnCreated; event ResourceEventHandler OnCreated;
/// <summary> /// <summary>
/// Edit a resource /// Edit a resource and replace every property
/// </summary> /// </summary>
/// <param name="edited">The resource to edit, it's ID can't change.</param> /// <param name="edited">The resource to edit, it's ID can't change.</param>
/// <param name="resetOld">Should old properties of the resource be discarded or should null values considered as not changed?</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception> /// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The resource edited and completed by database's information (related items and so on)</returns> /// <returns>The resource edited and completed by database's information (related items and so on)</returns>
Task<T> Edit(T edited, bool resetOld); Task<T> Edit(T edited);
/// <summary>
/// Edit only specific properties of a resource
/// </summary>
/// <param name="id">The id of the resource to edit</param>
/// <param name="patch">
/// 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
/// </param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The resource edited and completed by database's information (related items and so on)</returns>
Task<T> Patch(int id, Func<T, Task<bool>> patch);
/// <summary> /// <summary>
/// Called when a resource has been edited. /// Called when a resource has been edited.

View File

@ -16,33 +16,11 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Diagnostics.CodeAnalysis; namespace Kyoo.Models;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Xunit;
using Xunit.Abstractions;
namespace Kyoo.Tests.Database public class PartialResource
{ {
namespace PostgreSQL public int? Id { get; set; }
{
[Collection(nameof(Postgresql))]
public class GenreTests : AGenreTests
{
public GenreTests(PostgresFixture postgres, ITestOutputHelper output)
: base(new RepositoryActivator(output, postgres)) { }
}
}
public abstract class AGenreTests : RepositoryTests<Genre> public string? Slug { get; set; }
{
[SuppressMessage("ReSharper", "NotAccessedField.Local")]
private readonly IGenreRepository _repository;
protected AGenreTests(RepositoryActivator repositories)
: base(repositories)
{
_repository = Repositories.LibraryManager.GenreRepository;
}
}
} }

View File

@ -43,7 +43,7 @@ namespace Kyoo.Abstractions.Models
{ {
if (ShowSlug != null || Show?.Slug != null) if (ShowSlug != null || Show?.Slug != null)
return GetSlug(ShowSlug ?? Show.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber); return GetSlug(ShowSlug ?? Show.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber);
return GetSlug(ShowID.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber); return GetSlug(ShowId.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber);
} }
[UsedImplicitly] [UsedImplicitly]
@ -81,17 +81,17 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// The ID of the Show containing this episode. /// The ID of the Show containing this episode.
/// </summary> /// </summary>
[SerializeIgnore] public int ShowID { get; set; } [SerializeIgnore] public int ShowId { get; set; }
/// <summary> /// <summary>
/// The show that contains this episode. This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>. /// The show that contains this episode. This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
/// </summary> /// </summary>
[LoadableRelation(nameof(ShowID))] public Show? Show { get; set; } [LoadableRelation(nameof(ShowId))] public Show? Show { get; set; }
/// <summary> /// <summary>
/// The ID of the Season containing this episode. /// The ID of the Season containing this episode.
/// </summary> /// </summary>
[SerializeIgnore] public int? SeasonID { get; set; } [SerializeIgnore] public int? SeasonId { get; set; }
/// <summary> /// <summary>
/// The season that contains this episode. /// 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 /// This can be null if the season is unknown and the episode is only identified
/// by it's <see cref="AbsoluteNumber"/>. /// by it's <see cref="AbsoluteNumber"/>.
/// </remarks> /// </remarks>
[LoadableRelation(nameof(SeasonID))] public Season? Season { get; set; } [LoadableRelation(nameof(SeasonId))] public Season? Season { get; set; }
/// <summary> /// <summary>
/// The season in witch this episode is in. /// The season in witch this episode is in.

View File

@ -42,7 +42,7 @@ namespace Kyoo.Abstractions.Models
get get
{ {
if (ShowSlug == null && Show == null) if (ShowSlug == null && Show == null)
return $"{ShowID}-s{SeasonNumber}"; return $"{ShowId}-s{SeasonNumber}";
return $"{ShowSlug ?? Show?.Slug}-s{SeasonNumber}"; return $"{ShowSlug ?? Show?.Slug}-s{SeasonNumber}";
} }
@ -67,13 +67,13 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// The ID of the Show containing this season. /// The ID of the Show containing this season.
/// </summary> /// </summary>
[SerializeIgnore] public int ShowID { get; set; } [SerializeIgnore] public int ShowId { get; set; }
/// <summary> /// <summary>
/// The show that contains this season. /// The show that contains this season.
/// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>. /// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
/// </summary> /// </summary>
[LoadableRelation(nameof(ShowID))] public Show? Show { get; set; } [LoadableRelation(nameof(ShowId))] public Show? Show { get; set; }
/// <summary> /// <summary>
/// The number of this season. This can be set to 0 to indicate specials. /// The number of this season. This can be set to 0 to indicate specials.

View File

@ -51,7 +51,7 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// The list of alternative titles of this show. /// The list of alternative titles of this show.
/// </summary> /// </summary>
public string[] Aliases { get; set; } = Array.Empty<string>(); public List<string> Aliases { get; set; } = new();
/// <summary> /// <summary>
/// The summary of this show. /// The summary of this show.
@ -61,12 +61,12 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// A list of tags that match this movie. /// A list of tags that match this movie.
/// </summary> /// </summary>
public string[] Tags { get; set; } = Array.Empty<string>(); public List<string> Tags { get; set; } = new();
/// <summary> /// <summary>
/// The list of genres (themes) this show has. /// The list of genres (themes) this show has.
/// </summary> /// </summary>
public Genre[] Genres { get; set; } = Array.Empty<Genre>(); public List<Genre> Genres { get; set; } = new();
/// <summary> /// <summary>
/// Is this show airing, not aired yet or finished? /// Is this show airing, not aired yet or finished?
@ -106,13 +106,13 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// The ID of the Studio that made this show. /// The ID of the Studio that made this show.
/// </summary> /// </summary>
[SerializeIgnore] public int? StudioID { get; set; } [SerializeIgnore] public int? StudioId { get; set; }
/// <summary> /// <summary>
/// The Studio that made this show. /// The Studio that made this show.
/// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>. /// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
/// </summary> /// </summary>
[LoadableRelation(nameof(StudioID))][EditableRelation] public Studio? Studio { get; set; } [LoadableRelation(nameof(StudioId))][EditableRelation] public Studio? Studio { get; set; }
/// <summary> /// <summary>
/// The list of people that made this show. /// The list of people that made this show.

View File

@ -17,9 +17,7 @@
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
namespace Kyoo.Utils namespace Kyoo.Utils
@ -29,125 +27,6 @@ namespace Kyoo.Utils
/// </summary> /// </summary>
public static class EnumerableExtensions public static class EnumerableExtensions
{ {
/// <summary>
/// A Select where the index of the item can be used.
/// </summary>
/// <param name="self">The IEnumerable to map. If self is null, an empty list is returned</param>
/// <param name="mapper">The function that will map each items</param>
/// <typeparam name="T">The type of items in <paramref name="self"/></typeparam>
/// <typeparam name="T2">The type of items in the returned list</typeparam>
/// <returns>The list mapped.</returns>
/// <exception cref="ArgumentNullException">The list or the mapper can't be null</exception>
[LinqTunnel]
public static IEnumerable<T2> Map<T, T2>(this IEnumerable<T> self,
Func<T, int, T2> mapper)
{
if (self == null)
throw new ArgumentNullException(nameof(self));
if (mapper == null)
throw new ArgumentNullException(nameof(mapper));
static IEnumerable<T2> Generator(IEnumerable<T> self, Func<T, int, T2> mapper)
{
using IEnumerator<T> enumerator = self.GetEnumerator();
int index = 0;
while (enumerator.MoveNext())
{
yield return mapper(enumerator.Current, index);
index++;
}
}
return Generator(self, mapper);
}
/// <summary>
/// A map where the mapping function is asynchronous.
/// Note: <see cref="SelectAsync{T,T2}"/> might interest you.
/// </summary>
/// <param name="self">The IEnumerable to map.</param>
/// <param name="mapper">The asynchronous function that will map each items.</param>
/// <typeparam name="T">The type of items in <paramref name="self"/>.</typeparam>
/// <typeparam name="T2">The type of items in the returned list.</typeparam>
/// <returns>The list mapped as an AsyncEnumerable.</returns>
/// <exception cref="ArgumentNullException">The list or the mapper can't be null.</exception>
[LinqTunnel]
public static IAsyncEnumerable<T2> MapAsync<T, T2>(this IEnumerable<T> self,
Func<T, int, Task<T2>> mapper)
{
if (self == null)
throw new ArgumentNullException(nameof(self));
if (mapper == null)
throw new ArgumentNullException(nameof(mapper));
static async IAsyncEnumerable<T2> Generator(IEnumerable<T> self, Func<T, int, Task<T2>> mapper)
{
using IEnumerator<T> enumerator = self.GetEnumerator();
int index = 0;
while (enumerator.MoveNext())
{
yield return await mapper(enumerator.Current, index);
index++;
}
}
return Generator(self, mapper);
}
/// <summary>
/// An asynchronous version of Select.
/// </summary>
/// <param name="self">The IEnumerable to map</param>
/// <param name="mapper">The asynchronous function that will map each items</param>
/// <typeparam name="T">The type of items in <paramref name="self"/></typeparam>
/// <typeparam name="T2">The type of items in the returned list</typeparam>
/// <returns>The list mapped as an AsyncEnumerable</returns>
/// <exception cref="ArgumentNullException">The list or the mapper can't be null</exception>
[LinqTunnel]
public static IAsyncEnumerable<T2> SelectAsync<T, T2>(this IEnumerable<T> self,
Func<T, Task<T2>> mapper)
{
if (self == null)
throw new ArgumentNullException(nameof(self));
if (mapper == null)
throw new ArgumentNullException(nameof(mapper));
static async IAsyncEnumerable<T2> Generator(IEnumerable<T> self, Func<T, Task<T2>> mapper)
{
using IEnumerator<T> enumerator = self.GetEnumerator();
while (enumerator.MoveNext())
yield return await mapper(enumerator.Current);
}
return Generator(self, mapper);
}
/// <summary>
/// Convert an AsyncEnumerable to a List by waiting for every item.
/// </summary>
/// <param name="self">The async list</param>
/// <typeparam name="T">The type of items in the async list and in the returned list.</typeparam>
/// <returns>A task that will return a simple list</returns>
/// <exception cref="ArgumentNullException">The list can't be null</exception>
[LinqTunnel]
public static Task<List<T>> ToListAsync<T>(this IAsyncEnumerable<T> self)
{
if (self == null)
throw new ArgumentNullException(nameof(self));
static async Task<List<T>> ToList(IAsyncEnumerable<T> self)
{
List<T> ret = new();
await foreach (T i in self)
ret.Add(i);
return ret;
}
return ToList(self);
}
/// <summary> /// <summary>
/// If the enumerable is empty, execute an action. /// If the enumerable is empty, execute an action.
/// </summary> /// </summary>
@ -197,104 +76,5 @@ namespace Kyoo.Utils
foreach (T i in self) foreach (T i in self)
action(i); action(i);
} }
/// <summary>
/// A foreach used as a function with a little specificity: the list can be null.
/// </summary>
/// <param name="self">The list to enumerate. If this is null, the function result in a no-op</param>
/// <param name="action">The action to execute for each arguments</param>
public static void ForEach(this IEnumerable? self, Action<object> action)
{
if (self == null)
return;
foreach (object i in self)
action(i);
}
/// <summary>
/// A foreach used as a function with a little specificity: the list can be null.
/// </summary>
/// <param name="self">The list to enumerate. If this is null, the function result in a no-op</param>
/// <param name="action">The action to execute for each arguments</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task ForEachAsync(this IEnumerable? self, Func<object, Task> action)
{
if (self == null)
return;
foreach (object i in self)
await action(i);
}
/// <summary>
/// A foreach used as a function with a little specificity: the list can be null.
/// </summary>
/// <param name="self">The list to enumerate. If this is null, the function result in a no-op</param>
/// <param name="action">The asynchronous action to execute for each arguments</param>
/// <typeparam name="T">The type of items in the list.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task ForEachAsync<T>(this IEnumerable<T>? self, Func<T, Task> action)
{
if (self == null)
return;
foreach (T i in self)
await action(i);
}
/// <summary>
/// A foreach used as a function with a little specificity: the list can be null.
/// </summary>
/// <param name="self">The async list to enumerate. If this is null, the function result in a no-op</param>
/// <param name="action">The action to execute for each arguments</param>
/// <typeparam name="T">The type of items in the list.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task ForEachAsync<T>(this IAsyncEnumerable<T>? self, Action<T> action)
{
if (self == null)
return;
await foreach (T i in self)
action(i);
}
/// <summary>
/// Split a list in a small chunk of data.
/// </summary>
/// <param name="list">The list to split</param>
/// <param name="countPerList">The number of items in each chunk</param>
/// <typeparam name="T">The type of data in the initial list.</typeparam>
/// <returns>A list of chunks</returns>
[LinqTunnel]
public static IEnumerable<List<T>> BatchBy<T>(this List<T> list, int countPerList)
{
for (int i = 0; i < list.Count; i += countPerList)
yield return list.GetRange(i, Math.Min(list.Count - i, countPerList));
}
/// <summary>
/// Split a list in a small chunk of data.
/// </summary>
/// <param name="list">The list to split</param>
/// <param name="countPerList">The number of items in each chunk</param>
/// <typeparam name="T">The type of data in the initial list.</typeparam>
/// <returns>A list of chunks</returns>
[LinqTunnel]
public static IEnumerable<T[]> BatchBy<T>(this IEnumerable<T> list, int countPerList)
{
T[] ret = new T[countPerList];
int i = 0;
using IEnumerator<T> 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;
}
} }
} }

View File

@ -17,13 +17,10 @@
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using JetBrains.Annotations; using JetBrains.Annotations;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Utils namespace Kyoo.Utils
@ -33,99 +30,9 @@ namespace Kyoo.Utils
/// </summary> /// </summary>
public static class Merger public static class Merger
{ {
/// <summary>
/// Merge two lists, can keep duplicates or remove them.
/// </summary>
/// <param name="first">The first enumerable to merge</param>
/// <param name="second">The second enumerable to merge, if items from this list are equals to one from the first, they are not kept</param>
/// <param name="isEqual">Equality function to compare items. If this is null, duplicated elements are kept</param>
/// <typeparam name="T">The type of items in the lists to merge.</typeparam>
/// <returns>The two list merged as an array</returns>
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
public static T[] MergeLists<T>(IEnumerable<T>? first,
IEnumerable<T>? second,
Func<T, T, bool>? isEqual = null)
{
if (first == null)
return second?.ToArray();
if (second == null)
return first.ToArray();
if (isEqual == null)
return first.Concat(second).ToArray();
List<T> list = first.ToList();
return list.Concat(second.Where(x => !list.Any(y => isEqual(x, y)))).ToArray();
}
/// <summary>
/// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept.
/// </summary>
/// <param name="first">The first dictionary to merge</param>
/// <param name="second">The second dictionary to merge</param>
/// <typeparam name="T">The type of the keys in dictionaries</typeparam>
/// <typeparam name="T2">The type of values in the dictionaries</typeparam>
/// <returns>The first dictionary with the missing elements of <paramref name="second"/>.</returns>
/// <seealso cref="MergeDictionaries{T,T2}(System.Collections.Generic.IDictionary{T,T2},System.Collections.Generic.IDictionary{T,T2},out bool)"/>
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
public static IDictionary<T, T2> MergeDictionaries<T, T2>(IDictionary<T, T2>? first,
IDictionary<T, T2>? second)
{
return MergeDictionaries(first, second, out bool _);
}
/// <summary>
/// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept.
/// </summary>
/// <param name="first">The first dictionary to merge</param>
/// <param name="second">The second dictionary to merge</param>
/// <param name="hasChanged">
/// <c>true</c> if a new items has been added to the dictionary, <c>false</c> otherwise.
/// </param>
/// <typeparam name="T">The type of the keys in dictionaries</typeparam>
/// <typeparam name="T2">The type of values in the dictionaries</typeparam>
/// <returns>The first dictionary with the missing elements of <paramref name="second"/>.</returns>
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
public static IDictionary<T, T2> MergeDictionaries<T, T2>(IDictionary<T, T2>? first,
IDictionary<T, T2>? 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;
}
/// <summary> /// <summary>
/// Merge two dictionary, if the same key is found on both dictionary, the values of the second one is kept. /// Merge two dictionary, if the same key is found on both dictionary, the values of the second one is kept.
/// </summary> /// </summary>
/// <remarks>
/// The only difference in this function compared to
/// <see cref="MergeDictionaries{T,T2}(System.Collections.Generic.IDictionary{T,T2},System.Collections.Generic.IDictionary{T,T2}, out bool)"/>
/// is the way <paramref name="hasChanged"/> is calculated and the order of the arguments.
/// <code>
/// MergeDictionaries(first, second);
/// </code>
/// will do the same thing as
/// <code>
/// CompleteDictionaries(second, first, out bool _);
/// </code>
/// </remarks>
/// <param name="first">The first dictionary to merge</param> /// <param name="first">The first dictionary to merge</param>
/// <param name="second">The second dictionary to merge</param> /// <param name="second">The second dictionary to merge</param>
/// <param name="hasChanged"> /// <param name="hasChanged">
@ -138,7 +45,7 @@ namespace Kyoo.Utils
/// set to those of <paramref name="first"/>. /// set to those of <paramref name="first"/>.
/// </returns> /// </returns>
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
public static IDictionary<T, T2> CompleteDictionaries<T, T2>(IDictionary<T, T2>? first, public static IDictionary<T, T2>? CompleteDictionaries<T, T2>(IDictionary<T, T2>? first,
IDictionary<T, T2>? second, IDictionary<T, T2>? second,
out bool hasChanged) out bool hasChanged)
{ {
@ -160,14 +67,8 @@ namespace Kyoo.Utils
/// <summary> /// <summary>
/// Set every non-default values of seconds to the corresponding property of second. /// 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 /// Dictionaries are handled like anonymous objects with a property per key/pair value
/// (see
/// <see cref="MergeDictionaries{T,T2}(System.Collections.Generic.IDictionary{T,T2},System.Collections.Generic.IDictionary{T,T2})"/>
/// for more details).
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/> /// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>
/// </summary> /// </summary>
/// <remarks>
/// This does the opposite of <see cref="Merge{T}"/>.
/// </remarks>
/// <example> /// <example>
/// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "foo"} /// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "foo"}
/// </example> /// </example>
@ -182,19 +83,16 @@ namespace Kyoo.Utils
/// </param> /// </param>
/// <typeparam name="T">Fields of T will be completed</typeparam> /// <typeparam name="T">Fields of T will be completed</typeparam>
/// <returns><paramref name="first"/></returns> /// <returns><paramref name="first"/></returns>
/// <exception cref="ArgumentNullException">If first is null</exception> public static T Complete<T>(T first,
public static T Complete<T>([NotNull] T first,
T? second, T? second,
[InstantHandle] Func<PropertyInfo, bool>? where = null) [InstantHandle] Func<PropertyInfo, bool>? where = null)
{ {
if (first == null)
throw new ArgumentNullException(nameof(first));
if (second == null) if (second == null)
return first; return first;
Type type = typeof(T); Type type = typeof(T);
IEnumerable<PropertyInfo> properties = type.GetProperties() IEnumerable<PropertyInfo> properties = type.GetProperties()
.Where(x => x.CanRead && x.CanWrite .Where(x => x is { CanRead: true, CanWrite: true }
&& Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null);
if (where != null) if (where != null)
@ -202,17 +100,16 @@ namespace Kyoo.Utils
foreach (PropertyInfo property in properties) foreach (PropertyInfo property in properties)
{ {
object value = property.GetValue(second); object? value = property.GetValue(second);
object defaultValue = property.GetCustomAttribute<DefaultValueAttribute>()?.Value
?? property.PropertyType.GetClrDefault();
if (value?.Equals(defaultValue) != false || value.Equals(property.GetValue(first))) if (value?.Equals(property.GetValue(first)) == true)
continue; continue;
if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>))) if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>)))
{ {
Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>))
.GenericTypeArguments; .GenericTypeArguments;
object[] parameters = object?[] parameters =
{ {
property.GetValue(first), property.GetValue(first),
value, value,
@ -222,8 +119,8 @@ namespace Kyoo.Utils
typeof(Merger), typeof(Merger),
nameof(CompleteDictionaries), nameof(CompleteDictionaries),
dictionaryTypes, dictionaryTypes,
parameters); parameters)!;
if ((bool)parameters[2]) if ((bool)parameters[2]!)
property.SetValue(first, newDictionary); property.SetValue(first, newDictionary);
} }
else else
@ -234,109 +131,5 @@ namespace Kyoo.Utils
merge.OnMerge(second); merge.OnMerge(second);
return first; return first;
} }
/// <summary>
/// This will set missing values of <paramref name="first"/> to the corresponding values of <paramref name="second"/>.
/// Enumerable will be merged (concatenated) and Dictionaries too.
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>.
/// </summary>
/// <example>
/// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "test"}
/// </example>
/// <param name="first">
/// The object to complete
/// </param>
/// <param name="second">
/// Missing fields of first will be completed by fields of this item. If second is null, the function no-op.
/// </param>
/// <param name="where">
/// Filter fields that will be merged
/// </param>
/// <typeparam name="T">Fields of T will be merged</typeparam>
/// <returns><paramref name="first"/></returns>
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
public static T Merge<T>(T? first,
T? second,
[InstantHandle] Func<PropertyInfo, bool>? where = null)
{
if (first == null)
return second;
if (second == null)
return first;
Type type = typeof(T);
IEnumerable<PropertyInfo> 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<object>(
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<IResource, IResource, bool> equalityComparer = enumerableType.IsAssignableTo(typeof(IResource))
? (x, y) => x.Slug == y.Slug
: null;
property.SetValue(first, Utility.RunGenericMethod<object>(
typeof(Merger),
nameof(MergeLists),
enumerableType,
oldValue, newValue, equalityComparer));
}
}
if (first is IOnMerge merge)
merge.OnMerge(second);
return first;
}
/// <summary>
/// Set every fields of <paramref name="obj"/> to the default value.
/// </summary>
/// <param name="obj">The object to nullify</param>
/// <typeparam name="T">Fields of T will be nullified</typeparam>
/// <returns><paramref name="obj"/></returns>
public static T Nullify<T>(T obj)
{
Type type = typeof(T);
foreach (PropertyInfo property in type.GetProperties())
{
if (!property.CanWrite || property.GetCustomAttribute<ComputedAttribute>() != null)
continue;
property.SetValue(obj, property.PropertyType.GetClrDefault());
}
return obj;
}
} }
} }

View File

@ -18,7 +18,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Kyoo.Utils namespace Kyoo.Utils
{ {
@ -49,37 +48,5 @@ namespace Kyoo.Utils
return x.Result; return x.Result;
}, TaskContinuationOptions.ExecuteSynchronously); }, TaskContinuationOptions.ExecuteSynchronously);
} }
/// <summary>
/// Map the result of a task to another result.
/// </summary>
/// <param name="task">The task to map.</param>
/// <param name="map">The mapper method, it take the task's result as a parameter and should return the new result.</param>
/// <typeparam name="T">The type of returns of the given task</typeparam>
/// <typeparam name="TResult">The resulting task after the mapping method</typeparam>
/// <returns>A task wrapping the initial task and mapping the initial result.</returns>
/// <exception cref="TaskCanceledException">The source task has been canceled.</exception>
public static Task<TResult> Map<T, TResult>(this Task<T> task, Func<T, TResult> map)
{
return task.ContinueWith(x =>
{
if (x.IsFaulted)
x.Exception!.InnerException!.ReThrow();
if (x.IsCanceled)
throw new TaskCanceledException();
return map(x.Result);
}, TaskContinuationOptions.ExecuteSynchronously);
}
/// <summary>
/// A method to return the a default value from a task if the initial task is null.
/// </summary>
/// <param name="value">The initial task</param>
/// <typeparam name="T">The type that the task will return</typeparam>
/// <returns>A non-null task.</returns>
public static Task<T> DefaultIfNull<T>(Task<T>? value)
{
return value ?? Task.FromResult<T>(default);
}
} }
} }

View File

@ -41,8 +41,6 @@ namespace Kyoo.Utils
/// <returns>True if the expression is a member, false otherwise</returns> /// <returns>True if the expression is a member, false otherwise</returns>
public static bool IsPropertyExpression(LambdaExpression ex) public static bool IsPropertyExpression(LambdaExpression ex)
{ {
if (ex == null)
return false;
return ex.Body is MemberExpression return ex.Body is MemberExpression
|| (ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression); || (ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression);
} }
@ -57,7 +55,7 @@ namespace Kyoo.Utils
{ {
if (!IsPropertyExpression(ex)) if (!IsPropertyExpression(ex))
throw new ArgumentException($"{ex} is not a property expression."); 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 ? ((UnaryExpression)ex.Body).Operand as MemberExpression
: ex.Body as MemberExpression; : ex.Body as MemberExpression;
return member!.Member.Name; return member!.Member.Name;
@ -92,18 +90,6 @@ namespace Kyoo.Utils
return str; return str;
} }
/// <summary>
/// Get the default value of a type.
/// </summary>
/// <param name="type">The type to get the default value</param>
/// <returns>The default value of the given type.</returns>
public static object GetClrDefault(this Type type)
{
return type.IsValueType
? Activator.CreateInstance(type)
: null;
}
/// <summary> /// <summary>
/// Return every <see cref="Type"/> in the inheritance tree of the parameter (interfaces are not returned) /// Return every <see cref="Type"/> in the inheritance tree of the parameter (interfaces are not returned)
/// </summary> /// </summary>
@ -194,13 +180,6 @@ namespace Kyoo.Utils
Type[] generics, Type[] generics,
object[] args) 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) MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public)
.Where(x => x.Name == name) .Where(x => x.Name == name)
.Where(x => x.GetGenericArguments().Length == generics.Length) .Where(x => x.GetGenericArguments().Length == generics.Length)
@ -295,12 +274,11 @@ namespace Kyoo.Utils
/// <returns>The return of the method you wanted to run.</returns> /// <returns>The return of the method you wanted to run.</returns>
/// <seealso cref="RunGenericMethod{T}(object,string,System.Type[],object[])"/> /// <seealso cref="RunGenericMethod{T}(object,string,System.Type[],object[])"/>
/// <seealso cref="RunGenericMethod{T}(System.Type,string,System.Type,object[])"/> /// <seealso cref="RunGenericMethod{T}(System.Type,string,System.Type,object[])"/>
[PublicAPI] public static T? RunGenericMethod<T>(
public static T RunGenericMethod<T>(
Type owner, Type owner,
string methodName, string methodName,
Type[] types, Type[] types,
params object[] args) params object?[] args)
{ {
if (owner == null) if (owner == null)
throw new ArgumentNullException(nameof(owner)); throw new ArgumentNullException(nameof(owner));
@ -311,7 +289,7 @@ namespace Kyoo.Utils
if (types.Length < 1) if (types.Length < 1)
throw new ArgumentException($"The {nameof(types)} array is empty. At least one type is needed."); throw new ArgumentException($"The {nameof(types)} array is empty. At least one type is needed.");
MethodInfo method = GetMethod(owner, BindingFlags.Static, methodName, types, args); 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);
} }
/// <summary> /// <summary>

View File

@ -27,6 +27,7 @@ using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Abstractions.Models.Utils; using Kyoo.Abstractions.Models.Utils;
using Kyoo.Authentication.Models; using Kyoo.Authentication.Models;
using Kyoo.Authentication.Models.DTO; using Kyoo.Authentication.Models.DTO;
using Kyoo.Models;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
@ -229,7 +230,7 @@ namespace Kyoo.Authentication.Views
try try
{ {
user.Id = userID; user.Id = userID;
return await _users.Edit(user, true); return await _users.Edit(user);
} }
catch (ItemNotFoundException) catch (ItemNotFoundException)
{ {
@ -252,14 +253,15 @@ namespace Kyoo.Authentication.Views
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(RequestError))]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
public async Task<ActionResult<User>> PatchMe(User user) public async Task<ActionResult<User>> PatchMe(PartialResource user)
{ {
if (!int.TryParse(User.FindFirstValue(Claims.Id), out int userID)) if (!int.TryParse(User.FindFirstValue(Claims.Id), out int userID))
return Unauthorized(new RequestError("User not authenticated or token invalid.")); return Unauthorized(new RequestError("User not authenticated or token invalid."));
try try
{ {
user.Id = userID; if (user.Id.HasValue && user.Id != userID)
return await _users.Edit(user, false); throw new ArgumentException("Can't edit your user id.");
return await _users.Patch(userID, TryUpdateModelAsync);
} }
catch (ItemNotFoundException) catch (ItemNotFoundException)
{ {

View File

@ -284,12 +284,12 @@ namespace Kyoo.Core.Controllers
(Show s, nameof(Show.Seasons)) => _SetRelation(s, (Show s, nameof(Show.Seasons)) => _SetRelation(s,
SeasonRepository.GetAll(x => x.Show.Id == obj.Id), SeasonRepository.GetAll(x => x.Show.Id == obj.Id),
(x, y) => x.Seasons = y, (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, (Show s, nameof(Show.Episodes)) => _SetRelation(s,
EpisodeRepository.GetAll(x => x.Show.Id == obj.Id), EpisodeRepository.GetAll(x => x.Show.Id == obj.Id),
(x, y) => x.Episodes = y, (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 (Show s, nameof(Show.Collections)) => CollectionRepository
.GetAll(x => x.Shows.Any(y => y.Id == obj.Id)) .GetAll(x => x.Shows.Any(y => y.Id == obj.Id))
@ -300,21 +300,21 @@ namespace Kyoo.Core.Controllers
.Then(x => .Then(x =>
{ {
s.Studio = x; s.Studio = x;
s.StudioID = x?.Id ?? 0; s.StudioId = x?.Id ?? 0;
}), }),
(Season s, nameof(Season.Episodes)) => _SetRelation(s, (Season s, nameof(Season.Episodes)) => _SetRelation(s,
EpisodeRepository.GetAll(x => x.Season.Id == obj.Id), EpisodeRepository.GetAll(x => x.Season.Id == obj.Id),
(x, y) => x.Episodes = y, (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 (Season s, nameof(Season.Show)) => ShowRepository
.GetOrDefault(x => x.Seasons.Any(y => y.Id == obj.Id)) .GetOrDefault(x => x.Seasons.Any(y => y.Id == obj.Id))
.Then(x => .Then(x =>
{ {
s.Show = x; s.Show = x;
s.ShowID = x?.Id ?? 0; s.ShowId = x?.Id ?? 0;
}), }),
@ -323,7 +323,7 @@ namespace Kyoo.Core.Controllers
.Then(x => .Then(x =>
{ {
e.Show = x; e.Show = x;
e.ShowID = x?.Id ?? 0; e.ShowId = x?.Id ?? 0;
}), }),
(Episode e, nameof(Episode.Season)) => SeasonRepository (Episode e, nameof(Episode.Season)) => SeasonRepository
@ -331,18 +331,18 @@ namespace Kyoo.Core.Controllers
.Then(x => .Then(x =>
{ {
e.Season = x; e.Season = x;
e.SeasonID = x?.Id ?? 0; e.SeasonId = x?.Id ?? 0;
}), }),
(Episode e, nameof(Episode.PreviousEpisode)) => EpisodeRepository (Episode e, nameof(Episode.PreviousEpisode)) => EpisodeRepository
.GetAll( .GetAll(
where: x => x.ShowID == e.ShowID, where: x => x.ShowId == e.ShowId,
limit: new Pagination(1, e.Id, true) limit: new Pagination(1, e.Id, true)
).Then(x => e.PreviousEpisode = x.FirstOrDefault()), ).Then(x => e.PreviousEpisode = x.FirstOrDefault()),
(Episode e, nameof(Episode.NextEpisode)) => EpisodeRepository (Episode e, nameof(Episode.NextEpisode)) => EpisodeRepository
.GetAll( .GetAll(
where: x => x.ShowID == e.ShowID, where: x => x.ShowId == e.ShowId,
limit: new Pagination(1, e.Id) limit: new Pagination(1, e.Id)
).Then(x => e.NextEpisode = x.FirstOrDefault()), ).Then(x => e.NextEpisode = x.FirstOrDefault()),
@ -438,10 +438,17 @@ namespace Kyoo.Core.Controllers
} }
/// <inheritdoc /> /// <inheritdoc />
public Task<T> Edit<T>(T item, bool resetOld) public Task<T> Edit<T>(T item)
where T : class, IResource where T : class, IResource
{ {
return GetRepository<T>().Edit(item, resetOld); return GetRepository<T>().Edit(item);
}
/// <inheritdoc />
public Task<T> Patch<T>(int id, Func<T, Task<bool>> patch)
where T : class, IResource
{
return GetRepository<T>().Patch(id, patch);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -64,7 +64,7 @@ namespace Kyoo.Core.Controllers
// Edit episode slugs when the show's slug changes. // Edit episode slugs when the show's slug changes.
shows.OnEdited += (show) => shows.OnEdited += (show) =>
{ {
List<Episode> episodes = _database.Episodes.AsTracking().Where(x => x.ShowID == show.Id).ToList(); List<Episode> episodes = _database.Episodes.AsTracking().Where(x => x.ShowId == show.Id).ToList();
foreach (Episode ep in episodes) foreach (Episode ep in episodes)
{ {
ep.ShowSlug = show.Slug; ep.ShowSlug = show.Slug;
@ -77,7 +77,7 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
public Task<Episode> GetOrDefault(int showID, int seasonNumber, int episodeNumber) public Task<Episode> 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.SeasonNumber == seasonNumber
&& x.EpisodeNumber == episodeNumber); && x.EpisodeNumber == episodeNumber);
} }
@ -111,7 +111,7 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
public Task<Episode> GetAbsolute(int showID, int absoluteNumber) public Task<Episode> GetAbsolute(int showID, int absoluteNumber)
{ {
return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID return _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == showID
&& x.AbsoluteNumber == absoluteNumber); && x.AbsoluteNumber == absoluteNumber);
} }
@ -142,12 +142,12 @@ namespace Kyoo.Core.Controllers
public override async Task<Episode> Create(Episode obj) public override async Task<Episode> Create(Episode obj)
{ {
await base.Create(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; _database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync(() => await _database.SaveChangesAsync(() =>
obj.SeasonNumber != null && obj.EpisodeNumber != null obj.SeasonNumber != null && obj.EpisodeNumber != null
? Get(obj.ShowID, obj.SeasonNumber.Value, obj.EpisodeNumber.Value) ? Get(obj.ShowId, obj.SeasonNumber.Value, obj.EpisodeNumber.Value)
: GetAbsolute(obj.ShowID, obj.AbsoluteNumber.Value)); : GetAbsolute(obj.ShowId, obj.AbsoluteNumber.Value));
OnResourceCreated(obj); OnResourceCreated(obj);
return obj; return obj;
} }
@ -156,14 +156,14 @@ namespace Kyoo.Core.Controllers
protected override async Task Validate(Episode resource) protected override async Task Validate(Episode resource)
{ {
await base.Validate(resource); await base.Validate(resource);
if (resource.ShowID <= 0) if (resource.ShowId <= 0)
{ {
if (resource.Show == null) if (resource.Show == null)
{ {
throw new ArgumentException($"Can't store an episode not related " + 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) if (obj == null)
throw new ArgumentNullException(nameof(obj)); 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; _database.Entry(obj).State = EntityState.Deleted;
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
await base.Delete(obj); await base.Delete(obj);
if (epCount == 1) if (epCount == 1)
await _shows.Delete(obj.ShowID); await _shows.Delete(obj.ShowId);
} }
} }
} }

View File

@ -100,7 +100,11 @@ namespace Kyoo.Core.Controllers
=> throw new InvalidOperationException(); => throw new InvalidOperationException();
/// <inheritdoc /> /// <inheritdoc />
public override Task<ILibraryItem> Edit(ILibraryItem obj, bool resetOld) public override Task<ILibraryItem> Edit(ILibraryItem obj)
=> throw new InvalidOperationException();
/// <inheritdoc />
public override Task<ILibraryItem> Patch(int id, Func<ILibraryItem, Task<bool>> patch)
=> throw new InvalidOperationException(); => throw new InvalidOperationException();
/// <inheritdoc /> /// <inheritdoc />

View File

@ -144,7 +144,7 @@ namespace Kyoo.Core.Controllers
/// <param name="reference">The reference item (the AfterID query)</param> /// <param name="reference">The reference item (the AfterID query)</param>
/// <param name="next">True if the following page should be returned, false for the previous.</param> /// <param name="next">True if the following page should be returned, false for the previous.</param>
/// <returns>An expression ready to be added to a Where close of a sorted query to handle the AfterID</returns> /// <returns>An expression ready to be added to a Where close of a sorted query to handle the AfterID</returns>
protected Expression<Func<T, bool>> KeysetPaginatate( protected Expression<Func<T, bool>> KeysetPaginate(
Sort<T> sort, Sort<T> sort,
T reference, T reference,
bool next = true) bool next = true)
@ -155,22 +155,22 @@ namespace Kyoo.Core.Controllers
ParameterExpression x = Expression.Parameter(typeof(T), "x"); ParameterExpression x = Expression.Parameter(typeof(T), "x");
ConstantExpression referenceC = Expression.Constant(reference, typeof(T)); ConstantExpression referenceC = Expression.Constant(reference, typeof(T));
IEnumerable<Sort<T>.By> _GetSortsBy(Sort<T> sort) IEnumerable<Sort<T>.By> GetSortsBy(Sort<T> sort)
{ {
return sort switch return sort switch
{ {
Sort<T>.Default => _GetSortsBy(DefaultSort), Sort<T>.Default => GetSortsBy(DefaultSort),
Sort<T>.By @sortBy => new[] { sortBy }, Sort<T>.By @sortBy => new[] { sortBy },
Sort<T>.Conglomerate(var list) => list.SelectMany(_GetSortsBy), Sort<T>.Conglomerate(var list) => list.SelectMany(GetSortsBy),
_ => Array.Empty<Sort<T>.By>(), _ => Array.Empty<Sort<T>.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<T>.By id = new(x => x.Id); Sort<T>.By id = new(x => x.Id);
IEnumerable<Sort<T>.By> sorts = _GetSortsBy(sort).Append(id); IEnumerable<Sort<T>.By> sorts = GetSortsBy(sort).Append(id);
BinaryExpression filter = null; BinaryExpression? filter = null;
List<Sort<T>.By> previousSteps = new(); List<Sort<T>.By> previousSteps = new();
// TODO: Add an outer query >= for perf // TODO: Add an outer query >= for perf
// PERF: See https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-equivalent-logic // 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); PropertyInfo property = typeof(T).GetProperty(key);
// Comparing a value with null always return false so we short opt < > comparisons with null. // 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<T>.By(key, desc));
continue; 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. // 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 // 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) if (Nullable.GetUnderlyingType(property.PropertyType) != null)
{ {
BinaryExpression equalNull = Expression.Equal(xkey, Expression.Constant(null)); BinaryExpression equalNull = Expression.Equal(xkey, Expression.Constant(null));
@ -223,7 +223,7 @@ namespace Kyoo.Core.Controllers
previousSteps.Add(new(key, desc)); previousSteps.Add(new(key, desc));
} }
return Expression.Lambda<Func<T, bool>>(filter, x); return Expression.Lambda<Func<T, bool>>(filter!, x);
} }
/// <summary> /// <summary>
@ -316,7 +316,7 @@ namespace Kyoo.Core.Controllers
if (limit?.AfterID != null) if (limit?.AfterID != null)
{ {
T reference = await Get(limit.AfterID.Value); 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) if (limit?.Reverse == true)
query = query.Reverse(); query = query.Reverse();
@ -343,12 +343,9 @@ namespace Kyoo.Core.Controllers
await Validate(obj); await Validate(obj);
if (obj is IThumbnails thumbs) if (obj is IThumbnails thumbs)
{ {
if (thumbs.Poster != null) Database.Entry(thumbs).Reference(x => x.Poster).IsModified = thumbs.Poster != null;
Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry.State = EntityState.Added; Database.Entry(thumbs).Reference(x => x.Thumbnail).IsModified = thumbs.Thumbnail != null;
if (thumbs.Thumbnail != null) Database.Entry(thumbs).Reference(x => x.Logo).IsModified = thumbs.Logo != 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;
} }
return obj; return obj;
} }
@ -376,9 +373,6 @@ namespace Kyoo.Core.Controllers
{ {
try try
{ {
if (obj == null)
throw new ArgumentNullException(nameof(obj));
T old = await GetOrDefault(obj.Slug); T old = await GetOrDefault(obj.Slug);
if (old != null) if (old != null)
return old; return old;
@ -392,21 +386,16 @@ namespace Kyoo.Core.Controllers
} }
/// <inheritdoc/> /// <inheritdoc/>
public virtual async Task<T> Edit(T edited, bool resetOld) public virtual async Task<T> Edit(T edited)
{ {
if (edited == null)
throw new ArgumentNullException(nameof(edited));
bool lazyLoading = Database.ChangeTracker.LazyLoadingEnabled; bool lazyLoading = Database.ChangeTracker.LazyLoadingEnabled;
Database.ChangeTracker.LazyLoadingEnabled = false; Database.ChangeTracker.LazyLoadingEnabled = false;
try try
{ {
T old = await GetWithTracking(edited.Id); T old = await GetWithTracking(edited.Id);
if (resetOld)
old = Merger.Nullify(old);
Merger.Complete(old, edited, x => x.GetCustomAttribute<LoadableRelationAttribute>() == null); Merger.Complete(old, edited, x => x.GetCustomAttribute<LoadableRelationAttribute>() == null);
await EditRelations(old, edited, resetOld); await EditRelations(old, edited, true);
await Database.SaveChangesAsync(); await Database.SaveChangesAsync();
OnEdited?.Invoke(old); OnEdited?.Invoke(old);
return old; return old;
@ -418,6 +407,28 @@ namespace Kyoo.Core.Controllers
} }
} }
/// <inheritdoc/>
public virtual async Task<T> Patch(int id, Func<T, Task<bool>> 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();
}
}
/// <summary> /// <summary>
/// An overridable method to edit relation of a resource. /// An overridable method to edit relation of a resource.
/// </summary> /// </summary>
@ -434,12 +445,11 @@ namespace Kyoo.Core.Controllers
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected virtual Task EditRelations(T resource, T changed, bool resetOld) 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).IsModified = thumbs.Poster != chng.Poster;
Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry.State = EntityState.Modified; Database.Entry(thumbs).Reference(x => x.Thumbnail).IsModified = thumbs.Thumbnail != chng.Thumbnail;
Database.Entry(thumbs).Reference(x => x.Thumbnail).TargetEntry.State = EntityState.Modified; Database.Entry(thumbs).Reference(x => x.Logo).IsModified = thumbs.Logo != chng.Logo;
Database.Entry(thumbs).Reference(x => x.Logo).TargetEntry.State = EntityState.Modified;
} }
return Validate(resource); return Validate(resource);
} }

View File

@ -55,7 +55,7 @@ namespace Kyoo.Core.Controllers
// Edit seasons slugs when the show's slug changes. // Edit seasons slugs when the show's slug changes.
shows.OnEdited += (show) => shows.OnEdited += (show) =>
{ {
List<Season> seasons = _database.Seasons.AsTracking().Where(x => x.ShowID == show.Id).ToList(); List<Season> seasons = _database.Seasons.AsTracking().Where(x => x.ShowId == show.Id).ToList();
foreach (Season season in seasons) foreach (Season season in seasons)
{ {
season.ShowSlug = show.Slug; season.ShowSlug = show.Slug;
@ -86,7 +86,7 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc/> /// <inheritdoc/>
public Task<Season> GetOrDefault(int showID, int seasonNumber) public Task<Season> GetOrDefault(int showID, int seasonNumber)
{ {
return _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID return _database.Seasons.FirstOrDefaultAsync(x => x.ShowId == showID
&& x.SeasonNumber == seasonNumber); && x.SeasonNumber == seasonNumber);
} }
@ -112,9 +112,9 @@ namespace Kyoo.Core.Controllers
public override async Task<Season> Create(Season obj) public override async Task<Season> Create(Season obj)
{ {
await base.Create(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; _database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync(() => Get(obj.ShowID, obj.SeasonNumber)); await _database.SaveChangesAsync(() => Get(obj.ShowId, obj.SeasonNumber));
OnResourceCreated(obj); OnResourceCreated(obj);
return obj; return obj;
} }
@ -123,14 +123,14 @@ namespace Kyoo.Core.Controllers
protected override async Task Validate(Season resource) protected override async Task Validate(Season resource)
{ {
await base.Validate(resource); await base.Validate(resource);
if (resource.ShowID <= 0) if (resource.ShowId <= 0)
{ {
if (resource.Show == null) if (resource.Show == null)
{ {
throw new ArgumentException($"Can't store a season not related to any show " + 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;
} }
} }

View File

@ -97,7 +97,7 @@ namespace Kyoo.Core.Controllers
if (resource.Studio != null) if (resource.Studio != null)
{ {
resource.Studio = await _studios.CreateIfNotExists(resource.Studio); resource.Studio = await _studios.CreateIfNotExists(resource.Studio);
resource.StudioID = resource.Studio.Id; resource.StudioId = resource.Studio.Id;
} }
if (resource.People != null) if (resource.People != null)

View File

@ -16,12 +16,14 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Abstractions.Models.Utils; using Kyoo.Abstractions.Models.Utils;
using Kyoo.Models;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -162,11 +164,11 @@ namespace Kyoo.Core.Api
public async Task<ActionResult<T>> Edit([FromBody] T resource) public async Task<ActionResult<T>> Edit([FromBody] T resource)
{ {
if (resource.Id > 0) if (resource.Id > 0)
return await Repository.Edit(resource, true); return await Repository.Edit(resource);
T old = await Repository.Get(resource.Slug); T old = await Repository.Get(resource.Slug);
resource.Id = old.Id; resource.Id = old.Id;
return await Repository.Edit(resource, true); return await Repository.Edit(resource);
} }
/// <summary> /// <summary>
@ -185,14 +187,15 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<T>> Patch([FromBody] T resource) public async Task<ActionResult<T>> Patch([FromBody] PartialResource resource)
{ {
if (resource.Id > 0) if (resource.Id.HasValue)
return await Repository.Edit(resource, false); 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); T old = await Repository.Get(resource.Slug);
resource.Id = old.Id; return await Repository.Patch(old.Id, TryUpdateModelAsync);
return await Repository.Edit(resource, false);
} }
/// <summary> /// <summary>

View File

@ -83,7 +83,7 @@ namespace Kyoo.Core.Api
[FromQuery] Pagination pagination) [FromQuery] Pagination pagination)
{ {
ICollection<Show> resources = await _libraryManager.GetAll( ICollection<Show> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Show>(x => x.StudioID, x => x.Studio.Slug)), ApiHelper.ParseWhere(where, identifier.Matcher<Show>(x => x.StudioId, x => x.Studio.Slug)),
Sort<Show>.From(sortBy), Sort<Show>.From(sortBy),
pagination pagination
); );

View File

@ -84,7 +84,7 @@ namespace Kyoo.Core.Api
[FromQuery] Pagination pagination) [FromQuery] Pagination pagination)
{ {
ICollection<Episode> resources = await _libraryManager.GetAll( ICollection<Episode> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.SeasonID, x => x.Season.Slug)), ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.SeasonId, x => x.Season.Slug)),
Sort<Episode>.From(sortBy), Sort<Episode>.From(sortBy),
pagination pagination
); );

View File

@ -86,7 +86,7 @@ namespace Kyoo.Core.Api
[FromQuery] Pagination pagination) [FromQuery] Pagination pagination)
{ {
ICollection<Season> resources = await _libraryManager.GetAll( ICollection<Season> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Season>(x => x.ShowID, x => x.Show.Slug)), ApiHelper.ParseWhere(where, identifier.Matcher<Season>(x => x.ShowId, x => x.Show.Slug)),
Sort<Season>.From(sortBy), Sort<Season>.From(sortBy),
pagination pagination
); );
@ -121,7 +121,7 @@ namespace Kyoo.Core.Api
[FromQuery] Pagination pagination) [FromQuery] Pagination pagination)
{ {
ICollection<Episode> resources = await _libraryManager.GetAll( ICollection<Episode> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.ShowID, x => x.Show.Slug)), ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.ShowId, x => x.Show.Slug)),
Sort<Episode>.From(sortBy), Sort<Episode>.From(sortBy),
pagination pagination
); );

View File

@ -306,13 +306,13 @@ namespace Kyoo.Postgresql
.HasIndex(x => x.Slug) .HasIndex(x => x.Slug)
.IsUnique(); .IsUnique();
modelBuilder.Entity<Season>() modelBuilder.Entity<Season>()
.HasIndex(x => new { x.ShowID, x.SeasonNumber }) .HasIndex(x => new { ShowID = x.ShowId, x.SeasonNumber })
.IsUnique(); .IsUnique();
modelBuilder.Entity<Season>() modelBuilder.Entity<Season>()
.HasIndex(x => x.Slug) .HasIndex(x => x.Slug)
.IsUnique(); .IsUnique();
modelBuilder.Entity<Episode>() modelBuilder.Entity<Episode>()
.HasIndex(x => new { x.ShowID, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber }) .HasIndex(x => new { ShowID = x.ShowId, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber })
.IsUnique(); .IsUnique();
modelBuilder.Entity<Episode>() modelBuilder.Entity<Episode>()
.HasIndex(x => x.Slug) .HasIndex(x => x.Slug)

View File

@ -140,16 +140,10 @@ namespace Kyoo.Tests.Database
KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(TestSample.Get<T>())); KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(TestSample.Get<T>()));
} }
[Fact]
public async Task EditNullTest()
{
await Assert.ThrowsAsync<ArgumentNullException>(() => _repository.Edit(null!, false));
}
[Fact] [Fact]
public async Task EditNonExistingTest() public async Task EditNonExistingTest()
{ {
await Assert.ThrowsAsync<ItemNotFoundException>(() => _repository.Edit(new T { Id = 56 }, false)); await Assert.ThrowsAsync<ItemNotFoundException>(() => _repository.Edit(new T { Id = 56 }));
} }
[Fact] [Fact]
@ -170,12 +164,6 @@ namespace Kyoo.Tests.Database
await Assert.ThrowsAsync<ItemNotFoundException>(() => _repository.Get(x => x.Slug == "non-existing")); await Assert.ThrowsAsync<ItemNotFoundException>(() => _repository.Get(x => x.Slug == "non-existing"));
} }
[Fact]
public async Task GetExpressionNullTest()
{
await Assert.ThrowsAsync<ArgumentNullException>(() => _repository.Get((Expression<Func<T, bool>>)null!));
}
[Fact] [Fact]
public async Task GetOrDefaultTest() public async Task GetOrDefaultTest()
{ {

View File

@ -66,29 +66,19 @@ namespace Kyoo.Tests.Database
Assert.Equal("2!", ret.Slug); Assert.Equal("2!", ret.Slug);
} }
[Fact]
public async Task CreateWithoutNameTest()
{
Collection collection = TestSample.GetNew<Collection>();
collection.Name = null;
await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(collection));
}
[Fact] [Fact]
public async Task CreateWithExternalIdTest() public async Task CreateWithExternalIdTest()
{ {
Collection collection = TestSample.GetNew<Collection>(); Collection collection = TestSample.GetNew<Collection>();
collection.ExternalId = new[] collection.ExternalId = new Dictionary<string, MetadataId>
{ {
new MetadataId ["1"] = new()
{ {
Provider = TestSample.Get<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}, },
new MetadataId ["2"] = new()
{ {
Provider = TestSample.GetNew<Provider>(),
Link = "new-provider-link", Link = "new-provider-link",
DataId = "new-id" DataId = "new-id"
} }
@ -96,7 +86,6 @@ namespace Kyoo.Tests.Database
await _repository.Create(collection); await _repository.Create(collection);
Collection retrieved = await _repository.Get(2); Collection retrieved = await _repository.Get(2);
await Repositories.LibraryManager.Load(retrieved, x => x.ExternalId);
Assert.Equal(2, retrieved.ExternalId.Count); Assert.Equal(2, retrieved.ExternalId.Count);
KAssert.DeepEqual(collection.ExternalId.First(), retrieved.ExternalId.First()); KAssert.DeepEqual(collection.ExternalId.First(), retrieved.ExternalId.First());
KAssert.DeepEqual(collection.ExternalId.Last(), retrieved.ExternalId.Last()); KAssert.DeepEqual(collection.ExternalId.Last(), retrieved.ExternalId.Last());
@ -107,11 +96,8 @@ namespace Kyoo.Tests.Database
{ {
Collection value = await _repository.Get(TestSample.Get<Collection>().Slug); Collection value = await _repository.Get(TestSample.Get<Collection>().Slug);
value.Name = "New Title"; value.Name = "New Title";
value.Images = new Dictionary<int, string> value.Poster = new Image("new-poster");
{ await _repository.Edit(value);
[Images.Poster] = "new-poster"
};
await _repository.Edit(value, false);
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Collection retrieved = await database.Collections.FirstAsync(); Collection retrieved = await database.Collections.FirstAsync();
@ -123,21 +109,19 @@ namespace Kyoo.Tests.Database
public async Task EditMetadataTest() public async Task EditMetadataTest()
{ {
Collection value = await _repository.Get(TestSample.Get<Collection>().Slug); Collection value = await _repository.Get(TestSample.Get<Collection>().Slug);
value.ExternalId = new[] value.ExternalId = new Dictionary<string, MetadataId>
{ {
new MetadataId ["test"] = new()
{ {
Provider = TestSample.Get<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}, },
}; };
await _repository.Edit(value, false); await _repository.Edit(value);
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Collection retrieved = await database.Collections Collection retrieved = await database.Collections
.Include(x => x.ExternalId) .Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync(); .FirstAsync();
KAssert.DeepEqual(value, retrieved); KAssert.DeepEqual(value, retrieved);
@ -147,40 +131,36 @@ namespace Kyoo.Tests.Database
public async Task AddMetadataTest() public async Task AddMetadataTest()
{ {
Collection value = await _repository.Get(TestSample.Get<Collection>().Slug); Collection value = await _repository.Get(TestSample.Get<Collection>().Slug);
value.ExternalId = new List<MetadataId> value.ExternalId = new Dictionary<string, MetadataId>
{ {
new() ["toto"] = new()
{ {
Provider = TestSample.Get<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}, },
}; };
await _repository.Edit(value, false); await _repository.Edit(value);
{ {
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Collection retrieved = await database.Collections Collection retrieved = await database.Collections
.Include(x => x.ExternalId) .Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync(); .FirstAsync();
KAssert.DeepEqual(value, retrieved); KAssert.DeepEqual(value, retrieved);
} }
value.ExternalId.Add(new MetadataId value.ExternalId.Add("test", new MetadataId
{ {
Provider = TestSample.GetNew<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}); });
await _repository.Edit(value, false); await _repository.Edit(value);
{ {
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Collection retrieved = await database.Collections Collection retrieved = await database.Collections
.Include(x => x.ExternalId) .Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync(); .FirstAsync();
KAssert.DeepEqual(value, retrieved); KAssert.DeepEqual(value, retrieved);

View File

@ -57,10 +57,10 @@ namespace Kyoo.Tests.Database
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e1", episode.Slug); Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e1", episode.Slug);
Show show = new() Show show = new()
{ {
Id = episode.ShowID, Id = episode.ShowId,
Slug = "new-slug" Slug = "new-slug"
}; };
await Repositories.LibraryManager.ShowRepository.Edit(show, false); await Repositories.LibraryManager.ShowRepository.Edit(show);
episode = await _repository.Get(1); episode = await _repository.Get(1);
Assert.Equal("new-slug-s1e1", episode.Slug); Assert.Equal("new-slug-s1e1", episode.Slug);
} }
@ -74,8 +74,8 @@ namespace Kyoo.Tests.Database
{ {
Id = 1, Id = 1,
SeasonNumber = 2, SeasonNumber = 2,
ShowID = 1 ShowId = 1
}, false); });
Assert.Equal($"{TestSample.Get<Show>().Slug}-s2e1", episode.Slug); Assert.Equal($"{TestSample.Get<Show>().Slug}-s2e1", episode.Slug);
episode = await _repository.Get(1); episode = await _repository.Get(1);
Assert.Equal($"{TestSample.Get<Show>().Slug}-s2e1", episode.Slug); Assert.Equal($"{TestSample.Get<Show>().Slug}-s2e1", episode.Slug);
@ -90,8 +90,8 @@ namespace Kyoo.Tests.Database
{ {
Id = 1, Id = 1,
EpisodeNumber = 2, EpisodeNumber = 2,
ShowID = 1 ShowId = 1
}, false); });
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug); Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
episode = await _repository.Get(1); episode = await _repository.Get(1);
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug); Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
@ -102,7 +102,7 @@ namespace Kyoo.Tests.Database
{ {
Episode episode = await _repository.Create(new Episode Episode episode = await _repository.Create(new Episode
{ {
ShowID = TestSample.Get<Show>().Id, ShowId = TestSample.Get<Show>().Id,
SeasonNumber = 2, SeasonNumber = 2,
EpisodeNumber = 4 EpisodeNumber = 4
}); });
@ -129,10 +129,10 @@ namespace Kyoo.Tests.Database
Episode episode = await _repository.Create(TestSample.GetAbsoluteEpisode()); Episode episode = await _repository.Create(TestSample.GetAbsoluteEpisode());
Show show = new() Show show = new()
{ {
Id = episode.ShowID, Id = episode.ShowId,
Slug = "new-slug" Slug = "new-slug"
}; };
await Repositories.LibraryManager.ShowRepository.Edit(show, false); await Repositories.LibraryManager.ShowRepository.Edit(show);
episode = await _repository.Get(2); episode = await _repository.Get(2);
Assert.Equal($"new-slug-3", episode.Slug); Assert.Equal($"new-slug-3", episode.Slug);
} }
@ -145,8 +145,8 @@ namespace Kyoo.Tests.Database
{ {
Id = 2, Id = 2,
AbsoluteNumber = 56, AbsoluteNumber = 56,
ShowID = 1 ShowId = 1
}, false); });
Assert.Equal($"{TestSample.Get<Show>().Slug}-56", episode.Slug); Assert.Equal($"{TestSample.Get<Show>().Slug}-56", episode.Slug);
episode = await _repository.Get(2); episode = await _repository.Get(2);
Assert.Equal($"{TestSample.Get<Show>().Slug}-56", episode.Slug); Assert.Equal($"{TestSample.Get<Show>().Slug}-56", episode.Slug);
@ -161,8 +161,8 @@ namespace Kyoo.Tests.Database
Id = 2, Id = 2,
SeasonNumber = 1, SeasonNumber = 1,
EpisodeNumber = 2, EpisodeNumber = 2,
ShowID = 1 ShowId = 1
}, false); });
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug); Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
episode = await _repository.Get(2); episode = await _repository.Get(2);
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug); Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
@ -174,49 +174,25 @@ namespace Kyoo.Tests.Database
Episode episode = await _repository.Get(1); Episode episode = await _repository.Get(1);
episode.SeasonNumber = null; episode.SeasonNumber = null;
episode.AbsoluteNumber = 12; episode.AbsoluteNumber = 12;
episode = await _repository.Edit(episode, true); episode = await _repository.Edit(episode);
Assert.Equal($"{TestSample.Get<Show>().Slug}-12", episode.Slug); Assert.Equal($"{TestSample.Get<Show>().Slug}-12", episode.Slug);
episode = await _repository.Get(1); episode = await _repository.Get(1);
Assert.Equal($"{TestSample.Get<Show>().Slug}-12", episode.Slug); Assert.Equal($"{TestSample.Get<Show>().Slug}-12", episode.Slug);
} }
[Fact]
public async Task MovieEpisodeTest()
{
Episode episode = await _repository.Create(TestSample.GetMovieEpisode());
Assert.Equal(TestSample.Get<Show>().Slug, episode.Slug);
episode = await _repository.Get(3);
Assert.Equal(TestSample.Get<Show>().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] [Fact]
public async Task CreateWithExternalIdTest() public async Task CreateWithExternalIdTest()
{ {
Episode value = TestSample.GetNew<Episode>(); Episode value = TestSample.GetNew<Episode>();
value.ExternalId = new[] value.ExternalId = new Dictionary<string, MetadataId>
{ {
new MetadataId ["2"] = new()
{ {
Provider = TestSample.Get<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}, },
new MetadataId ["3"] = new()
{ {
Provider = TestSample.GetNew<Provider>(),
Link = "new-provider-link", Link = "new-provider-link",
DataId = "new-id" DataId = "new-id"
} }
@ -224,7 +200,6 @@ namespace Kyoo.Tests.Database
await _repository.Create(value); await _repository.Create(value);
Episode retrieved = await _repository.Get(2); Episode retrieved = await _repository.Get(2);
await Repositories.LibraryManager.Load(retrieved, x => x.ExternalId);
Assert.Equal(2, retrieved.ExternalId.Count); Assert.Equal(2, retrieved.ExternalId.Count);
KAssert.DeepEqual(value.ExternalId.First(), retrieved.ExternalId.First()); KAssert.DeepEqual(value.ExternalId.First(), retrieved.ExternalId.First());
KAssert.DeepEqual(value.ExternalId.Last(), retrieved.ExternalId.Last()); KAssert.DeepEqual(value.ExternalId.Last(), retrieved.ExternalId.Last());
@ -235,11 +210,8 @@ namespace Kyoo.Tests.Database
{ {
Episode value = await _repository.Get(TestSample.Get<Episode>().Slug); Episode value = await _repository.Get(TestSample.Get<Episode>().Slug);
value.Name = "New Title"; value.Name = "New Title";
value.Images = new Dictionary<int, string> value.Poster = new Image("poster");
{ await _repository.Edit(value);
[Images.Poster] = "new-poster"
};
await _repository.Edit(value, false);
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Episode retrieved = await database.Episodes.FirstAsync(); Episode retrieved = await database.Episodes.FirstAsync();
@ -251,22 +223,18 @@ namespace Kyoo.Tests.Database
public async Task EditMetadataTest() public async Task EditMetadataTest()
{ {
Episode value = await _repository.Get(TestSample.Get<Episode>().Slug); Episode value = await _repository.Get(TestSample.Get<Episode>().Slug);
value.ExternalId = new[] value.ExternalId = new Dictionary<string, MetadataId>
{ {
new MetadataId ["1"] = new()
{ {
Provider = TestSample.Get<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}, },
}; };
await _repository.Edit(value, false); await _repository.Edit(value);
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Episode retrieved = await database.Episodes Episode retrieved = await database.Episodes.FirstAsync();
.Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync();
KAssert.DeepEqual(value, retrieved); KAssert.DeepEqual(value, retrieved);
} }
@ -275,41 +243,33 @@ namespace Kyoo.Tests.Database
public async Task AddMetadataTest() public async Task AddMetadataTest()
{ {
Episode value = await _repository.Get(TestSample.Get<Episode>().Slug); Episode value = await _repository.Get(TestSample.Get<Episode>().Slug);
value.ExternalId = new List<MetadataId> value.ExternalId = new Dictionary<string, MetadataId>
{ {
new() ["toto"] = new()
{ {
Provider = TestSample.Get<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}, },
}; };
await _repository.Edit(value, false); await _repository.Edit(value);
{ {
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Episode retrieved = await database.Episodes Episode retrieved = await database.Episodes.FirstAsync();
.Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync();
KAssert.DeepEqual(value, retrieved); KAssert.DeepEqual(value, retrieved);
} }
value.ExternalId.Add(new MetadataId value.ExternalId.Add("test", new MetadataId
{ {
Provider = TestSample.GetNew<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}); });
await _repository.Edit(value, false); await _repository.Edit(value);
{ {
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Episode retrieved = await database.Episodes Episode retrieved = await database.Episodes.FirstAsync();
.Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync();
KAssert.DeepEqual(value, retrieved); KAssert.DeepEqual(value, retrieved);
} }
@ -326,7 +286,7 @@ namespace Kyoo.Tests.Database
Episode value = new() Episode value = new()
{ {
Name = "This is a test super title", Name = "This is a test super title",
ShowID = 1, ShowId = 1,
AbsoluteNumber = 2 AbsoluteNumber = 2
}; };
await _repository.Create(value); await _repository.Create(value);
@ -343,8 +303,8 @@ namespace Kyoo.Tests.Database
Episode expected = TestSample.Get<Episode>(); Episode expected = TestSample.Get<Episode>();
expected.Id = 0; expected.Id = 0;
expected.ShowID = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get<Show>())).Id; expected.ShowId = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get<Show>())).Id;
expected.SeasonID = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get<Season>())).Id; expected.SeasonId = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get<Season>())).Id;
await _repository.Create(expected); await _repository.Create(expected);
KAssert.DeepEqual(expected, await _repository.Get(expected.Slug)); KAssert.DeepEqual(expected, await _repository.Get(expected.Slug));
} }
@ -355,8 +315,8 @@ namespace Kyoo.Tests.Database
Episode expected = TestSample.Get<Episode>(); Episode expected = TestSample.Get<Episode>();
KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(TestSample.Get<Episode>())); KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(TestSample.Get<Episode>()));
await _repository.Delete(TestSample.Get<Episode>()); await _repository.Delete(TestSample.Get<Episode>());
expected.ShowID = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get<Show>())).Id; expected.ShowId = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get<Show>())).Id;
expected.SeasonID = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get<Season>())).Id; expected.SeasonId = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get<Season>())).Id;
KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(expected)); KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(expected));
} }
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
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<Show>());
LibraryItem actual = await _repository.Get(1);
KAssert.DeepEqual(expected, actual);
}
[Fact]
public async Task GetCollectionTests()
{
LibraryItem expected = new(TestSample.Get<Collection>());
LibraryItem actual = await _repository.Get(-1);
KAssert.DeepEqual(expected, actual);
}
[Fact]
public async Task GetShowSlugTests()
{
LibraryItem expected = new(TestSample.Get<Show>());
LibraryItem actual = await _repository.Get(TestSample.Get<Show>().Slug);
KAssert.DeepEqual(expected, actual);
}
[Fact]
public async Task GetCollectionSlugTests()
{
LibraryItem expected = new(TestSample.Get<Collection>());
LibraryItem actual = await _repository.Get(TestSample.Get<Collection>().Slug);
KAssert.DeepEqual(expected, actual);
}
[Fact]
public async Task GetDuplicatedSlugTests()
{
await _repositories.LibraryManager.Create(new Collection
{
Slug = TestSample.Get<Show>().Slug,
Name = "name"
});
await Assert.ThrowsAsync<InvalidOperationException>(() => _repository.Get(TestSample.Get<Show>().Slug));
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
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<Library>
{
private readonly ILibraryRepository _repository;
protected ALibraryTests(RepositoryActivator repositories)
: base(repositories)
{
_repository = Repositories.LibraryManager.LibraryRepository;
}
[Fact]
public async Task CreateWithoutPathTest()
{
Library library = TestSample.GetNew<Library>();
library.Paths = null;
await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(library));
}
[Fact]
public async Task CreateWithEmptySlugTest()
{
Library library = TestSample.GetNew<Library>();
library.Slug = string.Empty;
await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(library));
}
[Fact]
public async Task CreateWithNumberSlugTest()
{
Library library = TestSample.GetNew<Library>();
library.Slug = "2";
Library ret = await _repository.Create(library);
Assert.Equal("2!", ret.Slug);
}
[Fact]
public async Task CreateWithoutNameTest()
{
Library library = TestSample.GetNew<Library>();
library.Name = null;
await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(library));
}
[Fact]
public async Task CreateWithProvider()
{
Library library = TestSample.GetNew<Library>();
library.Providers = new[] { TestSample.Get<Provider>() };
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<Provider>().Slug, retrieved.Providers.First().Slug);
}
[Fact]
public async Task EditTest()
{
Library value = await _repository.Get(TestSample.Get<Library>().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<Library>().Slug);
value.Providers = new[]
{
TestSample.GetNew<Provider>()
};
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<Library>().Slug);
await Repositories.LibraryManager.Load(value, x => x.Providers);
value.Providers.Add(TestSample.GetNew<Provider>());
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<Library> ret = await _repository.Search(query);
KAssert.DeepEqual(value, ret.First());
}
}
}

View File

@ -52,17 +52,15 @@ namespace Kyoo.Tests.Database
public async Task CreateWithExternalIdTest() public async Task CreateWithExternalIdTest()
{ {
People value = TestSample.GetNew<People>(); People value = TestSample.GetNew<People>();
value.ExternalId = new[] value.ExternalId = new Dictionary<string, MetadataId>
{ {
new MetadataId ["2"] = new()
{ {
Provider = TestSample.Get<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}, },
new MetadataId ["1"] = new()
{ {
Provider = TestSample.GetNew<Provider>(),
Link = "new-provider-link", Link = "new-provider-link",
DataId = "new-id" DataId = "new-id"
} }
@ -70,7 +68,6 @@ namespace Kyoo.Tests.Database
await _repository.Create(value); await _repository.Create(value);
People retrieved = await _repository.Get(2); People retrieved = await _repository.Get(2);
await Repositories.LibraryManager.Load(retrieved, x => x.ExternalId);
Assert.Equal(2, retrieved.ExternalId.Count); Assert.Equal(2, retrieved.ExternalId.Count);
KAssert.DeepEqual(value.ExternalId.First(), retrieved.ExternalId.First()); KAssert.DeepEqual(value.ExternalId.First(), retrieved.ExternalId.First());
KAssert.DeepEqual(value.ExternalId.Last(), retrieved.ExternalId.Last()); KAssert.DeepEqual(value.ExternalId.Last(), retrieved.ExternalId.Last());
@ -81,11 +78,8 @@ namespace Kyoo.Tests.Database
{ {
People value = await _repository.Get(TestSample.Get<People>().Slug); People value = await _repository.Get(TestSample.Get<People>().Slug);
value.Name = "New Name"; value.Name = "New Name";
value.Images = new Dictionary<int, string> value.Poster = new Image("poster");
{ await _repository.Edit(value);
[Images.Poster] = "new-poster"
};
await _repository.Edit(value, false);
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
People retrieved = await database.People.FirstAsync(); People retrieved = await database.People.FirstAsync();
@ -97,22 +91,18 @@ namespace Kyoo.Tests.Database
public async Task EditMetadataTest() public async Task EditMetadataTest()
{ {
People value = await _repository.Get(TestSample.Get<People>().Slug); People value = await _repository.Get(TestSample.Get<People>().Slug);
value.ExternalId = new[] value.ExternalId = new Dictionary<string, MetadataId>
{ {
new MetadataId ["toto"] = new()
{ {
Provider = TestSample.Get<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}, },
}; };
await _repository.Edit(value, false); await _repository.Edit(value);
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
People retrieved = await database.People People retrieved = await database.People.FirstAsync();
.Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync();
KAssert.DeepEqual(value, retrieved); KAssert.DeepEqual(value, retrieved);
} }
@ -121,41 +111,32 @@ namespace Kyoo.Tests.Database
public async Task AddMetadataTest() public async Task AddMetadataTest()
{ {
People value = await _repository.Get(TestSample.Get<People>().Slug); People value = await _repository.Get(TestSample.Get<People>().Slug);
value.ExternalId = new List<MetadataId> value.ExternalId = new Dictionary<string, MetadataId>
{ {
new() ["1"] = new()
{ {
Provider = TestSample.Get<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}, },
}; };
await _repository.Edit(value, false); await _repository.Edit(value);
{ {
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
People retrieved = await database.People People retrieved = await database.People.FirstAsync();
.Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync();
KAssert.DeepEqual(value, retrieved); KAssert.DeepEqual(value, retrieved);
} }
value.ExternalId.Add(new MetadataId value.ExternalId.Add("toto", new MetadataId
{ {
Provider = TestSample.GetNew<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}); });
await _repository.Edit(value, false); await _repository.Edit(value);
{ {
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
People retrieved = await database.People People retrieved = await database.People.FirstAsync();
.Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync();
KAssert.DeepEqual(value, retrieved); KAssert.DeepEqual(value, retrieved);
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
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<Provider>
{
[SuppressMessage("ReSharper", "NotAccessedField.Local")]
private readonly IProviderRepository _repository;
protected AProviderTests(RepositoryActivator repositories)
: base(repositories)
{
_repository = Repositories.LibraryManager.ProviderRepository;
}
}
}

View File

@ -55,10 +55,10 @@ namespace Kyoo.Tests.Database
Assert.Equal("anohana-s1", season.Slug); Assert.Equal("anohana-s1", season.Slug);
Show show = new() Show show = new()
{ {
Id = season.ShowID, Id = season.ShowId,
Slug = "new-slug" Slug = "new-slug"
}; };
await Repositories.LibraryManager.ShowRepository.Edit(show, false); await Repositories.LibraryManager.ShowRepository.Edit(show);
season = await _repository.Get(1); season = await _repository.Get(1);
Assert.Equal("new-slug-s1", season.Slug); Assert.Equal("new-slug-s1", season.Slug);
} }
@ -72,8 +72,8 @@ namespace Kyoo.Tests.Database
{ {
Id = 1, Id = 1,
SeasonNumber = 2, SeasonNumber = 2,
ShowID = 1 ShowId = 1
}, false); });
season = await _repository.Get(1); season = await _repository.Get(1);
Assert.Equal("anohana-s2", season.Slug); Assert.Equal("anohana-s2", season.Slug);
} }
@ -83,7 +83,7 @@ namespace Kyoo.Tests.Database
{ {
Season season = await _repository.Create(new Season Season season = await _repository.Create(new Season
{ {
ShowID = TestSample.Get<Show>().Id, ShowId = TestSample.Get<Show>().Id,
SeasonNumber = 2 SeasonNumber = 2
}); });
Assert.Equal($"{TestSample.Get<Show>().Slug}-s2", season.Slug); Assert.Equal($"{TestSample.Get<Show>().Slug}-s2", season.Slug);
@ -93,17 +93,15 @@ namespace Kyoo.Tests.Database
public async Task CreateWithExternalIdTest() public async Task CreateWithExternalIdTest()
{ {
Season season = TestSample.GetNew<Season>(); Season season = TestSample.GetNew<Season>();
season.ExternalId = new[] season.ExternalId = new Dictionary<string, MetadataId>
{ {
new MetadataId ["2"] = new()
{ {
Provider = TestSample.Get<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}, },
new MetadataId ["1"] = new()
{ {
Provider = TestSample.GetNew<Provider>(),
Link = "new-provider-link", Link = "new-provider-link",
DataId = "new-id" DataId = "new-id"
} }
@ -111,7 +109,6 @@ namespace Kyoo.Tests.Database
await _repository.Create(season); await _repository.Create(season);
Season retrieved = await _repository.Get(2); Season retrieved = await _repository.Get(2);
await Repositories.LibraryManager.Load(retrieved, x => x.ExternalId);
Assert.Equal(2, retrieved.ExternalId.Count); Assert.Equal(2, retrieved.ExternalId.Count);
KAssert.DeepEqual(season.ExternalId.First(), retrieved.ExternalId.First()); KAssert.DeepEqual(season.ExternalId.First(), retrieved.ExternalId.First());
KAssert.DeepEqual(season.ExternalId.Last(), retrieved.ExternalId.Last()); KAssert.DeepEqual(season.ExternalId.Last(), retrieved.ExternalId.Last());
@ -122,11 +119,8 @@ namespace Kyoo.Tests.Database
{ {
Season value = await _repository.Get(TestSample.Get<Season>().Slug); Season value = await _repository.Get(TestSample.Get<Season>().Slug);
value.Name = "New Title"; value.Name = "New Title";
value.Images = new Dictionary<int, string> value.Poster = new Image("test");
{ await _repository.Edit(value);
[Images.Poster] = "new-poster"
};
await _repository.Edit(value, false);
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Season retrieved = await database.Seasons.FirstAsync(); Season retrieved = await database.Seasons.FirstAsync();
@ -138,22 +132,18 @@ namespace Kyoo.Tests.Database
public async Task EditMetadataTest() public async Task EditMetadataTest()
{ {
Season value = await _repository.Get(TestSample.Get<Season>().Slug); Season value = await _repository.Get(TestSample.Get<Season>().Slug);
value.ExternalId = new[] value.ExternalId = new Dictionary<string, MetadataId>
{ {
new MetadataId ["toto"] = new()
{ {
Provider = TestSample.Get<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}, },
}; };
await _repository.Edit(value, false); await _repository.Edit(value);
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Season retrieved = await database.Seasons Season retrieved = await database.Seasons.FirstAsync();
.Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync();
KAssert.DeepEqual(value, retrieved); KAssert.DeepEqual(value, retrieved);
} }
@ -162,41 +152,33 @@ namespace Kyoo.Tests.Database
public async Task AddMetadataTest() public async Task AddMetadataTest()
{ {
Season value = await _repository.Get(TestSample.Get<Season>().Slug); Season value = await _repository.Get(TestSample.Get<Season>().Slug);
value.ExternalId = new List<MetadataId> value.ExternalId = new Dictionary<string, MetadataId>
{ {
new() ["1"] = new()
{ {
Provider = TestSample.Get<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}, },
}; };
await _repository.Edit(value, false); await _repository.Edit(value);
{ {
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Season retrieved = await database.Seasons Season retrieved = await database.Seasons.FirstAsync();
.Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync();
KAssert.DeepEqual(value, retrieved); KAssert.DeepEqual(value, retrieved);
} }
value.ExternalId.Add(new MetadataId value.ExternalId.Add("toto", new MetadataId
{ {
Provider = TestSample.GetNew<Provider>(),
Link = "link", Link = "link",
DataId = "id" DataId = "id"
}); });
await _repository.Edit(value, false); await _repository.Edit(value);
{ {
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Season retrieved = await database.Seasons Season retrieved = await database.Seasons.FirstAsync();
.Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync();
KAssert.DeepEqual(value, retrieved); KAssert.DeepEqual(value, retrieved);
} }
@ -213,7 +195,7 @@ namespace Kyoo.Tests.Database
Season value = new() Season value = new()
{ {
Name = "This is a test super title", Name = "This is a test super title",
ShowID = 1 ShowId = 1
}; };
await _repository.Create(value); await _repository.Create(value);
ICollection<Season> ret = await _repository.Search(query); ICollection<Season> ret = await _repository.Search(query);

View File

@ -16,7 +16,6 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -55,9 +54,8 @@ namespace Kyoo.Tests.Database
public async Task EditTest() public async Task EditTest()
{ {
Show value = await _repository.Get(TestSample.Get<Show>().Slug); Show value = await _repository.Get(TestSample.Get<Show>().Slug);
value.Path = "/super";
value.Name = "New Title"; value.Name = "New Title";
Show edited = await _repository.Edit(value, false); Show edited = await _repository.Edit(value);
KAssert.DeepEqual(value, edited); KAssert.DeepEqual(value, edited);
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
@ -70,11 +68,11 @@ namespace Kyoo.Tests.Database
public async Task EditGenreTest() public async Task EditGenreTest()
{ {
Show value = await _repository.Get(TestSample.Get<Show>().Slug); Show value = await _repository.Get(TestSample.Get<Show>().Slug);
value.Genres = new[] { new Genre("test") }; value.Genres = new List<Genre> { Genre.Action };
Show edited = await _repository.Edit(value, false); Show edited = await _repository.Edit(value);
Assert.Equal(value.Slug, edited.Slug); 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(); await using DatabaseContext database = Repositories.Context.New();
Show show = await database.Shows Show show = await database.Shows
@ -82,19 +80,18 @@ namespace Kyoo.Tests.Database
.FirstAsync(); .FirstAsync();
Assert.Equal(value.Slug, show.Slug); 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] [Fact]
public async Task AddGenreTest() public async Task AddGenreTest()
{ {
Show value = await _repository.Get(TestSample.Get<Show>().Slug); Show value = await _repository.Get(TestSample.Get<Show>().Slug);
await Repositories.LibraryManager.Load(value, x => x.Genres); value.Genres.Add(Genre.Drama);
value.Genres.Add(new Genre("test")); Show edited = await _repository.Edit(value);
Show edited = await _repository.Edit(value, false);
Assert.Equal(value.Slug, edited.Slug); 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(); await using DatabaseContext database = Repositories.Context.New();
Show show = await database.Shows Show show = await database.Shows
@ -102,7 +99,7 @@ namespace Kyoo.Tests.Database
.FirstAsync(); .FirstAsync();
Assert.Equal(value.Slug, show.Slug); 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] [Fact]
@ -110,10 +107,10 @@ namespace Kyoo.Tests.Database
{ {
Show value = await _repository.Get(TestSample.Get<Show>().Slug); Show value = await _repository.Get(TestSample.Get<Show>().Slug);
value.Studio = new Studio("studio"); 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(value.Slug, edited.Slug);
Assert.Equal("studio", edited.Studio.Slug); Assert.Equal("studio", edited.Studio!.Slug);
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Show show = await database.Shows Show show = await database.Shows
@ -121,15 +118,15 @@ namespace Kyoo.Tests.Database
.FirstAsync(); .FirstAsync();
Assert.Equal(value.Slug, show.Slug); Assert.Equal(value.Slug, show.Slug);
Assert.Equal("studio", show.Studio.Slug); Assert.Equal("studio", show.Studio!.Slug);
} }
[Fact] [Fact]
public async Task EditAliasesTest() public async Task EditAliasesTest()
{ {
Show value = await _repository.Get(TestSample.Get<Show>().Slug); Show value = await _repository.Get(TestSample.Get<Show>().Slug);
value.Aliases = new[] { "NiceNewAlias", "SecondAlias" }; value.Aliases = new List<string>() { "NiceNewAlias", "SecondAlias" };
Show edited = await _repository.Edit(value, false); Show edited = await _repository.Edit(value);
Assert.Equal(value.Slug, edited.Slug); Assert.Equal(value.Slug, edited.Slug);
Assert.Equal(value.Aliases, edited.Aliases); Assert.Equal(value.Aliases, edited.Aliases);
@ -156,10 +153,10 @@ namespace Kyoo.Tests.Database
Role = "NiceCharacter" Role = "NiceCharacter"
} }
}; };
Show edited = await _repository.Edit(value, false); Show edited = await _repository.Edit(value);
Assert.Equal(value.Slug, edited.Slug); Assert.Equal(value.Slug, edited.Slug);
Assert.Equal(edited.People.First().ShowID, value.Id); Assert.Equal(edited.People!.First().ShowID, value.Id);
Assert.Equal( Assert.Equal(
value.People.Select(x => new { x.Role, x.Slug, x.People.Name }), value.People.Select(x => new { x.Role, x.Slug, x.People.Name }),
edited.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.Slug, show.Slug);
Assert.Equal( Assert.Equal(
value.People.Select(x => new { x.Role, x.Slug, x.People.Name }), 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] [Fact]
public async Task EditExternalIDsTest() public async Task EditExternalIDsTest()
{ {
Show value = await _repository.Get(TestSample.Get<Show>().Slug); Show value = await _repository.Get(TestSample.Get<Show>().Slug);
value.ExternalId = new[] value.ExternalId = new Dictionary<string, MetadataId>()
{ {
new MetadataId ["test"] = new()
{ {
Provider = new Provider("test", "test.png"),
DataId = "1234" DataId = "1234"
} }
}; };
Show edited = await _repository.Edit(value, false); Show edited = await _repository.Edit(value);
Assert.Equal(value.Slug, edited.Slug); Assert.Equal(value.Slug, edited.Slug);
Assert.Equal( Assert.Equal(value.ExternalId, edited.ExternalId);
value.ExternalId.Select(x => new { x.DataID, x.Provider.Slug }),
edited.ExternalId.Select(x => new { x.DataID, x.Provider.Slug }));
await using DatabaseContext database = Repositories.Context.New(); await using DatabaseContext database = Repositories.Context.New();
Show show = await database.Shows Show show = await database.Shows.FirstAsync();
.Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync();
Assert.Equal(value.Slug, show.Slug); Assert.Equal(value.Slug, show.Slug);
Assert.Equal( Assert.Equal(value.ExternalId, show.ExternalId);
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<Show>().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);
} }
[Fact] [Fact]
@ -237,22 +202,14 @@ namespace Kyoo.Tests.Database
Show expected = TestSample.Get<Show>(); Show expected = TestSample.Get<Show>();
expected.Id = 0; expected.Id = 0;
expected.Slug = "created-relation-test"; expected.Slug = "created-relation-test";
expected.ExternalId = new[] expected.ExternalId = new Dictionary<string, MetadataId>
{ {
new MetadataId ["test"] = new()
{ {
Provider = new Provider("provider", "provider.png"),
DataId = "ID" DataId = "ID"
} }
}; };
expected.Genres = new[] expected.Genres = new List<Genre>() { Genre.Action };
{
new Genre
{
Name = "Genre",
Slug = "genre"
}
};
expected.People = new[] expected.People = new[]
{ {
new PeopleRole new PeopleRole
@ -270,7 +227,6 @@ namespace Kyoo.Tests.Database
await using DatabaseContext context = Repositories.Context.New(); await using DatabaseContext context = Repositories.Context.New();
Show retrieved = await context.Shows Show retrieved = await context.Shows
.Include(x => x.ExternalId) .Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.Include(x => x.Genres) .Include(x => x.Genres)
.Include(x => x.People) .Include(x => x.People)
.ThenInclude(x => x.People) .ThenInclude(x => x.People)
@ -281,10 +237,7 @@ namespace Kyoo.Tests.Database
x.Show = null; x.Show = null;
x.People.Roles = null; x.People.Roles = null;
}); });
retrieved.Studio.Shows = null; retrieved.Studio!.Shows = null;
retrieved.Genres.ForEach(x => x.Shows = null);
expected.Genres.ForEach(x => x.Shows = null);
expected.People.ForEach(x => expected.People.ForEach(x =>
{ {
x.Show = null; x.Show = null;
@ -300,11 +253,10 @@ namespace Kyoo.Tests.Database
Show expected = TestSample.Get<Show>(); Show expected = TestSample.Get<Show>();
expected.Id = 0; expected.Id = 0;
expected.Slug = "created-relation-test"; expected.Slug = "created-relation-test";
expected.ExternalId = new[] expected.ExternalId = new Dictionary<string, MetadataId>
{ {
new MetadataId ["test"] = new()
{ {
Provider = TestSample.Get<Provider>(),
DataId = "ID" DataId = "ID"
} }
}; };
@ -313,11 +265,10 @@ namespace Kyoo.Tests.Database
await using DatabaseContext context = Repositories.Context.New(); await using DatabaseContext context = Repositories.Context.New();
Show retrieved = await context.Shows Show retrieved = await context.Shows
.Include(x => x.ExternalId) .Include(x => x.ExternalId)
.ThenInclude(x => x.Provider)
.FirstAsync(x => x.Id == created.Id); .FirstAsync(x => x.Id == created.Id);
KAssert.DeepEqual(expected, retrieved); KAssert.DeepEqual(expected, retrieved);
Assert.Single(retrieved.ExternalId); Assert.Single(retrieved.ExternalId);
Assert.Equal("ID", retrieved.ExternalId.First().DataID); Assert.Equal("ID", retrieved.ExternalId["test"].DataId);
} }
[Fact] [Fact]
@ -362,25 +313,12 @@ namespace Kyoo.Tests.Database
await Repositories.LibraryManager.Load(show, x => x.Seasons); await Repositories.LibraryManager.Load(show, x => x.Seasons);
await Repositories.LibraryManager.Load(show, x => x.Episodes); await Repositories.LibraryManager.Load(show, x => x.Episodes);
Assert.Equal(1, await _repository.GetCount()); Assert.Equal(1, await _repository.GetCount());
Assert.Single(show.Seasons); Assert.Single(show.Seasons!);
Assert.Single(show.Episodes); Assert.Single(show.Episodes!);
await _repository.Delete(show); await _repository.Delete(show);
Assert.Equal(0, await Repositories.LibraryManager.ShowRepository.GetCount()); Assert.Equal(0, await Repositories.LibraryManager.ShowRepository.GetCount());
Assert.Equal(0, await Repositories.LibraryManager.SeasonRepository.GetCount()); Assert.Equal(0, await Repositories.LibraryManager.SeasonRepository.GetCount());
Assert.Equal(0, await Repositories.LibraryManager.EpisodeRepository.GetCount()); Assert.Equal(0, await Repositories.LibraryManager.EpisodeRepository.GetCount());
} }
[Fact]
public async Task AddShowLinkTest()
{
await Repositories.LibraryManager.Create(TestSample.GetNew<Library>());
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));
}
} }
} }

View File

@ -35,10 +35,7 @@ namespace Kyoo.Tests
Slug = "new-collection", Slug = "new-collection",
Name = "New Collection", Name = "New Collection",
Overview = "A collection created by new sample", Overview = "A collection created by new sample",
Images = new Dictionary<int, string> Thumbnail = new Image("thumbnail")
{
[Images.Thumbnail] = "thumbnail"
}
} }
}, },
{ {
@ -52,13 +49,9 @@ namespace Kyoo.Tests
Status = Status.Planned, Status = Status.Planned,
StartAir = new DateTime(2011, 1, 1).ToUniversalTime(), StartAir = new DateTime(2011, 1, 1).ToUniversalTime(),
EndAir = new DateTime(2011, 1, 1).ToUniversalTime(), EndAir = new DateTime(2011, 1, 1).ToUniversalTime(),
Images = new Dictionary<int, string> Poster = new Image("Poster"),
{ Logo = new Image("Logo"),
[Images.Poster] = "Poster", Thumbnail = new Image("Thumbnail"),
[Images.Logo] = "Logo",
[Images.Thumbnail] = "Thumbnail"
},
IsMovie = false,
Studio = null Studio = null
} }
}, },
@ -67,17 +60,14 @@ namespace Kyoo.Tests
() => new Season () => new Season
{ {
Id = 2, Id = 2,
ShowID = 1, ShowId = 1,
ShowSlug = Get<Show>().Slug, ShowSlug = Get<Show>().Slug,
Name = "New season", Name = "New season",
Overview = "New overview", Overview = "New overview",
EndDate = new DateTime(2000, 10, 10).ToUniversalTime(), EndDate = new DateTime(2000, 10, 10).ToUniversalTime(),
SeasonNumber = 2, SeasonNumber = 2,
StartDate = new DateTime(2010, 10, 10).ToUniversalTime(), StartDate = new DateTime(2010, 10, 10).ToUniversalTime(),
Images = new Dictionary<int, string> Logo = new Image("logo")
{
[Images.Logo] = "logo"
}
} }
}, },
{ {
@ -85,9 +75,9 @@ namespace Kyoo.Tests
() => new Episode () => new Episode
{ {
Id = 2, Id = 2,
ShowID = 1, ShowId = 1,
ShowSlug = Get<Show>().Slug, ShowSlug = Get<Show>().Slug,
SeasonID = 1, SeasonId = 1,
SeasonNumber = Get<Season>().SeasonNumber, SeasonNumber = Get<Season>().SeasonNumber,
EpisodeNumber = 3, EpisodeNumber = 3,
AbsoluteNumber = 4, AbsoluteNumber = 4,
@ -95,23 +85,7 @@ namespace Kyoo.Tests
Name = "New Episode Title", Name = "New Episode Title",
ReleaseDate = new DateTime(2000, 10, 10).ToUniversalTime(), ReleaseDate = new DateTime(2000, 10, 10).ToUniversalTime(),
Overview = "new episode overview", Overview = "new episode overview",
Images = new Dictionary<int, string> Logo = new Image("new episode logo")
{
[Images.Logo] = "new episode logo"
}
}
},
{
typeof(Provider),
() => new Provider
{
ID = 2,
Slug = "new-provider",
Name = "Provider NewSample",
Images = new Dictionary<int, string>
{
[Images.Logo] = "logo"
}
} }
}, },
{ {
@ -121,27 +95,14 @@ namespace Kyoo.Tests
Id = 2, Id = 2,
Slug = "new-person-name", Slug = "new-person-name",
Name = "New person name", Name = "New person name",
Images = new Dictionary<int, string> Logo = new Image("Old Logo"),
{ Poster = new Image("Old poster")
[Images.Logo] = "Old Logo",
[Images.Poster] = "Old poster"
}
} }
} }
}; };
private static readonly Dictionary<Type, Func<object>> Samples = new() private static readonly Dictionary<Type, Func<object>> Samples = new()
{ {
{
typeof(Library),
() => new Library
{
ID = 1,
Slug = "deck",
Name = "Deck",
Paths = new[] { "/path/to/deck" }
}
},
{ {
typeof(Collection), typeof(Collection),
() => new Collection () => new Collection
@ -150,10 +111,7 @@ namespace Kyoo.Tests
Slug = "collection", Slug = "collection",
Name = "Collection", Name = "Collection",
Overview = "A nice collection for tests", Overview = "A nice collection for tests",
Images = new Dictionary<int, string> Poster = new Image("Poster")
{
[Images.Poster] = "Poster"
}
} }
}, },
{ {
@ -163,7 +121,7 @@ namespace Kyoo.Tests
Id = 1, Id = 1,
Slug = "anohana", Slug = "anohana",
Name = "Anohana: The Flower We Saw That Day", Name = "Anohana: The Flower We Saw That Day",
Aliases = new[] Aliases = new List<string>
{ {
"Ano Hi Mita Hana no Namae o Bokutachi wa Mada Shiranai.", "Ano Hi Mita Hana no Namae o Bokutachi wa Mada Shiranai.",
"AnoHana", "AnoHana",
@ -173,16 +131,12 @@ namespace Kyoo.Tests
"In time, however, these childhood friends drifted apart, and when they became high " + "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.", "school students, they had long ceased to think of each other as friends.",
Status = Status.Finished, Status = Status.Finished,
StudioID = 1, StudioId = 1,
StartAir = new DateTime(2011, 1, 1).ToUniversalTime(), StartAir = new DateTime(2011, 1, 1).ToUniversalTime(),
EndAir = new DateTime(2011, 1, 1).ToUniversalTime(), EndAir = new DateTime(2011, 1, 1).ToUniversalTime(),
Images = new Dictionary<int, string> Poster = new Image("Poster"),
{ Logo = new Image("Logo"),
[Images.Poster] = "Poster", Thumbnail = new Image("Thumbnail"),
[Images.Logo] = "Logo",
[Images.Thumbnail] = "Thumbnail"
},
IsMovie = false,
Studio = null Studio = null
} }
}, },
@ -192,18 +146,15 @@ namespace Kyoo.Tests
{ {
Id = 1, Id = 1,
ShowSlug = "anohana", ShowSlug = "anohana",
ShowID = 1, ShowId = 1,
SeasonNumber = 1, SeasonNumber = 1,
Name = "Season 1", Name = "Season 1",
Overview = "The first season", Overview = "The first season",
StartDate = new DateTime(2020, 06, 05).ToUniversalTime(), StartDate = new DateTime(2020, 06, 05).ToUniversalTime(),
EndDate = new DateTime(2020, 07, 05).ToUniversalTime(), EndDate = new DateTime(2020, 07, 05).ToUniversalTime(),
Images = new Dictionary<int, string> Poster = new Image("Poster"),
{ Logo = new Image("Logo"),
[Images.Poster] = "Poster", Thumbnail = new Image("Thumbnail")
[Images.Logo] = "Logo",
[Images.Thumbnail] = "Thumbnail"
},
} }
}, },
{ {
@ -212,18 +163,15 @@ namespace Kyoo.Tests
{ {
Id = 1, Id = 1,
ShowSlug = "anohana", ShowSlug = "anohana",
ShowID = 1, ShowId = 1,
SeasonID = 1, SeasonId = 1,
SeasonNumber = 1, SeasonNumber = 1,
EpisodeNumber = 1, EpisodeNumber = 1,
AbsoluteNumber = 1, AbsoluteNumber = 1,
Path = "/home/kyoo/anohana-s1e1", Path = "/home/kyoo/anohana-s1e1",
Images = new Dictionary<int, string> Poster = new Image("Poster"),
{ Logo = new Image("Logo"),
[Images.Poster] = "Poster", Thumbnail = new Image("Thumbnail"),
[Images.Logo] = "Logo",
[Images.Thumbnail] = "Thumbnail"
},
Name = "Episode 1", Name = "Episode 1",
Overview = "Summary of the first episode", Overview = "Summary of the first episode",
ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime() ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime()
@ -236,12 +184,9 @@ namespace Kyoo.Tests
Id = 1, Id = 1,
Slug = "the-actor", Slug = "the-actor",
Name = "The Actor", Name = "The Actor",
Images = new Dictionary<int, string> Poster = new Image("Poster"),
{ Logo = new Image("Logo"),
[Images.Poster] = "Poster", Thumbnail = new Image("Thumbnail")
[Images.Logo] = "Logo",
[Images.Thumbnail] = "Thumbnail"
},
} }
}, },
{ {
@ -253,30 +198,6 @@ namespace Kyoo.Tests
Name = "Hyper studio", 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<int, string>
{
[Images.Poster] = "Poster",
[Images.Logo] = "path/tvdb.svg",
[Images.Thumbnail] = "Thumbnail"
}
}
},
{ {
typeof(User), typeof(User),
() => new User () => new User
@ -309,20 +230,20 @@ namespace Kyoo.Tests
Show show = Get<Show>(); Show show = Get<Show>();
show.Id = 0; show.Id = 0;
show.StudioID = 0; show.StudioId = 0;
context.Shows.Add(show); context.Shows.Add(show);
Season season = Get<Season>(); Season season = Get<Season>();
season.Id = 0; season.Id = 0;
season.ShowID = 0; season.ShowId = 0;
season.Show = show; season.Show = show;
context.Seasons.Add(season); context.Seasons.Add(season);
Episode episode = Get<Episode>(); Episode episode = Get<Episode>();
episode.Id = 0; episode.Id = 0;
episode.ShowID = 0; episode.ShowId = 0;
episode.Show = show; episode.Show = show;
episode.SeasonID = 0; episode.SeasonId = 0;
episode.Season = season; episode.Season = season;
context.Episodes.Add(episode); context.Episodes.Add(episode);
@ -331,20 +252,10 @@ namespace Kyoo.Tests
studio.Shows = new List<Show> { show }; studio.Shows = new List<Show> { show };
context.Studios.Add(studio); context.Studios.Add(studio);
Genre genre = Get<Genre>();
genre.ID = 0;
genre.Shows = new List<Show> { show };
context.Genres.Add(genre);
People people = Get<People>(); People people = Get<People>();
people.Id = 0; people.Id = 0;
context.People.Add(people); context.People.Add(people);
Library library = Get<Library>();
library.ID = 0;
library.Collections = new List<Collection> { collection };
context.Libraries.Add(library);
User user = Get<User>(); User user = Get<User>();
user.Id = 0; user.Id = 0;
context.Users.Add(user); context.Users.Add(user);
@ -358,41 +269,18 @@ namespace Kyoo.Tests
{ {
Id = 2, Id = 2,
ShowSlug = "anohana", ShowSlug = "anohana",
ShowID = 1, ShowId = 1,
SeasonNumber = null, SeasonNumber = null,
EpisodeNumber = null, EpisodeNumber = null,
AbsoluteNumber = 3, AbsoluteNumber = 3,
Path = "/home/kyoo/anohana-3", Path = "/home/kyoo/anohana-3",
Images = new Dictionary<int, string> Poster = new Image("Poster"),
{ Logo = new Image("Logo"),
[Images.Poster] = "Poster", Thumbnail = new Image("Thumbnail"),
[Images.Logo] = "Logo",
[Images.Thumbnail] = "Thumbnail"
},
Name = "Episode 3", Name = "Episode 3",
Overview = "Summary of the third absolute episode", Overview = "Summary of the third absolute episode",
ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime() 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<int, string>
{
[Images.Poster] = "Poster",
[Images.Logo] = "Logo",
[Images.Thumbnail] = "Thumbnail"
},
Name = "John wick",
Overview = "A movie episode test",
ReleaseDate = new DateTime(1595, 05, 12).ToUniversalTime()
};
}
} }
} }

View File

@ -17,9 +17,7 @@
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Kyoo.Utils; using Kyoo.Utils;
using Xunit; using Xunit;
@ -27,53 +25,6 @@ namespace Kyoo.Tests.Utility
{ {
public class EnumerableTests 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<ArgumentNullException>(() => list.Map(((Func<int, int, int>)null)!));
list = null;
Assert.Throws<ArgumentNullException>(() => 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<ArgumentNullException>(() => list.MapAsync(((Func<int, int, Task<int>>)null)!));
list = null;
Assert.Throws<ArgumentNullException>(() => 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<ArgumentNullException>(() => list.SelectAsync(((Func<int, Task<int>>)null)!));
list = null;
Assert.Throws<ArgumentNullException>(() => list!.SelectAsync(x => Task.FromResult(x + 1)));
}
[Fact]
public async Task ToListAsyncTest()
{
int[] expected = { 1, 2, 3, 4 };
IAsyncEnumerable<int> list = expected.SelectAsync(Task.FromResult);
Assert.Equal(expected, await list.ToListAsync());
list = null;
await Assert.ThrowsAsync<ArgumentNullException>(() => list!.ToListAsync());
}
[Fact] [Fact]
public void IfEmptyTest() public void IfEmptyTest()
{ {

View File

@ -16,13 +16,9 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils; using Kyoo.Utils;
using Xunit; using Xunit;
@ -30,318 +26,21 @@ namespace Kyoo.Tests.Utility
{ {
public class MergerTests 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<Genre>(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<Exception>(() => 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<int, string> Dictionary { get; set; }
}
[Fact]
public void GlobalMergeDictionariesTest()
{
MergeDictionaryTest test = new()
{
ID = 5,
Dictionary = new Dictionary<int, string>
{
[2] = "two"
}
};
MergeDictionaryTest test2 = new()
{
Dictionary = new Dictionary<int, string>
{
[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<int, string>
{
[2] = "two"
}
};
MergeDictionaryTest test2 = new()
{
Dictionary = new Dictionary<int, string>
{
[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<int, string> first = new()
{
[1] = "test",
[5] = "value"
};
Dictionary<int, string> second = new()
{
[3] = "third",
};
IDictionary<int, string> 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<int, string> first = new()
{
[1] = "test",
[5] = "value"
};
Dictionary<int, string> second = new()
{
[3] = "third",
[5] = "new-value",
};
IDictionary<int, string> 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] [Fact]
public void CompleteTest() public void CompleteTest()
{ {
Genre genre = new() Studio genre = new()
{ {
ID = 5, Id = 5,
Name = "merged" Name = "merged"
}; };
Genre genre2 = new() Studio genre2 = new()
{ {
Name = "test" Name = "test"
}; };
Genre ret = Merger.Complete(genre, genre2); Studio ret = Merger.Complete(genre, genre2);
Assert.True(ReferenceEquals(genre, ret)); Assert.True(ReferenceEquals(genre, ret));
Assert.Equal(5, ret.ID); Assert.Equal(5, ret.Id);
Assert.Equal("test", genre.Name); Assert.Equal("test", genre.Name);
Assert.Null(genre.Slug); 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. // This should no call the setter of first so the test should pass.
} }
[Fact]
public void MergeDictionaryNoChangeNoSetTest()
{
TestMergeSetter first = new()
{
Backing = new Dictionary<int, int>
{
[2] = 3
}
};
TestMergeSetter second = new()
{
Backing = new Dictionary<int, int>()
};
Merger.Merge(first, second);
// This should no call the setter of first so the test should pass.
}
[Fact]
public void MergeDictionaryNullValue()
{
Dictionary<string, string> first = new()
{
["logo"] = "logo",
["poster"] = null
};
Dictionary<string, string> second = new()
{
["poster"] = "new-poster",
["thumbnail"] = "thumbnails"
};
IDictionary<string, string> 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<string, string> first = new()
{
["logo"] = "logo",
["poster"] = null
};
Dictionary<string, string> second = new()
{
["poster"] = null,
};
IDictionary<string, string> 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] [Fact]
public void CompleteDictionaryNullValue() public void CompleteDictionaryNullValue()
{ {

View File

@ -26,13 +26,6 @@ namespace Kyoo.Tests.Utility
{ {
public class TaskTests public class TaskTests
{ {
[Fact]
public async Task DefaultIfNullTest()
{
Assert.Equal(0, await TaskUtils.DefaultIfNull<int>(null));
Assert.Equal(1, await TaskUtils.DefaultIfNull(Task.FromResult(1)));
}
[Fact] [Fact]
public async Task ThenTest() public async Task ThenTest()
{ {
@ -59,37 +52,5 @@ namespace Kyoo.Tests.Utility
await Assert.ThrowsAsync<TaskCanceledException>(() => Task.Run(Infinite, token.Token) await Assert.ThrowsAsync<TaskCanceledException>(() => Task.Run(Infinite, token.Token)
.Then(_ => { })); .Then(_ => { }));
} }
[Fact]
public async Task MapTest()
{
await Assert.ThrowsAsync<ArgumentException>(() => Task.FromResult(1)
.Map<int, int>(_ => throw new ArgumentException()));
Assert.Equal(2, await Task.FromResult(1)
.Map(x => x + 1));
static async Task<int> Faulted()
{
await Task.Delay(1);
throw new ArgumentException();
}
await Assert.ThrowsAsync<ArgumentException>(() => Faulted()
.Map(x =>
{
KAssert.Fail();
return x;
}));
static async Task<int> Infinite()
{
await Task.Delay(100000);
return 1;
}
CancellationTokenSource token = new();
token.Cancel();
await Assert.ThrowsAsync<TaskCanceledException>(() => Task.Run(Infinite, token.Token)
.Map(x => x));
}
} }
} }

View File

@ -20,7 +20,6 @@ using System;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Utils;
using Xunit; using Xunit;
using KUtility = Kyoo.Utils.Utility; using KUtility = Kyoo.Utils.Utility;
@ -35,7 +34,6 @@ namespace Kyoo.Tests.Utility
Expression<Func<Show, int>> member = x => x.Id; Expression<Func<Show, int>> member = x => x.Id;
Expression<Func<Show, object>> memberCast = x => x.Id; Expression<Func<Show, object>> memberCast = x => x.Id;
Assert.False(KUtility.IsPropertyExpression(null));
Assert.True(KUtility.IsPropertyExpression(member)); Assert.True(KUtility.IsPropertyExpression(member));
Assert.True(KUtility.IsPropertyExpression(memberCast)); Assert.True(KUtility.IsPropertyExpression(memberCast));
@ -51,7 +49,6 @@ namespace Kyoo.Tests.Utility
Assert.Equal("ID", KUtility.GetPropertyName(member)); Assert.Equal("ID", KUtility.GetPropertyName(member));
Assert.Equal("ID", KUtility.GetPropertyName(memberCast)); Assert.Equal("ID", KUtility.GetPropertyName(memberCast));
Assert.Throws<ArgumentException>(() => KUtility.GetPropertyName(null));
} }
[Fact] [Fact]
@ -84,16 +81,5 @@ namespace Kyoo.Tests.Utility
Array.Empty<Type>(), Array.Empty<Type>(),
new object[] { this })); 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);
}
} }
} }