Non-blocking polish from the fix-commit re-review (verdict MERGEABLE):
- BuildPath: comment the assumption that each {param} is its own path segment
(revalidate the whole path if a template ever concatenates adjacent params).
- Two additive BoundedLineReader tests: exactly-at-cap line returned intact
(inclusive max), and \r\n carriage-return stripping. No logic change.
36 tests (was 34).
Refs #289
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
88 lines
2.8 KiB
C#
88 lines
2.8 KiB
C#
using System.Text;
|
|
using ErsatzTV.Mcp;
|
|
using NUnit.Framework;
|
|
using Shouldly;
|
|
|
|
namespace ErsatzTV.Mcp.Tests;
|
|
|
|
[TestFixture]
|
|
public class BoundedLineReaderTests
|
|
{
|
|
[Test]
|
|
public async Task ReadLineAsync_Should_Return_Line_Without_Trailing_Newline()
|
|
{
|
|
using var reader = new StringReader("hello world\n");
|
|
|
|
BoundedLineReader.Line line = await BoundedLineReader.ReadLineAsync(reader, 1024);
|
|
|
|
line.EndOfStream.ShouldBeFalse();
|
|
line.Overflowed.ShouldBeFalse();
|
|
line.Text.ShouldBe("hello world");
|
|
}
|
|
|
|
[Test]
|
|
public async Task ReadLineAsync_Should_Return_Line_Of_Exactly_Cap_Length_Intact()
|
|
{
|
|
// The cap is the inclusive max: a line of exactly `cap` chars is returned, not overflowed.
|
|
using var reader = new StringReader("abcdefgh\n");
|
|
|
|
BoundedLineReader.Line line = await BoundedLineReader.ReadLineAsync(reader, 8);
|
|
|
|
line.Overflowed.ShouldBeFalse();
|
|
line.Text.ShouldBe("abcdefgh");
|
|
}
|
|
|
|
[Test]
|
|
public async Task ReadLineAsync_Should_Strip_Carriage_Return_In_Crlf()
|
|
{
|
|
using var reader = new StringReader("hello\r\n");
|
|
|
|
BoundedLineReader.Line line = await BoundedLineReader.ReadLineAsync(reader, 1024);
|
|
|
|
line.Text.ShouldBe("hello");
|
|
}
|
|
|
|
[Test]
|
|
public async Task ReadLineAsync_Should_Signal_End_Of_Stream()
|
|
{
|
|
using var reader = new StringReader(string.Empty);
|
|
|
|
BoundedLineReader.Line line = await BoundedLineReader.ReadLineAsync(reader, 1024);
|
|
|
|
line.EndOfStream.ShouldBeTrue();
|
|
}
|
|
|
|
[Test]
|
|
public async Task ReadLineAsync_Should_Overflow_And_Not_Buffer_Oversized_Line()
|
|
{
|
|
// A line far longer than the cap must be reported overflowed with no buffered text —
|
|
// the memory-exhaustion guard.
|
|
string oversized = new string('x', 10_000) + "\n";
|
|
using var reader = new StringReader(oversized);
|
|
|
|
BoundedLineReader.Line line = await BoundedLineReader.ReadLineAsync(reader, 16);
|
|
|
|
line.EndOfStream.ShouldBeFalse();
|
|
line.Overflowed.ShouldBeTrue();
|
|
line.Text.ShouldBeEmpty();
|
|
}
|
|
|
|
[Test]
|
|
public async Task ReadLineAsync_Should_Keep_Subsequent_Lines_Aligned_After_Overflow()
|
|
{
|
|
// After draining an oversized line, the next line must still be read intact.
|
|
var content = new StringBuilder()
|
|
.Append(new string('x', 100)).Append('\n')
|
|
.Append("good\n")
|
|
.ToString();
|
|
using var reader = new StringReader(content);
|
|
|
|
BoundedLineReader.Line first = await BoundedLineReader.ReadLineAsync(reader, 16);
|
|
BoundedLineReader.Line second = await BoundedLineReader.ReadLineAsync(reader, 16);
|
|
|
|
first.Overflowed.ShouldBeTrue();
|
|
second.Overflowed.ShouldBeFalse();
|
|
second.Text.ShouldBe("good");
|
|
}
|
|
}
|