@@ -21,54 +21,206 @@ public class ChatServiceSimpleTests
2121 private readonly Mock < IChatMetricsService > _mockMetricsService ;
2222 private readonly Mock < IConversationService > _mockConversationService ;
2323 private readonly Mock < ICharacterService > _mockCharacterService ;
24+ private readonly Mock < IServiceScopeFactory > _mockScopeFactory ;
25+ private readonly Mock < IServiceScope > _mockScope ;
26+ private readonly Mock < IServiceProvider > _mockServiceProvider ;
27+ private readonly Mock < ILogger < ChatService > > _mockLogger ;
2428
2529 public ChatServiceSimpleTests ( )
2630 {
2731 _mockMetricsService = new Mock < IChatMetricsService > ( ) ;
2832 _mockConversationService = new Mock < IConversationService > ( ) ;
2933 _mockCharacterService = new Mock < ICharacterService > ( ) ;
34+ _mockScopeFactory = new Mock < IServiceScopeFactory > ( ) ;
35+ _mockScope = new Mock < IServiceScope > ( ) ;
36+ _mockServiceProvider = new Mock < IServiceProvider > ( ) ;
37+ _mockLogger = new Mock < ILogger < ChatService > > ( ) ;
38+
39+ // Setup scope factory chain
40+ _mockScopeFactory . Setup ( x => x . CreateScope ( ) ) . Returns ( _mockScope . Object ) ;
41+ _mockScope . Setup ( x => x . ServiceProvider ) . Returns ( _mockServiceProvider . Object ) ;
42+ }
43+
44+ #region Service Scope Management Tests
45+
46+ [ Fact ]
47+ public void ChatService_Constructor_WithServiceScopeFactory_ShouldAcceptDependency ( )
48+ {
49+ // Arrange & Act & Assert
50+ var act = ( ) => CreateTestChatService ( ) ;
51+ act . Should ( ) . NotThrow ( "IServiceScopeFactory should be a valid dependency for ChatService" ) ;
3052 }
3153
32- [ Fact ( Skip = "ChatService has complex dependencies that cannot be easily mocked. Integration tests should be used instead." ) ]
33- public void ChatService_Constructor_ShouldNotThrow ( )
54+ [ Fact ]
55+ public void ServiceScopeFactory_CreateScope_ShouldReturnValidScope ( )
3456 {
35- // This test is skipped because ChatService depends on concrete classes without interfaces,
36- // making it difficult to unit test. The service should be refactored to depend on interfaces.
57+ // Arrange
58+ var mockScope = new Mock < IServiceScope > ( ) ;
59+ var mockServiceProvider = new Mock < IServiceProvider > ( ) ;
60+ mockScope . Setup ( x => x . ServiceProvider ) . Returns ( mockServiceProvider . Object ) ;
61+
62+ var mockScopeFactory = new Mock < IServiceScopeFactory > ( ) ;
63+ mockScopeFactory . Setup ( x => x . CreateScope ( ) ) . Returns ( mockScope . Object ) ;
64+
65+ // Act
66+ var scope = mockScopeFactory . Object . CreateScope ( ) ;
67+
68+ // Assert
69+ scope . Should ( ) . NotBeNull ( ) ;
70+ scope . ServiceProvider . Should ( ) . NotBeNull ( ) ;
71+ mockScopeFactory . Verify ( x => x . CreateScope ( ) , Times . Once ) ;
3772 }
3873
39- [ Fact ( Skip = "ChatService has complex dependencies that cannot be easily mocked. Integration tests should be used instead." ) ]
40- public async Task EnqueueChatRequestAsync_WithValidCommand_ShouldCallMetricsService ( )
74+ [ Fact ]
75+ public void ServiceScope_ShouldImplementIDisposable ( )
4176 {
42- // This test is skipped because ChatService depends on concrete classes without interfaces,
43- // making it difficult to unit test. The service should be refactored to depend on interfaces.
44- await Task . CompletedTask ;
77+ // Arrange
78+ var mockScope = new Mock < IServiceScope > ( ) ;
79+
80+ // Act & Assert
81+ mockScope . Object . Should ( ) . BeAssignableTo < IDisposable > ( "IServiceScope should be disposable for proper resource management" ) ;
4582 }
4683
47- private ChatService ? TryCreateChatService ( )
84+ [ Fact ]
85+ public void ServiceProvider_GetRequiredService_ShouldResolveServicesCorrectly ( )
4886 {
49- try
50- {
51- // Create a minimal service collection with all required services
52- var services = new ServiceCollection ( ) ;
53-
54- // Add required services with mocks
55- services . AddSingleton ( _mockMetricsService . Object ) ;
56- services . AddSingleton ( _mockConversationService . Object ) ;
57- services . AddSingleton ( _mockCharacterService . Object ) ;
58-
59- // Add logging
60- services . AddLogging ( builder => builder . AddConsole ( ) . SetMinimumLevel ( LogLevel . Warning ) ) ;
61-
62- // Note: Due to the complex dependency graph of ChatService with concrete classes,
63- // we cannot easily mock all dependencies. This is a limitation of the current design.
64- // For proper unit testing, the ChatService should depend on interfaces, not concrete classes.
65-
66- return null ; // Indicates that ChatService cannot be easily unit tested with its current design
67- }
68- catch
87+ // Arrange
88+ var mockChatSuccessHandler = new Mock < ChatSuccessHandler > ( ) ;
89+ var mockChatResultProcessor = new Mock < ChatResultProcessor > ( ) ;
90+
91+ _mockServiceProvider . Setup ( x => x . GetRequiredService < ChatSuccessHandler > ( ) )
92+ . Returns ( mockChatSuccessHandler . Object ) ;
93+ _mockServiceProvider . Setup ( x => x . GetRequiredService < ChatResultProcessor > ( ) )
94+ . Returns ( mockChatResultProcessor . Object ) ;
95+
96+ // Act
97+ var successHandler = _mockServiceProvider . Object . GetRequiredService < ChatSuccessHandler > ( ) ;
98+ var resultProcessor = _mockServiceProvider . Object . GetRequiredService < ChatResultProcessor > ( ) ;
99+
100+ // Assert
101+ successHandler . Should ( ) . NotBeNull ( ) ;
102+ resultProcessor . Should ( ) . NotBeNull ( ) ;
103+ successHandler . Should ( ) . BeSameAs ( mockChatSuccessHandler . Object ) ;
104+ resultProcessor . Should ( ) . BeSameAs ( mockChatResultProcessor . Object ) ;
105+ }
106+
107+ [ Fact ]
108+ public async Task ProcessChatRequestInternalAsync_ShouldCreateNewScopeForBackgroundTasks ( )
109+ {
110+ // This test verifies the concept of scope creation for background tasks
111+ // The actual method is private and complex, so we test the scope creation pattern
112+
113+ // Arrange
114+ var mockChatSuccessHandler = new Mock < ChatSuccessHandler > ( ) ;
115+ var mockChatResultProcessor = new Mock < ChatResultProcessor > ( ) ;
116+
117+ _mockServiceProvider . Setup ( x => x . GetRequiredService < ChatSuccessHandler > ( ) )
118+ . Returns ( mockChatSuccessHandler . Object ) ;
119+ _mockServiceProvider . Setup ( x => x . GetRequiredService < ChatResultProcessor > ( ) )
120+ . Returns ( mockChatResultProcessor . Object ) ;
121+
122+ // Act - Simulate the scope creation pattern used in ChatService
123+ using var scope = _mockScopeFactory . Object . CreateScope ( ) ;
124+ var successHandler = scope . ServiceProvider . GetRequiredService < ChatSuccessHandler > ( ) ;
125+ var resultProcessor = scope . ServiceProvider . GetRequiredService < ChatResultProcessor > ( ) ;
126+
127+ // Assert
128+ _mockScopeFactory . Verify ( x => x . CreateScope ( ) , Times . Once ) ;
129+ successHandler . Should ( ) . NotBeNull ( ) ;
130+ resultProcessor . Should ( ) . NotBeNull ( ) ;
131+
132+ // Verify both services come from the same scope
133+ _mockServiceProvider . Verify ( x => x . GetRequiredService < ChatSuccessHandler > ( ) , Times . Once ) ;
134+ _mockServiceProvider . Verify ( x => x . GetRequiredService < ChatResultProcessor > ( ) , Times . Once ) ;
135+
136+ await Task . CompletedTask ; // To satisfy async context
137+ }
138+
139+ [ Fact ]
140+ public void UsingScope_ShouldDisposeProperlyAndPreventObjectDisposedException ( )
141+ {
142+ // Arrange
143+ var disposeCalled = false ;
144+ _mockScope . Setup ( x => x . Dispose ( ) ) . Callback ( ( ) => disposeCalled = true ) ;
145+
146+ // Act
147+ using ( var scope = _mockScopeFactory . Object . CreateScope ( ) )
69148 {
70- return null ;
149+ scope . Should ( ) . NotBeNull ( ) ;
150+ disposeCalled . Should ( ) . BeFalse ( "Scope should not be disposed while in using block" ) ;
71151 }
152+
153+ // Assert
154+ disposeCalled . Should ( ) . BeTrue ( "Scope should be disposed after using block" ) ;
155+ _mockScope . Verify ( x => x . Dispose ( ) , Times . Once ) ;
156+ }
157+
158+ [ Fact ]
159+ public void ServiceScope_MultipleServiceResolution_ShouldUseSameProvider ( )
160+ {
161+ // This test verifies that multiple services resolved from the same scope
162+ // use the same service provider instance, preventing DbContext disposal issues
163+
164+ // Arrange
165+ var mockChatSuccessHandler = new Mock < ChatSuccessHandler > ( ) ;
166+ var mockChatResultProcessor = new Mock < ChatResultProcessor > ( ) ;
167+
168+ _mockServiceProvider . Setup ( x => x . GetRequiredService < ChatSuccessHandler > ( ) )
169+ . Returns ( mockChatSuccessHandler . Object ) ;
170+ _mockServiceProvider . Setup ( x => x . GetRequiredService < ChatResultProcessor > ( ) )
171+ . Returns ( mockChatResultProcessor . Object ) ;
172+
173+ // Act
174+ using var scope = _mockScopeFactory . Object . CreateScope ( ) ;
175+ var provider1 = scope . ServiceProvider ;
176+ var provider2 = scope . ServiceProvider ;
177+
178+ var service1 = provider1 . GetRequiredService < ChatSuccessHandler > ( ) ;
179+ var service2 = provider2 . GetRequiredService < ChatResultProcessor > ( ) ;
180+
181+ // Assert
182+ provider1 . Should ( ) . BeSameAs ( provider2 , "Same scope should always return same ServiceProvider" ) ;
183+ service1 . Should ( ) . NotBeNull ( ) ;
184+ service2 . Should ( ) . NotBeNull ( ) ;
185+
186+ // Both services should be resolved from the same provider instance
187+ _mockServiceProvider . Verify ( x => x . GetRequiredService < ChatSuccessHandler > ( ) , Times . Once ) ;
188+ _mockServiceProvider . Verify ( x => x . GetRequiredService < ChatResultProcessor > ( ) , Times . Once ) ;
72189 }
190+
191+ #endregion
192+
193+ #region Helper Methods
194+
195+ private ChatService CreateTestChatService ( )
196+ {
197+ // Create minimal mocks for all required dependencies
198+ var mockValidator = new Mock < ChatRequestValidator > ( ) ;
199+ var mockMemoryPreprocessor = new Mock < MemoryContextPreprocessor > ( ) ;
200+ var mockInputProcessor = new Mock < ICostTrackingDecorator < UserInputAnalysisProcessor > > ( ) ;
201+ var mockActionProcessor = new Mock < UserInputActionProcessor > ( ) ;
202+ var mockLLMProcessor = new Mock < ICostTrackingDecorator < ChatLLMProcessor > > ( ) ;
203+ var mockTTSProcessor = new Mock < ICostTrackingDecorator < ChatTTSProcessor > > ( ) ;
204+ var mockResultProcessor = new Mock < ChatResultProcessor > ( ) ;
205+ var mockFailureHandler = new Mock < ChatFailureHandler > ( ) ;
206+
207+ return new ChatService (
208+ _mockMetricsService . Object ,
209+ _mockScopeFactory . Object ,
210+ _mockLogger . Object ,
211+ _mockConversationService . Object ,
212+ _mockCharacterService . Object ,
213+ mockValidator . Object ,
214+ mockMemoryPreprocessor . Object ,
215+ mockInputProcessor . Object ,
216+ mockActionProcessor . Object ,
217+ mockLLMProcessor . Object ,
218+ mockTTSProcessor . Object ,
219+ mockResultProcessor . Object ,
220+ mockFailureHandler . Object
221+ ) ;
222+ }
223+
224+ #endregion
73225 }
74226}
0 commit comments