[Feature] Filter Meta Indexer by tag and by language (#11662). resolves #8884 resolves #7170 resolves #4787 resolves #2185

* bump to 0.18.*

Also partially addresses https://github.com/Jackett/Jackett/issues/661 (if user adds `enabled` and `disabled` tags).

Co-authored-by: garfield69 <garfieldsixtynine@gmail.com>
Co-authored-by: ilike2burnthing <59480337+ilike2burnthing@users.noreply.github.com>
This commit is contained in:
Alessio Gogna
2021-05-08 22:24:18 +02:00
committed by GitHub
parent b07543bff6
commit 66bec102db
30 changed files with 1091 additions and 170 deletions

View File

@@ -0,0 +1,98 @@
using System;
using Jackett.Common.Indexers;
using Jackett.Common.Utils.FilterFuncs;
using NUnit.Framework;
namespace Jackett.Test.Common.Utils.FilterFuncs
{
[TestFixture]
public class FilterFuncComponentTests
{
private readonly FilterFuncComponent target = new FilterFuncComponentStub("filter");
private static readonly Func<IIndexer, bool> Func = _ => true;
private class FilterFuncComponentStub : FilterFuncComponent
{
public FilterFuncComponentStub(string id) : base(id)
{
}
public override Func<IIndexer, bool> ToFunc(string args) => Func;
}
[Test]
public void Ctor_NullID_ThrowsException()
{
Assert.Throws<ArgumentNullException>(() => new FilterFuncComponentStub(null));
}
[Test]
public void Ctor_EmptyID_ThrowsException()
{
Assert.Throws<ArgumentException>(() => new FilterFuncComponentStub(string.Empty));
}
[Test]
public void Ctor_WhitespaceID_ThrowsException()
{
Assert.Throws<ArgumentException>(() => new FilterFuncComponentStub(" "));
}
[Test]
public void FromFilter_NullSource_Null()
{
var actual = target.FromFilter(null);
Assert.IsNull(actual);
}
[Test]
public void FromFilter_EmptySource_Null()
{
var actual = target.FromFilter(string.Empty);
Assert.IsNull(actual);
}
[Test]
public void FromFilter_WhitespaceSource_Null()
{
var actual = target.FromFilter(" ");
Assert.IsNull(actual);
}
[Test]
public void FromFilter_WrongSource_Null()
{
var actual = target.FromFilter("wrong:args");
Assert.IsNull(actual);
}
[Test]
public void FromFilter_NoArgsSource_Null()
{
var actual = target.FromFilter(target.ID);
Assert.IsNull(actual);
}
[Test]
public void FromFilter_EmptyArgsSource_Null()
{
var actual = target.FromFilter($"{target.ID}:");
Assert.IsNull(actual);
}
[Test]
public void FromFilter_SourceWithArgs()
{
var actual = target.FromFilter($"{target.ID.ToUpper()}:args");
Assert.AreSame(Func, actual);
}
[Test]
public void FromFilter_CaseInsensitivePrefixSource()
{
var actual = target.FromFilter($"{target.ID.ToUpper()}:args");
Assert.AreSame(Func, actual);
}
}
}

View File

@@ -0,0 +1,128 @@
using System;
using Jackett.Common.Indexers;
using Jackett.Common.Utils.FilterFuncs;
using Jackett.Test.TestHelpers;
using NUnit.Framework;
namespace Jackett.Test.Common.Utils.FilterFuncs
{
[TestFixture]
public class FilterFuncExpressionTests
{
private class FilterFuncComponentStub : FilterFuncComponent
{
private readonly Func<string, Func<IIndexer, bool>> builderFunc;
public FilterFuncComponentStub(string id, Func<string, Func<IIndexer, bool>> builderFunc) : base(id)
{
this.builderFunc = builderFunc;
}
public override Func<IIndexer, bool> ToFunc(string args) => builderFunc(args);
}
private static readonly FilterFuncComponentStub _BoolFilterFunc =
new FilterFuncComponentStub("bool",
args => bool.Parse(args) ? (Func<IIndexer, bool>)(_ => true) : _ => false
);
[Test]
public void Ctor_NoFilters_ThrowsException()
{
Assert.Throws<ArgumentException>(() => new FilterFuncExpression());
}
[Test]
public void Ctor_NullFilters_ThrowsException()
{
Assert.Throws<ArgumentNullException>(() => new FilterFuncExpression(null));
}
[Test]
public void Ctor_EmptyFilters_ThrowsException()
{
Assert.Throws<ArgumentException>(() =>
new FilterFuncExpression(Array.Empty<FilterFuncComponent>())
);
}
[Test]
public void Ctor_WithNullFilter_ThrowsException()
{
Assert.Throws<ArgumentException>(() =>
new FilterFuncExpression(default(FilterFuncComponent))
);
}
[Test]
public void Ctor_WithDuplicatedPrefixFilter_ThrowsException()
{
const string id = "f1";
Func<string, Func<IIndexer, bool>> func = _ => throw TestExceptions.UnexpectedInvocation;
Assert.Throws<ArgumentException>(() =>
{
new FilterFuncExpression(
new FilterFuncComponentStub(id, func),
new FilterFuncComponentStub(id, func));
});
}
[Test]
public void SingleSource()
{
Func<IIndexer, bool> expectedFunc1 = _ => throw TestExceptions.UnexpectedInvocation;
Func<IIndexer, bool> expectedFunc2 = _ => throw TestExceptions.UnexpectedInvocation;
var target = new FilterFuncExpression(
new FilterFuncComponentStub("f1", _ => expectedFunc1),
new FilterFuncComponentStub("f2", _ => expectedFunc2)
);
var actualFunc1 = target.FromFilter("f1:args");
Assert.AreSame(expectedFunc1, actualFunc1);
var actualFunc2 = target.FromFilter("f2:args");
Assert.AreSame(expectedFunc2, actualFunc2);
var actualFunc3 = target.FromFilter("f3:args");
Assert.IsNull(actualFunc3);
}
[Test]
public void SingleSource_NotOperator()
{
var target = new FilterFuncExpression(_BoolFilterFunc);
var filterFunc = target.FromFilter("!bool:true");
Assert.IsFalse(filterFunc(null));
}
[Test]
public void SingleSource_AndOperator()
{
var target = new FilterFuncExpression(_BoolFilterFunc);
var filterFunc = target.FromFilter("bool:true+bool:false");
Assert.IsFalse(filterFunc(null));
}
[Test]
public void SingleSource_OrOperator()
{
var target = new FilterFuncExpression(_BoolFilterFunc);
var filterFunc = target.FromFilter("bool:false,bool:true");
Assert.IsTrue(filterFunc(null));
}
[Test]
public void SingleSource_OperatorPrecedence()
{
var target = new FilterFuncExpression(_BoolFilterFunc);
var filterFunc1 = target.FromFilter("bool:false+bool:true,bool:true");
Assert.IsTrue(filterFunc1(null));
var filterFunc2 = target.FromFilter("bool:true,bool:true+bool:false");
Assert.IsTrue(filterFunc2(null));
}
}
}

View File

@@ -0,0 +1,55 @@
using System.Text;
using System.Threading.Tasks;
using Jackett.Common.Indexers;
using Jackett.Common.Models;
using Jackett.Common.Models.IndexerConfig;
using Jackett.Test.TestHelpers;
using Newtonsoft.Json.Linq;
namespace Jackett.Test.Common.Utils.FilterFuncs
{
public class IndexerStub : IIndexer
{
public virtual string SiteLink => throw TestExceptions.UnexpectedInvocation;
public virtual string[] AlternativeSiteLinks => throw TestExceptions.UnexpectedInvocation;
public virtual string DisplayName => throw TestExceptions.UnexpectedInvocation;
public virtual string DisplayDescription => throw TestExceptions.UnexpectedInvocation;
public virtual string Type => throw TestExceptions.UnexpectedInvocation;
public virtual string Language => throw TestExceptions.UnexpectedInvocation;
public virtual string LastError
{
get => throw TestExceptions.UnexpectedInvocation;
set => throw TestExceptions.UnexpectedInvocation;
}
public virtual string Id => throw TestExceptions.UnexpectedInvocation;
public virtual Encoding Encoding => throw TestExceptions.UnexpectedInvocation;
public virtual TorznabCapabilities TorznabCaps => throw TestExceptions.UnexpectedInvocation;
public virtual bool IsConfigured => throw TestExceptions.UnexpectedInvocation;
public virtual string[] Tags => throw TestExceptions.UnexpectedInvocation;
public virtual Task<ConfigurationData> GetConfigurationForSetup() => throw TestExceptions.UnexpectedInvocation;
public virtual Task<IndexerConfigurationStatus> ApplyConfiguration(JToken configJson) => throw TestExceptions.UnexpectedInvocation;
public virtual void LoadFromSavedConfiguration(JToken jsonConfig) => throw TestExceptions.UnexpectedInvocation;
public virtual void SaveConfig() => throw TestExceptions.UnexpectedInvocation;
public virtual void Unconfigure() => throw TestExceptions.UnexpectedInvocation;
public virtual Task<IndexerResult> ResultsForQuery(TorznabQuery query, bool isMetaIndexer = false) => throw TestExceptions.UnexpectedInvocation;
public virtual bool CanHandleQuery(TorznabQuery query) => throw TestExceptions.UnexpectedInvocation;
}
}

View File

@@ -0,0 +1,55 @@
using NUnit.Framework;
using static Jackett.Common.Utils.FilterFunc;
namespace Jackett.Test.Common.Utils.FilterFuncs
{
[TestFixture]
public class LanguageFuncTests
{
private class LanguageIndexerStub : IndexerStub
{
public LanguageIndexerStub(string language)
{
Language = language;
}
public override bool IsConfigured => true;
public override string Language { get; }
}
[Test]
public void CaseInsensitiveSource_CaseInsensitiveFilter()
{
var language = "en";
var region = "US";
var lrLanguage = new LanguageIndexerStub($"{language.ToLower()}-{region.ToLower()}");
var LRFilterFunc = Language.ToFunc($"{language.ToUpper()}-{region.ToUpper()}");
Assert.IsTrue(LRFilterFunc(lrLanguage));
var lRLanguage = new LanguageIndexerStub($"{language.ToLower()}-{region.ToUpper()}");
var LrFilterFunc = Language.ToFunc($"{language.ToUpper()}-{region.ToLower()}");
Assert.IsTrue(LrFilterFunc(lRLanguage));
var LrLanguage = new LanguageIndexerStub($"{language.ToUpper()}-{region.ToLower()}");
var lRFilterFunc = Language.ToFunc($"{language.ToLower()}-{region.ToUpper()}");
Assert.IsTrue(lRFilterFunc(LrLanguage));
var LRLanguage = new LanguageIndexerStub($"{language.ToUpper()}-{region.ToUpper()}");
var lrFilterFunc = Language.ToFunc($"{language.ToLower()}-{region.ToLower()}");
Assert.IsTrue(lrFilterFunc(LRLanguage));
}
[Test]
public void LanguageWithoutRegion()
{
var language = "en";
var funcFilter = Language.ToFunc(language);
Assert.IsTrue(funcFilter(new LanguageIndexerStub(language)));
Assert.IsTrue(funcFilter(new LanguageIndexerStub($"{language}-region1")));
Assert.IsFalse(funcFilter(new LanguageIndexerStub($"language2-{language}")));
}
}
}

View File

@@ -0,0 +1,47 @@
using NUnit.Framework;
using static Jackett.Common.Utils.FilterFunc;
namespace Jackett.Test.Common.Utils.FilterFuncs
{
[TestFixture]
public class TagFuncTests
{
private class TagsIndexerStub : IndexerStub
{
public TagsIndexerStub(params string[] tags)
{
Tags = tags;
}
public override bool IsConfigured => true;
public override string[] Tags { get; }
}
[Test]
public void CaseInsensitiveFilter()
{
var tagId = "g1";
var tag = new TagsIndexerStub(tagId);
var upperTarget = Tag.ToFunc(tagId.ToUpper());
Assert.IsTrue(upperTarget(tag));
var lowerTarget = Tag.ToFunc(tagId.ToLower());
Assert.IsTrue(lowerTarget(tag));
}
[Test]
public void ContainsTagId()
{
var tagId = "g1";
var target = Tag.ToFunc(tagId);
Assert.IsTrue(target(new TagsIndexerStub(tagId)));
Assert.IsTrue(target(new TagsIndexerStub(tagId, "g2")));
Assert.IsTrue(target(new TagsIndexerStub("g2", tagId)));
Assert.IsFalse(target(new TagsIndexerStub("g2")));
}
}
}

View File

@@ -0,0 +1,52 @@
using NUnit.Framework;
using static Jackett.Common.Utils.FilterFunc;
namespace Jackett.Test.Common.Utils.FilterFuncs
{
[TestFixture]
public class TypeFuncTests
{
private class TypeIndexerStub : IndexerStub
{
public TypeIndexerStub(string type)
{
Type = type;
}
public override bool IsConfigured => true;
public override string Type { get; }
}
[Test]
public void CaseInsensitiveSource_CaseInsensitiveFilter()
{
var typeId = "type-id";
var lowerType = new TypeIndexerStub(typeId.ToLower());
var upperType = new TypeIndexerStub(typeId.ToUpper());
var upperFilterFunc = Type.ToFunc(typeId.ToUpper());
Assert.IsTrue(upperFilterFunc(lowerType));
Assert.IsTrue(upperFilterFunc(upperType));
var lowerFilterFunc = Type.ToFunc(typeId.ToLower());
Assert.IsTrue(lowerFilterFunc(lowerType));
Assert.IsTrue(lowerFilterFunc(upperType));
}
[Test]
public void PartialType()
{
var typeId = "type-id";
var funcFilter = Type.ToFunc($"{typeId}");
Assert.IsFalse(funcFilter(new TypeIndexerStub($"{typeId}suffix")));
Assert.IsFalse(funcFilter(new TypeIndexerStub($"prefix{typeId}")));
Assert.IsFalse(funcFilter(new TypeIndexerStub($"prefix{typeId}suffix")));
}
}
}

View File

@@ -0,0 +1,9 @@
using NUnit.Framework;
namespace Jackett.Test.TestHelpers
{
internal static class TestExceptions
{
public static AssertionException UnexpectedInvocation => new AssertionException("Unexpected Invocation");
}
}

View File

@@ -25,6 +25,6 @@ namespace Jackett.Test.TestHelpers
public Task TestIndexer(string name) => throw new NotImplementedException();
public void InitAggregateIndexer() => throw new NotImplementedException();
public void InitMetaIndexers() => throw new NotImplementedException();
}
}