1+ using System . Diagnostics ;
2+ using System . Globalization ;
13using ManagedCode . ClaudeCodeSharpSDK . Configuration ;
24using ManagedCode . ClaudeCodeSharpSDK . Extensions . AI . Internal ;
35using ManagedCode . ClaudeCodeSharpSDK . Models ;
@@ -30,10 +32,14 @@ public class StreamingEventMapperTests
3032 private const string TestsDirectoryName = "tests" ;
3133 private const string UserPrompt = "request" ;
3234 private const string ProviderFailureJson = "{\" type\" :\" result\" ,\" is_error\" :true,\" result\" :\" authentication failed\" }" ;
35+ private const string ProcessIdFileName = "root.pid" ;
36+ private const string ProcessIdEnvironmentName = "CLAUDE_TEST_ROOT_PID" ;
37+ private const string ExitCodeEnvironmentName = "CLAUDE_TEST_EXIT_CODE" ;
3338 private const string NodeScript = "const args = process.argv.slice(2);\n "
3439 + "if (args.includes('" + CliVersionFlag + "')) { console.log('" + MetadataScriptVersion + "'); process.exit(0); }\n "
40+ + "require('node:fs').writeFileSync(process.env." + ProcessIdEnvironmentName + ", String(process.pid));\n "
3541 + "process.stdin.resume();\n "
36- + "process.stdin.on('end', () => process.stdout.write('" + ProviderFailureJson + "' + '\\ n'));\n " ;
42+ + "process.stdin.on('end', () => process.stdout.write('" + ProviderFailureJson + "' + '\\ n', () => { process.exitCode = Number(process.env." + ExitCodeEnvironmentName + "); } ));\n ";
3743
3844 [ Test ]
3945 public async Task ToUpdates_MapsThreadStartAssistantMessageAndUsage ( )
@@ -125,12 +131,15 @@ await CollectUpdates(ProviderFailureEvents(() => tailConsumed = true, () => sour
125131 }
126132
127133 [ Test ]
128- public async Task ClaudeChatClient_ProviderFailureConfirmsOnlyAfterRealCliDisposal ( )
134+ [ Arguments ( 0 ) ]
135+ [ Arguments ( 1 ) ]
136+ public async Task ClaudeChatClient_ProviderFailureConfirmsOnlyAfterRealCliDisposal ( int exitCode )
129137 {
130138 var sandboxDirectory = Path . Combine ( Environment . CurrentDirectory , TestsDirectoryName , SandboxDirectoryName ,
131139 $ "{ Guid . NewGuid ( ) : N} ") ;
132140 Directory . CreateDirectory ( sandboxDirectory ) ;
133141 var scriptPath = Path . Combine ( sandboxDirectory , ScriptFileName ) ;
142+ var processIdPath = Path . Combine ( sandboxDirectory , ProcessIdFileName ) ;
134143 await File . WriteAllTextAsync ( scriptPath , NodeScript ) ;
135144
136145 try
@@ -141,6 +150,11 @@ public async Task ClaudeChatClient_ProviderFailureConfirmsOnlyAfterRealCliDispos
141150 {
142151 ClaudeExecutablePath = scriptPath ,
143152 ProcessTerminationTimeout = TimeSpan . FromSeconds ( 3 ) ,
153+ EnvironmentVariables = new Dictionary < string , string >
154+ {
155+ [ ProcessIdEnvironmentName ] = processIdPath ,
156+ [ ExitCodeEnvironmentName ] = exitCode . ToString ( CultureInfo . InvariantCulture ) ,
157+ } ,
144158 } ,
145159 } ) ;
146160 var action = async ( ) =>
@@ -153,8 +167,21 @@ [new ChatMessage(ChatRole.User, UserPrompt)]))
153167
154168 var exception = await Assert . That ( action ) . ThrowsException ( ) ;
155169 await Assert . That ( exception ) . IsTypeOf < CliExecutionFailureException > ( ) ;
156- await Assert . That ( ( ( CliExecutionFailureException ) exception ! ) . ExitCode ) . IsNull ( ) ;
170+ await Assert . That ( ( ( CliExecutionFailureException ) exception ! ) . ExitCode ) . IsEqualTo ( exitCode == 0 ? ( int ? ) null : exitCode ) ;
157171 await Assert . That ( ( ( CliExecutionFailureException ) exception ) . RootProcessExitConfirmed ) . IsTrue ( ) ;
172+ var processId = int . Parse ( await File . ReadAllTextAsync ( processIdPath ) , CultureInfo . InvariantCulture ) ;
173+ var processes = Process . GetProcesses ( ) ;
174+ try
175+ {
176+ await Assert . That ( processes . Any ( process => process . Id == processId ) ) . IsFalse ( ) ;
177+ }
178+ finally
179+ {
180+ foreach ( var process in processes )
181+ {
182+ process . Dispose ( ) ;
183+ }
184+ }
158185 }
159186 finally
160187 {
0 commit comments