using AIStudio.Tools.RAG; namespace AIStudio.Tests.Tools; /// /// Checks how results are cut into pages by their length. /// /// /// A search which returns only what fits into its budget must not lose the rest: what does not fit /// on one page has to start the next one. Skipping a long result to make room for shorter ones /// after it would leave a gap in the middle of the ranking, and since a page is cut anew every /// time, no later page would ever bring the skipped result back. /// [TestFixture] public sealed class CharacterPagingTests { private const int BUDGET = 60; [Test] public void ResultsWhichFitTogetherStayOnOnePage() { Assert.That(CharacterPaging.Cut([10, 20, 30], BUDGET), Is.EqualTo(new CharacterPage[] { new(0, 3) })); } [Test] public void APageEndsBeforeTheFirstResultWhichNoLongerFits() { var pages = CharacterPaging.Cut([40, 30, 20, 10], BUDGET); Assert.That(pages, Is.EqualTo(new CharacterPage[] { new(0, 1), new(1, 3) }), "The second result starts the next page, instead of the third one taking its place on the first."); } [Test] public void AResultWhichFillsTheRestExactlyStillFits() { using (Assert.EnterMultipleScope()) { Assert.That(CharacterPaging.Cut([BUDGET], BUDGET), Is.EqualTo(new CharacterPage[] { new(0, 1) })); Assert.That(CharacterPaging.Cut([30, 30, 1], BUDGET), Is.EqualTo(new CharacterPage[] { new(0, 2), new(2, 1) })); } } [Test] public void AResultLongerThanTheBudgetFillsAPageOfItsOwn() { var pages = CharacterPaging.Cut([10, 100, 10], BUDGET); Assert.That(pages, Is.EqualTo(new CharacterPage[] { new(0, 1), new(1, 1), new(2, 1) }), "It must neither be lost nor hold up the results after it."); } [Test] public void EveryResultTurnsUpOnExactlyOnePageInOrder() { var random = new Random(4711); var lengths = Enumerable.Range(0, 200).Select(_ => random.Next(0, 2 * BUDGET)).ToArray(); var pages = CharacterPaging.Cut(lengths, BUDGET); using (Assert.EnterMultipleScope()) { Assert.That(pages.SelectMany(page => Enumerable.Range(page.Start, page.Count)), Is.EqualTo(Enumerable.Range(0, lengths.Length))); Assert.That(pages, Has.All.Matches(page => page.Count >= 1)); Assert.That(pages.Where(page => page.Count > 1), Has.All.Matches(page => lengths[page.Start..page.End].Sum() <= BUDGET), "Only a result alone on its page may exceed the budget."); Assert.That(pages.Zip(pages.Skip(1)), Has.All.Matches<(CharacterPage Page, CharacterPage Next)>(pair => lengths[pair.Page.Start..pair.Page.End].Sum() + lengths[pair.Next.Start] > BUDGET), "A page ends only where the next result no longer fits."); } } [Test] public void NoResultsMakeNoPages() { Assert.That(CharacterPaging.Cut([], BUDGET), Is.Empty); } [TestCase(0)] [TestCase(-1)] public void ABudgetBelowOneIsRefused(int budget) { Assert.Throws(() => CharacterPaging.Cut([10], budget)); } [Test] public void ANegativeLengthIsRefused() { Assert.Throws(() => CharacterPaging.Cut([10, -1], BUDGET)); } }