235 lines
9.6 KiB
C#
235 lines
9.6 KiB
C#
using ClaudeDo.Worker.Runner;
|
||
|
||
namespace ClaudeDo.Worker.Tests.Runner;
|
||
|
||
public sealed class StreamAnalyzerTests
|
||
{
|
||
[Fact]
|
||
public void Extracts_Result_Markdown()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"result","result":"## Done","session_id":"sess-1"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Equal("## Done", result.ResultMarkdown);
|
||
Assert.Equal("sess-1", result.SessionId);
|
||
}
|
||
|
||
[Fact]
|
||
public void Extracts_Structured_Output()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"result","result":"ok","structured_output":{"summary":"all good"},"session_id":"s1"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Equal("ok", result.ResultMarkdown);
|
||
Assert.Contains("all good", result.StructuredOutputJson);
|
||
}
|
||
|
||
[Fact]
|
||
public void Counts_Assistant_Turns()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"assistant","message":"hi"}""");
|
||
analyzer.ProcessLine("""{"type":"assistant","message":"working on it"}""");
|
||
analyzer.ProcessLine("""{"type":"result","result":"done","session_id":"s1"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Equal(2, result.TurnCount);
|
||
}
|
||
|
||
[Fact]
|
||
public void Accumulates_Token_Usage()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"stream_event","event":{"type":"message_start","message":{"usage":{"input_tokens":100,"output_tokens":50}}}}""");
|
||
analyzer.ProcessLine("""{"type":"stream_event","event":{"type":"message_start","message":{"usage":{"input_tokens":200,"output_tokens":80}}}}""");
|
||
analyzer.ProcessLine("""{"type":"result","result":"done","session_id":"s1"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Equal(300, result.TokensIn);
|
||
Assert.Equal(130, result.TokensOut);
|
||
}
|
||
|
||
[Fact]
|
||
public void Counts_Api_Retry_Events()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"system","subtype":"api_retry","attempt":1,"error":"rate_limit"}""");
|
||
analyzer.ProcessLine("""{"type":"system","subtype":"api_retry","attempt":2,"error":"rate_limit"}""");
|
||
analyzer.ProcessLine("""{"type":"result","result":"done","session_id":"s1"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Equal(2, result.ApiRetryCount);
|
||
}
|
||
|
||
[Fact]
|
||
public void Malformed_Json_Is_Ignored()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("not json {{{");
|
||
analyzer.ProcessLine("");
|
||
analyzer.ProcessLine(" ");
|
||
var result = analyzer.GetResult();
|
||
Assert.Null(result.ResultMarkdown);
|
||
Assert.Equal(0, result.TurnCount);
|
||
}
|
||
|
||
[Fact]
|
||
public void No_Result_Event_Returns_Null_Fields()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"assistant","message":"hi"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Null(result.ResultMarkdown);
|
||
Assert.Null(result.SessionId);
|
||
}
|
||
|
||
[Fact]
|
||
public void Token_Usage_From_Result_Event()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"result","result":"done","session_id":"s1","usage":{"input_tokens":150,"output_tokens":75,"cache_read_input_tokens":0}}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Equal(150, result.TokensIn);
|
||
Assert.Equal(75, result.TokensOut);
|
||
}
|
||
|
||
[Fact]
|
||
public void Result_Usage_Overrides_Stream_Event_Accumulation()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"stream_event","event":{"type":"message_start","message":{"usage":{"input_tokens":10,"output_tokens":5}}}}""");
|
||
analyzer.ProcessLine("""{"type":"result","result":"done","session_id":"s1","usage":{"input_tokens":200,"output_tokens":90}}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Equal(200, result.TokensIn);
|
||
Assert.Equal(90, result.TokensOut);
|
||
}
|
||
|
||
[Fact]
|
||
public void Empty_Result_Falls_Back_To_Structured_Output_Summary()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"result","result":"","structured_output":{"summary":"Task completed successfully.","data":{}},"session_id":"s1"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Equal("Task completed successfully.", result.ResultMarkdown);
|
||
Assert.Contains("summary", result.StructuredOutputJson);
|
||
}
|
||
|
||
[Fact]
|
||
public void Empty_Result_Falls_Back_To_Full_Json_When_No_Summary()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"result","result":"","structured_output":{"output":"42"},"session_id":"s1"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Contains("output", result.ResultMarkdown);
|
||
Assert.Contains("42", result.ResultMarkdown);
|
||
}
|
||
|
||
[Fact]
|
||
public void Collects_Blocked_Markers_From_Assistant_Text()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"assistant","message":{"content":[{"type":"text","text":"working\nCLAUDEDO_BLOCKED: missing API key\nmoving on"}]}}""");
|
||
analyzer.ProcessLine("""{"type":"assistant","message":{"content":[{"type":"text","text":"CLAUDEDO_BLOCKED: cannot reach db"}]}}""");
|
||
analyzer.ProcessLine("""{"type":"result","result":"done","session_id":"s1"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Equal(2, result.Blocks.Count);
|
||
Assert.Equal("missing API key", result.Blocks[0]);
|
||
Assert.Equal("cannot reach db", result.Blocks[1]);
|
||
}
|
||
|
||
[Fact]
|
||
public void Strips_Blocked_Markers_From_Result_Text()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"result","result":"All set.\nCLAUDEDO_BLOCKED: no creds\nDone.","session_id":"s1"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.DoesNotContain("CLAUDEDO_BLOCKED", result.ResultMarkdown);
|
||
Assert.Single(result.Blocks);
|
||
Assert.Equal("no creds", result.Blocks[0]);
|
||
}
|
||
|
||
[Fact]
|
||
public void No_Markers_Means_Empty_Blocks()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"result","result":"done","session_id":"s1"}""");
|
||
Assert.Empty(analyzer.GetResult().Blocks);
|
||
}
|
||
|
||
[Fact]
|
||
public void Extracts_Terminal_Reason_And_Errors_For_Max_Turns()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"result","subtype":"error_max_turns","terminal_reason":"max_turns","errors":["Reached maximum number of turns (30)"],"session_id":"s1"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Equal("max_turns", result.TerminalReason);
|
||
Assert.Equal("error_max_turns", result.ResultSubtype);
|
||
Assert.Single(result.Errors);
|
||
Assert.Equal("Reached maximum number of turns (30)", result.Errors[0]);
|
||
}
|
||
|
||
[Fact]
|
||
public void Extracts_Terminal_Reason_And_Result_Text_For_Api_Error()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"result","terminal_reason":"api_error","result":"You've hit your session limit – resets 1pm (Europe/Berlin)","session_id":"s1"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Equal("api_error", result.TerminalReason);
|
||
Assert.Contains("session limit", result.ResultMarkdown);
|
||
}
|
||
|
||
[Fact]
|
||
public void No_Terminal_Reason_Fields_Means_Null_And_Empty()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"result","result":"done","session_id":"s1"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Null(result.TerminalReason);
|
||
Assert.Null(result.ResultSubtype);
|
||
Assert.Empty(result.Errors);
|
||
}
|
||
|
||
[Fact]
|
||
public void Extracts_Permission_Denials_From_Result_Event()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""
|
||
{"type":"result","result":"I need permission to edit the file.","session_id":"s1",
|
||
"permission_denials":[{"tool_name":"Edit","tool_use_id":"t1","tool_input":{}}]}
|
||
""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Single(result.PermissionDenials);
|
||
Assert.Equal("Edit", result.PermissionDenials[0]);
|
||
}
|
||
|
||
[Fact]
|
||
public void No_Permission_Denials_Field_Means_Empty_List()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"result","result":"done","session_id":"s1"}""");
|
||
Assert.Empty(analyzer.GetResult().PermissionDenials);
|
||
}
|
||
|
||
[Fact]
|
||
public void Multiple_Permission_Denials_Are_All_Collected()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""
|
||
{"type":"result","result":"blocked","session_id":"s1","permission_denials":[
|
||
{"tool_name":"Edit","tool_use_id":"t1","tool_input":{}},
|
||
{"tool_name":"Write","tool_use_id":"t2","tool_input":{}}
|
||
]}
|
||
""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Equal(new[] { "Edit", "Write" }, result.PermissionDenials);
|
||
}
|
||
|
||
[Fact]
|
||
public void Duplicate_Marker_In_Assistant_And_Result_Is_Collected_Once()
|
||
{
|
||
var analyzer = new StreamAnalyzer();
|
||
analyzer.ProcessLine("""{"type":"assistant","message":{"content":[{"type":"text","text":"CLAUDEDO_BLOCKED: no creds"}]}}""");
|
||
analyzer.ProcessLine("""{"type":"result","result":"summary\nCLAUDEDO_BLOCKED: no creds","session_id":"s1"}""");
|
||
var result = analyzer.GetResult();
|
||
Assert.Single(result.Blocks);
|
||
Assert.Equal("no creds", result.Blocks[0]);
|
||
}
|
||
}
|