44using ManagedCode . GeminiSharpSDK . Internal ;
55using ManagedCode . GeminiSharpSDK . Models ;
66using ManagedCode . GeminiSharpSDK . Tests . Shared ;
7+ using ManagedCode . GeminiSharpSDK . Tests . TestSupport ;
78
89namespace ManagedCode . GeminiSharpSDK . Tests . Integration ;
910
1011[ Property ( "RequiresGeminiAuth" , "true" ) ]
12+ [ ParallelLimiter < GeminiAuthParallelLimit > ]
1113public class RealGeminiIntegrationTests
1214{
13- private const string SolutionFileName = "ManagedCode.GeminiSharpSDK.slnx" ;
14- private const string TestsDirectoryName = "tests" ;
15- private const string SandboxDirectoryName = ".sandbox" ;
16- private const string SandboxPrefix = "RealGeminiIntegrationTests-SessionVisibility-" ;
17- private const string GitExecutableName = "git" ;
18- private const string GitInitArgument = "init" ;
19- private const string QuietArgument = "-q" ;
15+ private const string SandboxPrefix = "RealGeminiIntegrationTests" ;
16+ private const string SessionVisibilitySandboxPrefix = "RealGeminiIntegrationTests-SessionVisibility" ;
2017 private const string ListSessionsFlag = "--list-sessions" ;
2118 private const string GeminiDirectoryName = ".gemini" ;
2219 private const string ProjectsFileName = "projects.json" ;
@@ -34,9 +31,10 @@ public class RealGeminiIntegrationTests
3431 public async Task RunAsync_WithRealGeminiCli_ReturnsStructuredOutput ( )
3532 {
3633 var settings = RealGeminiTestSupport . GetRequiredSettings ( ) ;
34+ using var sandbox = await RealGeminiTestSandbox . CreateAsync ( SandboxPrefix , CliCommandTimeout ) ;
3735
3836 using var client = RealGeminiTestSupport . CreateClient ( ) ;
39- var thread = StartRealIntegrationThread ( client , settings . Model ) ;
37+ var thread = StartRealIntegrationThread ( client , settings . Model , sandbox . WorkingDirectory ) ;
4038
4139 using var cancellation = new CancellationTokenSource ( TimeSpan . FromMinutes ( 2 ) ) ;
4240 var schema = IntegrationOutputSchemas . StatusOnly ( ) ;
@@ -55,9 +53,10 @@ public async Task RunAsync_WithRealGeminiCli_ReturnsStructuredOutput()
5553 public async Task RunStreamedAsync_WithRealGeminiCli_YieldsCurrentStreamJsonEvents ( )
5654 {
5755 var settings = RealGeminiTestSupport . GetRequiredSettings ( ) ;
56+ using var sandbox = await RealGeminiTestSandbox . CreateAsync ( SandboxPrefix , CliCommandTimeout ) ;
5857
5958 using var client = RealGeminiTestSupport . CreateClient ( ) ;
60- var thread = StartRealIntegrationThread ( client , settings . Model ) ;
59+ var thread = StartRealIntegrationThread ( client , settings . Model , sandbox . WorkingDirectory ) ;
6160 using var cancellation = new CancellationTokenSource ( TimeSpan . FromMinutes ( 2 ) ) ;
6261
6362 var streamed = await thread . RunStreamedAsync (
@@ -85,9 +84,10 @@ public async Task RunStreamedAsync_WithRealGeminiCli_YieldsCurrentStreamJsonEven
8584 public async Task RunAsync_WithRealGeminiCli_SecondTurnKeepsThreadId ( )
8685 {
8786 var settings = RealGeminiTestSupport . GetRequiredSettings ( ) ;
87+ using var sandbox = await RealGeminiTestSandbox . CreateAsync ( SandboxPrefix , CliCommandTimeout ) ;
8888
8989 using var client = RealGeminiTestSupport . CreateClient ( ) ;
90- var thread = StartRealIntegrationThread ( client , settings . Model ) ;
90+ var thread = StartRealIntegrationThread ( client , settings . Model , sandbox . WorkingDirectory ) ;
9191 using var cancellation = new CancellationTokenSource ( TimeSpan . FromMinutes ( 3 ) ) ;
9292
9393 var schema = IntegrationOutputSchemas . StatusOnly ( ) ;
@@ -117,99 +117,47 @@ public async Task RunAsync_WithRealGeminiCli_SecondTurnKeepsThreadId()
117117 public async Task RunAsync_WithFreshWorkingDirectory_PersistsSessionVisibleToGeminiCli ( )
118118 {
119119 var settings = RealGeminiTestSupport . GetRequiredSettings ( ) ;
120- var sandboxDirectory = await CreateGitSandboxDirectoryAsync ( ) ;
120+ using var sandbox = await RealGeminiTestSandbox . CreateAsync (
121+ SessionVisibilitySandboxPrefix ,
122+ CliCommandTimeout ) ;
123+ var sandboxDirectory = sandbox . WorkingDirectory ;
121124
122- try
123- {
124- using var client = RealGeminiTestSupport . CreateClient ( ) ;
125- var thread = client . StartThread ( new ThreadOptions
126- {
127- Model = settings . Model ,
128- WorkingDirectory = sandboxDirectory ,
129- Ephemeral = false ,
130- } ) ;
131- using var cancellation = new CancellationTokenSource ( TimeSpan . FromMinutes ( 2 ) ) ;
132-
133- var result = await thread . RunAsync (
134- ProjectSessionVisiblePrompt ,
135- new TurnOptions { CancellationToken = cancellation . Token } ) ;
136-
137- await Assert . That ( result . Usage ) . IsNotNull ( ) ;
138- await Assert . That ( thread . Id ) . IsNotNull ( ) ;
139-
140- var persistedSessionPath = await FindPersistedSessionPathAsync (
141- sandboxDirectory ,
142- thread . Id ! ,
143- SessionVisibilityTimeout ) ;
144-
145- await Assert . That ( persistedSessionPath ) . IsNotNull ( ) ;
146-
147- var listSessionsResult = await RunGeminiAsync (
148- sandboxDirectory ,
149- CliCommandTimeout ,
150- ListSessionsFlag ) ;
151-
152- await Assert . That ( listSessionsResult . ExitCode ) . IsEqualTo ( 0 ) ;
153- await Assert . That ( string . Concat ( listSessionsResult . StandardOutput , listSessionsResult . StandardError ) )
154- . Contains ( thread . Id ! ) ;
155- }
156- finally
157- {
158- if ( Directory . Exists ( sandboxDirectory ) )
159- {
160- Directory . Delete ( sandboxDirectory , recursive : true ) ;
161- }
162- }
163- }
125+ using var client = RealGeminiTestSupport . CreateClient ( ) ;
126+ var thread = client . StartThread ( sandbox . CreateThreadOptions ( settings . Model , ephemeral : false ) ) ;
127+ using var cancellation = new CancellationTokenSource ( TimeSpan . FromMinutes ( 2 ) ) ;
164128
165- private static GeminiThread StartRealIntegrationThread ( GeminiClient client , string model )
166- {
167- return client . StartThread ( new ThreadOptions
168- {
169- Model = model ,
170- } ) ;
171- }
129+ var result = await thread . RunAsync (
130+ ProjectSessionVisiblePrompt ,
131+ new TurnOptions { CancellationToken = cancellation . Token } ) ;
172132
173- private static async Task < string > CreateGitSandboxDirectoryAsync ( )
174- {
175- var repositoryRoot = ResolveRepositoryRootPath ( ) ;
176- var sandboxDirectory = Path . Combine (
177- repositoryRoot ,
178- TestsDirectoryName ,
179- SandboxDirectoryName ,
180- $ "{ SandboxPrefix } { Guid . NewGuid ( ) : N} ") ;
181-
182- Directory . CreateDirectory ( sandboxDirectory ) ;
183- var gitInitResult = await RunCommand (
184- GitExecutableName ,
133+ await Assert . That ( result . Usage ) . IsNotNull ( ) ;
134+ await Assert . That ( thread . Id ) . IsNotNull ( ) ;
135+
136+ var persistedSessionPath = await FindPersistedSessionPathAsync (
185137 sandboxDirectory ,
186- CliCommandTimeout ,
187- GitInitArgument ,
188- QuietArgument ) ;
138+ thread . Id ! ,
139+ SessionVisibilityTimeout ) ;
189140
190- if ( gitInitResult . ExitCode != 0 )
191- {
192- throw new InvalidOperationException (
193- $ "Failed to initialize git sandbox: { gitInitResult . StandardError } ") ;
194- }
141+ await Assert . That ( persistedSessionPath ) . IsNotNull ( ) ;
142+
143+ var listSessionsResult = await RunGeminiAsync (
144+ sandboxDirectory ,
145+ CliCommandTimeout ,
146+ ListSessionsFlag ) ;
195147
196- return sandboxDirectory ;
148+ await Assert . That ( listSessionsResult . ExitCode ) . IsEqualTo ( 0 ) ;
149+ await Assert . That ( string . Concat ( listSessionsResult . StandardOutput , listSessionsResult . StandardError ) )
150+ . Contains ( thread . Id ! ) ;
197151 }
198152
199- private static string ResolveRepositoryRootPath ( )
153+ private static GeminiThread StartRealIntegrationThread ( GeminiClient client , string model , string workingDirectory )
200154 {
201- var current = new DirectoryInfo ( AppContext . BaseDirectory ) ;
202- while ( current is not null )
155+ return client . StartThread ( new ThreadOptions
203156 {
204- if ( File . Exists ( Path . Combine ( current . FullName , SolutionFileName ) ) )
205- {
206- return current . FullName ;
207- }
208-
209- current = current . Parent ;
210- }
211-
212- throw new InvalidOperationException ( "Could not locate repository root from test execution directory." ) ;
157+ Model = model ,
158+ WorkingDirectory = workingDirectory ,
159+ Ephemeral = false ,
160+ } ) ;
213161 }
214162
215163 private static async Task < string ? > FindPersistedSessionPathAsync (
0 commit comments