diff --git a/Iceshrimp.Tests/Parsing/SearchQueryTests.cs b/Iceshrimp.Tests/Parsing/SearchQueryTests.cs new file mode 100644 index 00000000..c5939c2d --- /dev/null +++ b/Iceshrimp.Tests/Parsing/SearchQueryTests.cs @@ -0,0 +1,214 @@ +using Iceshrimp.Parsing; +using static Iceshrimp.Parsing.SearchQueryFilters; + +namespace Iceshrimp.Tests.Parsing; + +[TestClass] +public class SearchQueryTests +{ + private static List GetCandidatesByUsername(IEnumerable candidates) => + candidates.Select(p => $"{p}:username").SelectMany(SearchQuery.parse).ToList(); + + private static void Validate(ICollection results, object expectedResult, int count) + { + results.Count.Should().Be(count); + foreach (var res in results) res.Should().BeEquivalentTo(expectedResult); + } + + [TestMethod] + [DataRow(false), DataRow(true)] + public void TestParseFrom(bool negated) + { + List candidates = ["from", "author", "by", "user"]; + if (negated) candidates = candidates.Select(p => "-" + p).ToList(); + var results = GetCandidatesByUsername(candidates); + var expectedResult = new FromFilter(negated, "username"); + Validate(results, expectedResult, candidates.Count); + } + + [TestMethod] + [DataRow(false), DataRow(true)] + public void TestParseMention(bool negated) + { + List candidates = ["mention", "mentions", "mentioning"]; + if (negated) candidates = candidates.Select(p => "-" + p).ToList(); + var results = GetCandidatesByUsername(candidates); + var expectedResult = new MentionFilter(negated, "username"); + Validate(results, expectedResult, candidates.Count); + } + + [TestMethod] + [DataRow(false), DataRow(true)] + public void TestParseReply(bool negated) + { + List candidates = ["reply", "replying", "to"]; + if (negated) candidates = candidates.Select(p => "-" + p).ToList(); + var results = GetCandidatesByUsername(candidates); + var expectedResult = new ReplyFilter(negated, "username"); + Validate(results, expectedResult, candidates.Count); + } + + [TestMethod] + [DataRow(false), DataRow(true)] + public void TestParseInstance(bool negated) + { + List candidates = ["instance", "domain", "host"]; + if (negated) candidates = candidates.Select(p => "-" + p).ToList(); + var results = candidates.Select(p => $"{p}:instance.tld").SelectMany(SearchQuery.parse).ToList(); + var expectedResult = new InstanceFilter(negated, "instance.tld"); + Validate(results, expectedResult, candidates.Count); + } + + [TestMethod] + public void TestParseAfter() + { + List candidates = ["after", "since"]; + var results = candidates.Select(p => $"{p}:2024-03-01").SelectMany(SearchQuery.parse).ToList(); + var expectedResult = new AfterFilter(DateOnly.ParseExact("2024-03-01", "O")); + Validate(results, expectedResult, candidates.Count); + } + + [TestMethod] + public void TestParseBefore() + { + List candidates = ["before", "until"]; + var results = candidates.Select(p => $"{p}:2024-03-01").SelectMany(SearchQuery.parse).ToList(); + var expectedResult = new BeforeFilter(DateOnly.ParseExact("2024-03-01", "O")); + Validate(results, expectedResult, candidates.Count); + } + + [TestMethod] + [DataRow(false), DataRow(true)] + public void TestParseAttachment(bool negated) + { + List keyCandidates = ["has", "attachment", "attached"]; + if (negated) keyCandidates = keyCandidates.Select(p => "-" + p).ToList(); + List candidates = ["any", "image", "video", "audio", "file"]; + var results = + keyCandidates.Select(k => candidates.Select(v => $"{k}:{v}").SelectMany(SearchQuery.parse).ToList()); + List expectedResults = + [ + new AttachmentFilter(negated, "any"), + new AttachmentFilter(negated, "image"), + new AttachmentFilter(negated, "video"), + new AttachmentFilter(negated, "audio"), + new AttachmentFilter(negated, "file") + ]; + results.Should() + .HaveCount(keyCandidates.Count) + .And.AllBeEquivalentTo(expectedResults, opts => opts.RespectingRuntimeTypes()); + } + + [TestMethod] + public void TestParseCase() + { + const string key = "case"; + List candidates = ["sensitive", "insensitive"]; + var results = candidates.Select(v => $"{key}:{v}").SelectMany(SearchQuery.parse).ToList(); + List expectedResults = [new CaseFilter("sensitive"), new CaseFilter("insensitive")]; + results.Should() + .HaveCount(expectedResults.Count) + .And.BeEquivalentTo(expectedResults, opts => opts.RespectingRuntimeTypes()); + } + + [TestMethod] + public void TestParseMatch() + { + const string key = "match"; + List candidates = ["words", "word", "substr", "substring"]; + var results = candidates.Select(v => $"{key}:{v}").SelectMany(SearchQuery.parse).ToList(); + List expectedResults = + [ + new MatchFilter("words"), new MatchFilter("words"), new MatchFilter("substr"), new MatchFilter("substr") + ]; + results.Should() + .HaveCount(expectedResults.Count) + .And.BeEquivalentTo(expectedResults, opts => opts.RespectingRuntimeTypes()); + } + + [TestMethod] + [DataRow(false), DataRow(true)] + public void TestParseIn(bool negated) + { + var key = negated ? "-in" : "in"; + List candidates = ["bookmarks", "likes", "favorites", "favourites", "reactions"]; + var results = candidates.Select(v => $"{key}:{v}").SelectMany(SearchQuery.parse).ToList(); + List expectedResults = + [ + new InFilter(negated, "bookmarks"), + new InFilter(negated, "likes"), + new InFilter(negated, "likes"), + new InFilter(negated, "likes"), + new InFilter(negated, "reactions") + ]; + results.Should() + .HaveCount(expectedResults.Count) + .And.BeEquivalentTo(expectedResults, opts => opts.RespectingRuntimeTypes()); + } + + [TestMethod] + [DataRow(false), DataRow(true)] + public void TestParseMisc(bool negated) + { + var key = negated ? "-filter" : "filter"; + List candidates = + [ + "followers", "following", "replies", "reply", "renote", "renotes", "boosts", "boost" + ]; + var results = candidates.Select(v => $"{key}:{v}").SelectMany(SearchQuery.parse).ToList(); + List expectedResults = + [ + new MiscFilter(negated, "followers"), + new MiscFilter(negated, "following"), + new MiscFilter(negated, "replies"), + new MiscFilter(negated, "replies"), + new MiscFilter(negated, "renotes"), + new MiscFilter(negated, "renotes"), + new MiscFilter(negated, "renotes"), + new MiscFilter(negated, "renotes"), + ]; + results.Should() + .HaveCount(expectedResults.Count) + .And.BeEquivalentTo(expectedResults, opts => opts.RespectingRuntimeTypes()); + } + + [TestMethod] + [DataRow(false), DataRow(true)] + public void TestParseWord(bool negated) + { + List candidates = ["test", "word", "since:2023-10-10invalid", "in:bookmarkstypo"]; + if (negated) candidates = candidates.Select(p => "-" + p).ToList(); + var results = candidates.Select(v => $"{v}").SelectMany(SearchQuery.parse).ToList(); + List expectedResults = + [ + new WordFilter(negated, "test"), + new WordFilter(negated, "word"), + new WordFilter(negated, "since:2023-10-10invalid"), + new WordFilter(negated, "in:bookmarkstypo") + ]; + results.Should() + .HaveCount(expectedResults.Count) + .And.BeEquivalentTo(expectedResults, opts => opts.RespectingRuntimeTypes()); + } + + [TestMethod] + public void TestParseMultiWord() + { + const string input = "(word OR word2 OR word3)"; + var results = SearchQuery.parse(input).ToList(); + results.Should().HaveCount(1); + results[0].Should().BeOfType(); + ((MultiWordFilter)results[0]).Values.ToList().Should().BeEquivalentTo(["word", "word2", "word3"]); + } + + [TestMethod] + public void TestParseLiteralString() + { + const string input = "\"literal string with spaces $# and has:image before:2023-10-10 other things\""; + var results = SearchQuery.parse(input).ToList(); + results.Should().HaveCount(1); + results[0].Should().BeOfType(); + ((WordFilter)results[0]).Value.Should() + .BeEquivalentTo("literal string with spaces $# and has:image before:2023-10-10 other things"); + } +} \ No newline at end of file