mirror of
https://github.com/Jackett/Jackett.git
synced 2025-09-17 17:34:09 +02:00
[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:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
55
src/Jackett.Test/Common/Utils/FilterFuncs/IndexerStub.cs
Normal file
55
src/Jackett.Test/Common/Utils/FilterFuncs/IndexerStub.cs
Normal 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;
|
||||
}
|
||||
}
|
@@ -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}")));
|
||||
}
|
||||
}
|
||||
}
|
47
src/Jackett.Test/Common/Utils/FilterFuncs/TagFuncTests.cs
Normal file
47
src/Jackett.Test/Common/Utils/FilterFuncs/TagFuncTests.cs
Normal 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")));
|
||||
}
|
||||
}
|
||||
}
|
52
src/Jackett.Test/Common/Utils/FilterFuncs/TypeFuncTests.cs
Normal file
52
src/Jackett.Test/Common/Utils/FilterFuncs/TypeFuncTests.cs
Normal 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")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
9
src/Jackett.Test/TestHelpers/TestExceptions.cs
Normal file
9
src/Jackett.Test/TestHelpers/TestExceptions.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Jackett.Test.TestHelpers
|
||||
{
|
||||
internal static class TestExceptions
|
||||
{
|
||||
public static AssertionException UnexpectedInvocation => new AssertionException("Unexpected Invocation");
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user