Files
Libation/Source/_Tests/LibationSearchEngine.Tests/QueryFailureShapeTests.cs
T
Cursor Agentandrmcrackan bb55fb40a3 Stop the filter box looping on dialogs when the search index is at fault
Both grids restored the last good filter by recursing into the filter handler, which
never terminated once the search index rather than the query was the problem: the
restore fails the same way, and the retry uses the same filter. The user got an
endless run of dialogs, each of them blaming a filter string that was fine. Only an
empty last-good filter broke the loop, because that short-circuits before reaching
the search engine.

The fallback is now a bounded sequence -- last good filter, then no filter -- and
the message distinguishes an index Libation cannot reach from a query it cannot
parse. Only the first failure is reported, so restoring is quiet. A malformed query
never surfaces as an IO-family exception, which QueryFailureShapeTests pins against
the real engine, so a typo is never mistaken for index trouble or made to trigger a
rebuild.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-16 18:06:40 +00:00

72 lines
2.0 KiB
C#

using AssertionHelper;
using LibationSearchEngine;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
using DataLayer;
using Directory = System.IO.Directory;
namespace SearchEngineTests;
/// <summary>
/// The filter box has to tell a query the user mistyped apart from an index Libation cannot read: one deserves
/// "bad filter string", the other deserves the recovery steps, and only the latter is worth rebuilding over.
/// </summary>
[TestClass]
public class QueryFailureShapeTests
{
private string indexDirectory = null!;
[TestInitialize]
public void Initialize()
{
indexDirectory = Path.Combine(Path.GetTempPath(), "LibationSearchEngineTests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(indexDirectory);
var contributor = Contributor.GetEmpty();
var book = new Book(new AudibleProductId("B0TEST0001"), "Hound of the Baskervilles", null, null, 1, ContentType.Product, [contributor], [contributor], "us");
new SearchEngine(indexDirectory).CreateNewIndex(new List<LibraryBook> { new(book, new DateTime(2026, 8, 15), "account") });
}
[TestCleanup]
public void Cleanup()
{
try
{
if (Directory.Exists(indexDirectory))
Directory.Delete(indexDirectory, recursive: true);
}
catch (IOException) { }
}
[TestMethod]
[DataRow("title:[unclosed")]
[DataRow("*")]
[DataRow("AND OR")]
[DataRow("(((")]
[DataRow(@"title:""unbalanced")]
public void a_malformed_query_does_not_look_like_an_unreachable_index(string searchString)
{
var engine = new SearchEngine(indexDirectory);
Exception? thrown = null;
try
{
engine.Search(searchString);
}
catch (Exception ex)
{
thrown = ex;
}
// some of these parse fine and simply match nothing, which is also acceptable
if (thrown is null)
return;
// what must never happen is a parse failure being read as index trouble and triggering a rebuild
(thrown is IOException).Should().BeFalse();
SearchEngine.IsRecoverableCorruptIndexException(thrown).Should().BeFalse();
}
}