Files
ClaudeDo/tests/ClaudeDo.Ui.Tests/Helpers/StreamLineFormatterTests.cs
Mika Kuns 8eafa71ed3 fix: restore green test suite across all projects
* TaskRepository.UpdateAsync defensively detaches any locally tracked
  entity with the same Id before attaching the patched copy, preventing
  EF identity conflicts when callers load via AsNoTracking and write
  back through the same DbContext (surfaced by ExternalMcpService
  UpdateTask integration tests).
* TasksIslandViewModel auto-collapse now only fires for Finalized
  planning parents that are not yet Done. Active-phase parents stay
  expanded while the user is editing the plan, and Done parents stay
  expanded so all completed children land in CompletedItems alongside
  the parent.
* Update three Ui.Tests fakes (ConflictResolution, PlanningDiff,
  DetailsIslandPlanning) to implement the two new IWorkerClient
  members (OpenInteractiveTerminalAsync, QueuePlanningSubtasksAsync).
* Rewrite StreamLineFormatterTests to exercise the current
  assistant/user/result/system message format instead of the legacy
  stream_event parsing that was removed in the formatter rewrite.
* Align AppSettingsRepository seed-default assertion with the
  permission-mode default that flipped from bypassPermissions to auto.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:30:26 +02:00

139 lines
4.6 KiB
C#

using ClaudeDo.Ui.Helpers;
namespace ClaudeDo.Ui.Tests.Helpers;
public class StreamLineFormatterTests
{
private readonly StreamLineFormatter _formatter = new();
// --- Assistant text blocks ---
[Fact]
public void FormatLine_AssistantTextBlock_ReturnsTextContent()
{
var line = """{"type":"assistant","message":{"content":[{"type":"text","text":"Hello world"}]}}""";
Assert.Equal("Hello world\n", _formatter.FormatLine(line));
}
[Fact]
public void FormatLine_AssistantConsecutiveTextBlocks_ReturnEachAppended()
{
var line1 = """{"type":"assistant","message":{"content":[{"type":"text","text":"Hello "}]}}""";
var line2 = """{"type":"assistant","message":{"content":[{"type":"text","text":"world"}]}}""";
Assert.Equal("Hello \n", _formatter.FormatLine(line1));
Assert.Equal("world\n", _formatter.FormatLine(line2));
}
[Fact]
public void FormatLine_AssistantThinkingBlock_IsFiltered()
{
var line = """{"type":"assistant","message":{"content":[{"type":"thinking","text":"hidden"}]}}""";
Assert.Null(_formatter.FormatLine(line));
}
// --- Tool use, result, system, fallback ---
[Fact]
public void FormatLine_AssistantToolUseBlock_ReturnsToolNameLine()
{
var line = """{"type":"assistant","message":{"content":[{"type":"tool_use","id":"x","name":"Bash","input":{"command":"ls"}}]}}""";
Assert.Equal("[Bash] $ ls\n", _formatter.FormatLine(line));
}
[Fact]
public void FormatLine_InputJsonDelta_ReturnsNull()
{
var line = """{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"cmd\":"}}}""";
Assert.Null(_formatter.FormatLine(line));
}
[Fact]
public void FormatLine_Result_ReturnsFormattedResult()
{
var line = """{"type":"result","result":"Done."}""";
Assert.Equal("\n--- Result ---\nDone.\n", _formatter.FormatLine(line));
}
[Fact]
public void FormatLine_ApiRetry_ReturnsRetryNotice()
{
var line = """{"type":"system","subtype":"api_retry"}""";
Assert.Equal("[Retrying API call...]\n", _formatter.FormatLine(line));
}
[Fact]
public void FormatLine_SystemUnknownSubtype_ReturnsNull()
{
var line = """{"type":"system","subtype":"some_unknown_subtype"}""";
Assert.Null(_formatter.FormatLine(line));
}
[Fact]
public void FormatLine_AssistantType_ReturnsNull()
{
var line = """{"type":"assistant","message":{}}""";
Assert.Null(_formatter.FormatLine(line));
}
[Fact]
public void FormatLine_MalformedJson_ReturnsRawLine()
{
var line = "not json at all";
Assert.Equal("not json at all", _formatter.FormatLine(line));
}
[Fact]
public void FormatLine_MessageStartAndDelta_ReturnsNull()
{
var start = """{"type":"stream_event","event":{"type":"message_start","message":{}}}""";
var delta = """{"type":"stream_event","event":{"type":"message_delta","delta":{}}}""";
Assert.Null(_formatter.FormatLine(start));
Assert.Null(_formatter.FormatLine(delta));
}
// --- FormatFile and Trim ---
[Fact]
public void FormatFile_ParsesAllLinesAndReturnsFormattedText()
{
var lines = new[]
{
"""{"type":"assistant","message":{"content":[{"type":"text","text":"Hello"}]}}""",
"""{"type":"assistant","message":{"content":[{"type":"tool_use","id":"x","name":"Bash","input":{"command":"ls"}}]}}""",
"""{"type":"result","result":"Done."}""",
};
var file = Path.GetTempFileName();
try
{
File.WriteAllLines(file, lines);
var result = _formatter.FormatFile(file);
Assert.Contains("Hello", result);
Assert.Contains("[Bash]", result);
Assert.Contains("Done.", result);
}
finally
{
File.Delete(file);
}
}
[Fact]
public void FormatFile_TrimsLargeContent()
{
var chunk = new string('x', 1000);
var line = "{\"type\":\"assistant\",\"message\":{\"content\":[{\"type\":\"text\",\"text\":\"" + chunk + "\"}]}}";
var lines = Enumerable.Repeat(line, 65).ToArray();
var file = Path.GetTempFileName();
try
{
File.WriteAllLines(file, lines);
var result = _formatter.FormatFile(file);
Assert.True(result.Length <= 50_200, $"Expected <= 50200 but got {result.Length}");
}
finally
{
File.Delete(file);
}
}
}