mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-09-12 21:57:19 -04:00
It was a generic primer whose examples used title, body and mod_date. Only one of those exists here, so half the guide could not be typed into the search box. Every example is now a real field and was run against an index before being written down, including the exclusive range and the leading-wildcard error the old text described but never showed. Covers the two forms that reach the new number handling and had no test: a wildcard on a numeric title, and a curly-brace range. Co-authored-by: rmcrackan <rmcrackan@gmail.com>
157 lines
6.3 KiB
C#
157 lines
6.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using AssertionHelper;
|
|
using DataLayer;
|
|
using LibationSearchEngine;
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
using Directory = System.IO.Directory;
|
|
|
|
namespace SearchEngineTests;
|
|
|
|
/// <summary>
|
|
/// Lucene 3 has no numeric type, so a number field is indexed zero-padded to make a range sort correctly:
|
|
/// 600 minutes is stored as "00000600.00". Every other field is analyzed and keeps the number as written, so
|
|
/// the novel "1984" is stored as "1984". Padding every number in a query regardless of the field it was being
|
|
/// compared against made a numeric title unfindable: <c>title:1984</c> searched titles for "00001984.00", and
|
|
/// a bare <c>1984</c> could only ever mean "some number field equals 1984".
|
|
/// </summary>
|
|
[TestClass]
|
|
public class NumericTitleTests
|
|
{
|
|
private const string ORWELL = "B01984ORWEL";
|
|
private const string PETERS = "B00000014XX";
|
|
private const string CLARKE = "B02001SPACE";
|
|
private const string LONG = "B0LONGBOOK1";
|
|
private const string SHORT = "B0SHORTBOK1";
|
|
private const string FOURTEEN_HOURS = "B0HOUND0001";
|
|
|
|
private string indexDirectory = null!;
|
|
|
|
[TestInitialize]
|
|
public void Initialize()
|
|
{
|
|
indexDirectory = Path.Combine(Path.GetTempPath(), "LibationSearchEngineTests", Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(indexDirectory);
|
|
new SearchEngine(indexDirectory).CreateNewIndex(library);
|
|
}
|
|
|
|
[TestCleanup]
|
|
public void Cleanup()
|
|
{
|
|
try
|
|
{
|
|
if (Directory.Exists(indexDirectory))
|
|
Directory.Delete(indexDirectory, recursive: true);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
// Windows refuses to delete a file Lucene still holds open, and a leftover temp directory is
|
|
// not worth failing a test over
|
|
}
|
|
}
|
|
|
|
private static LibraryBook book(string asin, string title, int lengthInMinutes)
|
|
{
|
|
var contributor = Contributor.GetEmpty();
|
|
var b = new Book(new AudibleProductId(asin), title, null, null, lengthInMinutes, ContentType.Product, [contributor], [contributor], "us");
|
|
return new LibraryBook(b, new DateTime(2026, 8, 15), "account");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Three novels whose titles are numbers, and three books that match those same numbers through a number
|
|
/// field instead, so a test cannot pass by finding the right book for the wrong reason. Both
|
|
/// <c>LengthInMinutes</c> and <c>Hours</c> are indexed, and both are covered.
|
|
/// <para>
|
|
/// The novels' own lengths avoid the collision on purpose. At 866 minutes "14" was found through its
|
|
/// fourteen-hour running time and the test passed even against the padding bug, which is what
|
|
/// <see cref="FOURTEEN_HOURS"/> is here to cover deliberately rather than by accident.
|
|
/// </para>
|
|
/// </summary>
|
|
private static readonly List<LibraryBook> library =
|
|
[
|
|
book(ORWELL, "1984", lengthInMinutes: 675),
|
|
book(PETERS, "14", lengthInMinutes: 780),
|
|
book(CLARKE, "2001: A Space Odyssey", lengthInMinutes: 397),
|
|
book(LONG, "Sign of the Four", lengthInMinutes: 1984),
|
|
book(SHORT, "A Study in Scarlet", lengthInMinutes: 14),
|
|
book(FOURTEEN_HOURS, "The Hound of the Baskervilles", lengthInMinutes: 14 * 60)
|
|
];
|
|
|
|
private string[] search(string query)
|
|
=> [.. new SearchEngine(indexDirectory).Search(query).Docs.Select(d => d.ProductId)];
|
|
|
|
/// <summary>Typing a title into the search box finds the book, even when the title is a number.</summary>
|
|
[TestMethod]
|
|
[DataRow("1984", ORWELL)]
|
|
[DataRow("14", PETERS)]
|
|
[DataRow("2001", CLARKE)]
|
|
public void a_bare_number_finds_the_novel_named_after_it(string query, string expected)
|
|
=> CollectionAssert.Contains(search(query), expected);
|
|
|
|
/// <summary>
|
|
/// Nothing a bare number used to find is given up for it. The number fields were all it could match
|
|
/// before, and it still matches every one of them: the default field holds both spellings of a number,
|
|
/// so searching for both is a union rather than a choice between them.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void a_bare_number_adds_the_title_to_what_it_already_found()
|
|
{
|
|
//14 hours and 14 minutes both still match, and now so does the novel
|
|
search("14").Should().BeEquivalentTo([PETERS, SHORT, FOURTEEN_HOURS]);
|
|
search("1984").Should().BeEquivalentTo([ORWELL, LONG]);
|
|
}
|
|
|
|
/// <summary>Naming the field says which of them is wanted, and no answer is padded wrongly.</summary>
|
|
[TestMethod]
|
|
public void naming_the_field_picks_one_of_them()
|
|
{
|
|
search("title:1984").Should().BeEquivalentTo([ORWELL]);
|
|
search("LengthInMinutes:1984").Should().BeEquivalentTo([LONG]);
|
|
search("title:14").Should().BeEquivalentTo([PETERS]);
|
|
search("LengthInMinutes:14").Should().BeEquivalentTo([SHORT]);
|
|
search("Hours:14").Should().BeEquivalentTo([FOURTEEN_HOURS]);
|
|
}
|
|
|
|
/// <summary>A numeric title inside a phrase or beside other words is text too.</summary>
|
|
[TestMethod]
|
|
public void a_number_in_a_phrase_is_text()
|
|
{
|
|
search("title:\"2001 a space odyssey\"").Should().BeEquivalentTo([CLARKE]);
|
|
search("\"2001 a space odyssey\"").Should().BeEquivalentTo([CLARKE]);
|
|
search("title:2001 AND title:odyssey").Should().BeEquivalentTo([CLARKE]);
|
|
}
|
|
|
|
/// <summary>The padding exists for ranges, which still sort. This is what must not regress.</summary>
|
|
[TestMethod]
|
|
public void number_ranges_still_sort()
|
|
{
|
|
search("LengthInMinutes:[600 TO 900]").Should().BeEquivalentTo([ORWELL, PETERS, FOURTEEN_HOURS]);
|
|
search("LengthInMinutes:[1 to 400]").Should().BeEquivalentTo([CLARKE, SHORT]);
|
|
search("Hours:[10 TO 20]").Should().BeEquivalentTo([ORWELL, PETERS, FOURTEEN_HOURS]);
|
|
|
|
//curly braces exclude the bounds, and have to be recognised as a range the same way
|
|
search("LengthInMinutes:{675 TO 1984}").Should().BeEquivalentTo([PETERS, FOURTEEN_HOURS]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A wildcard reaches a numeric title too, which it could not while the number was being padded:
|
|
/// "title:19*" went looking for "00000019.00*".
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void a_wildcard_works_on_a_numeric_title()
|
|
{
|
|
search("title:19*").Should().BeEquivalentTo([ORWELL]);
|
|
search("title:20*").Should().BeEquivalentTo([CLARKE]);
|
|
}
|
|
|
|
/// <summary>A bare number combines with the rest of the syntax without dragging its expansion along.</summary>
|
|
[TestMethod]
|
|
public void a_bare_number_combines_with_other_terms()
|
|
{
|
|
search("1984 AND title:sign").Should().BeEquivalentTo([LONG]);
|
|
search("-1984").Should().BeEquivalentTo([PETERS, CLARKE, SHORT, FOURTEEN_HOURS]);
|
|
}
|
|
}
|