@@ -25,11 +25,11 @@ private static IConfiguration Cfg(string rev = "")
2525 private static ReviewingState Sut ( IMcpClient m , IChatClient l , string rev = "" ) =>
2626 new ( MkD ( m ) , l , Cfg ( rev ) , NullLogger < ReviewingState > . Instance ) ;
2727 private static GsdWorkflowContext PrCtx ( string diff = "d" ) =>
28- new ( ) { PrReview = new PrReviewContext ( 7 , "o" , "r" , diff ) , CurrentState = WorkflowState . Reviewing } ;
28+ new ( ) { PrReview = new PrReviewContext ( 7 , "o" , "r" , diff ) , CurrentState = WorkflowState . Reviewing } ;
2929 private static IChatClient LlmApprove ( )
3030 {
3131 var l = Substitute . For < IChatClient > ( ) ;
32- l . GetResponseAsync ( Arg . Any < IEnumerable < ChatMessage > > ( ) , Arg . Any < ChatOptions ? > ( ) , Arg . Any < CancellationToken > ( ) )
32+ l . GetResponseAsync ( Arg . Any < IEnumerable < ChatMessage > > ( ) , Arg . Any < ChatOptions ? > ( ) , Arg . Any < CancellationToken > ( ) )
3333 . Returns ( Task . FromResult ( new ChatResponse ( new ChatMessage ( ChatRole . Assistant ,
3434 "{\" verdict\" :\" APPROVE\" ,\" summary\" :\" ok\" ,\" comments\" :[]}" ) ) ) ) ;
3535 return l ;
@@ -39,96 +39,98 @@ private static IChatClient LlmApprove()
3939 public async Task GetPr_ReturnsError_FallsBackAndCompletes ( )
4040 {
4141 var m = Substitute . For < IMcpClient > ( ) ;
42- m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
43- . Returns ( Task . FromResult ( new McpToolResult ( "err" , true ) ) ) ;
44- m . CallToolAsync ( Arg . Is < string > ( "create_pull_request_review" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
45- . Returns ( Task . FromResult ( new McpToolResult ( "{}" , false ) ) ) ;
46- var res = await Sut ( m , LlmApprove ( ) ) . ExecuteAsync ( PrCtx ( ) , CancellationToken . None ) ;
47- Assert . Equal ( WorkflowState . Done , res . CurrentState ) ;
42+ m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
43+ . Returns ( Task . FromResult ( new McpToolResult ( "err" , true ) ) ) ;
44+ m . CallToolAsync ( Arg . Is < string > ( "create_pull_request_review" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
45+ . Returns ( Task . FromResult ( new McpToolResult ( "{}" , false ) ) ) ;
46+ var res = await Sut ( m , LlmApprove ( ) ) . ExecuteAsync ( PrCtx ( ) , CancellationToken . None ) ;
47+ Assert . Equal ( WorkflowState . Done , res . CurrentState ) ;
4848 }
4949
5050 [ Fact ]
5151 public async Task GetPr_ThrowsMcpException_FallsBackAndCompletes ( )
5252 {
5353 var m = Substitute . For < IMcpClient > ( ) ;
54- m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
55- . ThrowsAsync ( new McpException ( "notfound" , isTransient : false ) ) ;
56- m . CallToolAsync ( Arg . Is < string > ( "create_pull_request_review" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
57- . Returns ( Task . FromResult ( new McpToolResult ( "{}" , false ) ) ) ;
58- var res = await Sut ( m , LlmApprove ( ) ) . ExecuteAsync ( PrCtx ( ) , CancellationToken . None ) ;
59- Assert . Equal ( WorkflowState . Done , res . CurrentState ) ;
54+ m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
55+ . ThrowsAsync ( new McpException ( "notfound" , isTransient : false ) ) ;
56+ m . CallToolAsync ( Arg . Is < string > ( "create_pull_request_review" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
57+ . Returns ( Task . FromResult ( new McpToolResult ( "{}" , false ) ) ) ;
58+ var res = await Sut ( m , LlmApprove ( ) ) . ExecuteAsync ( PrCtx ( ) , CancellationToken . None ) ;
59+ Assert . Equal ( WorkflowState . Done , res . CurrentState ) ;
6060 }
6161
6262 [ Fact ]
6363 public async Task LlmThrows_AllAttempts_ThrowsInvalidOperationException ( )
6464 {
6565 var m = Substitute . For < IMcpClient > ( ) ;
66- m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
67- . Returns ( Task . FromResult ( new McpToolResult ( "{\" title\" :\" T\" ,\" body\" :\" \" }" , false ) ) ) ;
66+ m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
67+ . Returns ( Task . FromResult ( new McpToolResult ( "{\" title\" :\" T\" ,\" body\" :\" \" }" , false ) ) ) ;
6868 var l = Substitute . For < IChatClient > ( ) ;
69- l . GetResponseAsync ( Arg . Any < IEnumerable < ChatMessage > > ( ) , Arg . Any < ChatOptions ? > ( ) , Arg . Any < CancellationToken > ( ) )
69+ l . GetResponseAsync ( Arg . Any < IEnumerable < ChatMessage > > ( ) , Arg . Any < ChatOptions ? > ( ) , Arg . Any < CancellationToken > ( ) )
7070 . ThrowsAsync ( new HttpRequestException ( "down" ) ) ;
71- await Assert . ThrowsAsync < InvalidOperationException > ( ( ) => Sut ( m , l ) . ExecuteAsync ( PrCtx ( ) , CancellationToken . None ) ) ;
71+ await Assert . ThrowsAsync < InvalidOperationException > ( ( ) => Sut ( m , l ) . ExecuteAsync ( PrCtx ( ) , CancellationToken . None ) ) ;
7272 }
7373
7474 [ Fact ]
7575 public async Task LlmCanceled_PropagatesImmediately ( )
7676 {
7777 var m = Substitute . For < IMcpClient > ( ) ;
78- m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
79- . Returns ( Task . FromResult ( new McpToolResult ( "{\" title\" :\" T\" ,\" body\" :\" \" }" , false ) ) ) ;
78+ m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
79+ . Returns ( Task . FromResult ( new McpToolResult ( "{\" title\" :\" T\" ,\" body\" :\" \" }" , false ) ) ) ;
8080 using var cts = new CancellationTokenSource ( ) ;
8181 var l = Substitute . For < IChatClient > ( ) ;
82- l . GetResponseAsync ( Arg . Any < IEnumerable < ChatMessage > > ( ) , Arg . Any < ChatOptions ? > ( ) , Arg . Any < CancellationToken > ( ) )
83- . Returns < Task < ChatResponse > > ( ci=> {
82+ l . GetResponseAsync ( Arg . Any < IEnumerable < ChatMessage > > ( ) , Arg . Any < ChatOptions ? > ( ) , Arg . Any < CancellationToken > ( ) )
83+ . Returns < Task < ChatResponse > > ( ci =>
84+ {
8485 cts . Cancel ( ) ;
8586 ci . Arg < CancellationToken > ( ) . ThrowIfCancellationRequested ( ) ;
86- return Task . FromResult ( new ChatResponse ( new ChatMessage ( ChatRole . Assistant , "" ) ) ) ;
87+ return Task . FromResult ( new ChatResponse ( new ChatMessage ( ChatRole . Assistant , "" ) ) ) ;
8788 } ) ;
88- await Assert . ThrowsAsync < OperationCanceledException > ( ( ) => Sut ( m , l ) . ExecuteAsync ( PrCtx ( ) , cts . Token ) ) ;
89+ await Assert . ThrowsAsync < OperationCanceledException > ( ( ) => Sut ( m , l ) . ExecuteAsync ( PrCtx ( ) , cts . Token ) ) ;
8990 }
9091
9192 [ Fact ]
9293 public async Task SubmitReview_ReturnsError_ThrowsMcpException ( )
9394 {
9495 var m = Substitute . For < IMcpClient > ( ) ;
95- m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
96- . Returns ( Task . FromResult ( new McpToolResult ( "{\" title\" :\" T\" ,\" body\" :\" \" }" , false ) ) ) ;
97- m . CallToolAsync ( Arg . Is < string > ( "create_pull_request_review" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
98- . Returns ( Task . FromResult ( new McpToolResult ( "fail" , true ) ) ) ;
99- await Assert . ThrowsAsync < McpException > ( ( ) => Sut ( m , LlmApprove ( ) ) . ExecuteAsync ( PrCtx ( ) , CancellationToken . None ) ) ;
96+ m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
97+ . Returns ( Task . FromResult ( new McpToolResult ( "{\" title\" :\" T\" ,\" body\" :\" \" }" , false ) ) ) ;
98+ m . CallToolAsync ( Arg . Is < string > ( "create_pull_request_review" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
99+ . Returns ( Task . FromResult ( new McpToolResult ( "fail" , true ) ) ) ;
100+ await Assert . ThrowsAsync < McpException > ( ( ) => Sut ( m , LlmApprove ( ) ) . ExecuteAsync ( PrCtx ( ) , CancellationToken . None ) ) ;
100101 }
101102
102103 [ Fact ]
103104 public async Task WithReviewers_CompletesNormally ( )
104105 {
105106 var m = Substitute . For < IMcpClient > ( ) ;
106- m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
107- . Returns ( Task . FromResult ( new McpToolResult ( "{\" title\" :\" T\" ,\" body\" :\" \" }" , false ) ) ) ;
108- m . CallToolAsync ( Arg . Is < string > ( "create_pull_request_review" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
109- . Returns ( Task . FromResult ( new McpToolResult ( "{}" , false ) ) ) ;
110- var res = await Sut ( m , LlmApprove ( ) , rev : "alice,bob" ) . ExecuteAsync ( PrCtx ( ) , CancellationToken . None ) ;
111- Assert . Equal ( WorkflowState . Done , res . CurrentState ) ;
107+ m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
108+ . Returns ( Task . FromResult ( new McpToolResult ( "{\" title\" :\" T\" ,\" body\" :\" \" }" , false ) ) ) ;
109+ m . CallToolAsync ( Arg . Is < string > ( "create_pull_request_review" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
110+ . Returns ( Task . FromResult ( new McpToolResult ( "{}" , false ) ) ) ;
111+ var res = await Sut ( m , LlmApprove ( ) , rev : "alice,bob" ) . ExecuteAsync ( PrCtx ( ) , CancellationToken . None ) ;
112+ Assert . Equal ( WorkflowState . Done , res . CurrentState ) ;
112113 }
113114
114115 [ Fact ]
115116 public async Task LargeDiff_TruncatedBeforeLlm ( )
116117 {
117118 var m = Substitute . For < IMcpClient > ( ) ;
118- m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
119- . Returns ( Task . FromResult ( new McpToolResult ( "{\" title\" :\" T\" ,\" body\" :\" \" }" , false ) ) ) ;
120- m . CallToolAsync ( Arg . Is < string > ( "create_pull_request_review" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
121- . Returns ( Task . FromResult ( new McpToolResult ( "{}" , false ) ) ) ;
122- var big = new string ( '+' , 50_000 ) ;
119+ m . CallToolAsync ( Arg . Is < string > ( "get_pull_request" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
120+ . Returns ( Task . FromResult ( new McpToolResult ( "{\" title\" :\" T\" ,\" body\" :\" \" }" , false ) ) ) ;
121+ m . CallToolAsync ( Arg . Is < string > ( "create_pull_request_review" ) , Arg . Any < JsonObject > ( ) , Arg . Any < CancellationToken > ( ) )
122+ . Returns ( Task . FromResult ( new McpToolResult ( "{}" , false ) ) ) ;
123+ var big = new string ( '+' , 50_000 ) ;
123124 var seen = new List < string > ( ) ;
124125 var l = Substitute . For < IChatClient > ( ) ;
125- l . GetResponseAsync ( Arg . Any < IEnumerable < ChatMessage > > ( ) , Arg . Any < ChatOptions ? > ( ) , Arg . Any < CancellationToken > ( ) )
126- . Returns ( ci=> {
127- foreach ( var msg in ci . Arg < IEnumerable < ChatMessage > > ( ) ) if ( msg . Text != null ) seen . Add ( msg . Text ) ;
126+ l . GetResponseAsync ( Arg . Any < IEnumerable < ChatMessage > > ( ) , Arg . Any < ChatOptions ? > ( ) , Arg . Any < CancellationToken > ( ) )
127+ . Returns ( ci =>
128+ {
129+ foreach ( var msg in ci . Arg < IEnumerable < ChatMessage > > ( ) ) if ( msg . Text != null ) seen . Add ( msg . Text ) ;
128130 return Task . FromResult ( new ChatResponse ( new ChatMessage ( ChatRole . Assistant ,
129131 "{\" verdict\" :\" APPROVE\" ,\" summary\" :\" ok\" ,\" comments\" :[]}" ) ) ) ;
130132 } ) ;
131- await Sut ( m , l ) . ExecuteAsync ( PrCtx ( big ) , CancellationToken . None ) ;
132- Assert . Contains ( seen , s => s . Contains ( "[diff truncated" ) ) ;
133+ await Sut ( m , l ) . ExecuteAsync ( PrCtx ( big ) , CancellationToken . None ) ;
134+ Assert . Contains ( seen , s => s . Contains ( "[diff truncated" ) ) ;
133135 }
134136}
0 commit comments