Skip to content

Commit 1a0eaeb

Browse files
authored
Merge pull request #908 from Udhay-Adithya/improve-dashbot-tests
improve dashbot tests
2 parents 3aef093 + 16638a1 commit 1a0eaeb

File tree

4 files changed

+2055
-589
lines changed

4 files changed

+2055
-589
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import 'package:flutter_test/flutter_test.dart';
2+
import 'package:flutter_riverpod/flutter_riverpod.dart';
3+
import 'package:apidash/dashbot/providers/providers.dart';
4+
import 'package:apidash/dashbot/models/models.dart' show ChatMessage;
5+
import 'package:apidash/dashbot/repository/repository.dart';
6+
import 'package:apidash/dashbot/constants.dart';
7+
import 'package:apidash/models/models.dart';
8+
import 'package:apidash/dashbot/services/agent/prompt_builder.dart';
9+
import 'package:apidash/providers/settings_providers.dart';
10+
import 'package:apidash_core/apidash_core.dart';
11+
import 'package:apidash/providers/collection_providers.dart';
12+
import '../../../../providers/helpers.dart';
13+
14+
/// AI-enabled flow tests for ChatViewmodel.
15+
///
16+
/// This file contains tests specifically for AI-enabled chat functionality,
17+
// A mock ChatRemoteRepository returning configurable responses
18+
class MockChatRemoteRepository extends ChatRemoteRepository {
19+
String? mockResponse;
20+
Exception? mockError;
21+
22+
@override
23+
Future<String?> sendChat({required AIRequestModel request}) async {
24+
if (mockError != null) throw mockError!;
25+
return mockResponse;
26+
}
27+
}
28+
29+
class _PromptCaptureBuilder extends PromptBuilder {
30+
final PromptBuilder _inner;
31+
_PromptCaptureBuilder(this._inner);
32+
String? lastSystemPrompt;
33+
34+
@override
35+
String buildSystemPrompt(RequestModel? req, ChatMessageType type,
36+
{String? overrideLanguage, List<ChatMessage> history = const []}) {
37+
final r = _inner.buildSystemPrompt(req, type,
38+
overrideLanguage: overrideLanguage, history: history);
39+
lastSystemPrompt = r;
40+
return r;
41+
}
42+
43+
@override
44+
String? detectLanguage(String text) => _inner.detectLanguage(text);
45+
46+
@override
47+
String getUserMessageForTask(ChatMessageType type) =>
48+
_inner.getUserMessageForTask(type);
49+
}
50+
51+
void main() {
52+
TestWidgetsFlutterBinding.ensureInitialized();
53+
late ProviderContainer container;
54+
late MockChatRemoteRepository mockRepo;
55+
late _PromptCaptureBuilder promptCapture;
56+
57+
setUp(() async {
58+
await testSetUpTempDirForHive();
59+
});
60+
61+
// Helper to obtain a default PromptBuilder by reading the real provider in a temp container
62+
PromptBuilder basePromptBuilder() {
63+
final temp = ProviderContainer();
64+
final pb = temp.read(promptBuilderProvider);
65+
temp.dispose();
66+
return pb;
67+
}
68+
69+
ProviderContainer createTestContainer(
70+
{String? aiExplanation, String? actionsJson}) {
71+
mockRepo = MockChatRemoteRepository();
72+
if (aiExplanation != null) {
73+
// Build a response optionally with actions
74+
final actionsPart = actionsJson ?? '[]';
75+
mockRepo.mockResponse =
76+
'{"explanation":"$aiExplanation","actions":$actionsPart}';
77+
}
78+
79+
// Proper AI model JSON matching AIRequestModel.fromJson keys
80+
final aiModelJson = {
81+
'modelApiProvider': 'openai',
82+
'model': 'gpt-test',
83+
'apiKey': 'sk-test',
84+
'system_prompt': '',
85+
'user_prompt': '',
86+
'model_configs': [],
87+
'stream': false,
88+
};
89+
90+
final baseSettings = SettingsModel(defaultAIModel: aiModelJson);
91+
promptCapture = _PromptCaptureBuilder(basePromptBuilder());
92+
93+
return createContainer(overrides: [
94+
chatRepositoryProvider.overrideWithValue(mockRepo),
95+
settingsProvider.overrideWith(
96+
(ref) => ThemeStateNotifier(settingsModel: baseSettings)),
97+
// Force no selected request so chat uses the stable 'global' session key
98+
selectedRequestModelProvider.overrideWith((ref) => null),
99+
promptBuilderProvider.overrideWith((ref) => promptCapture),
100+
]);
101+
}
102+
103+
group('ChatViewmodel AI Enabled Flow', () {
104+
test('processes valid AI explanation + actions list', () async {
105+
container = createTestContainer(
106+
aiExplanation: 'Here is your code',
107+
actionsJson:
108+
'[{"action":"other","target":"code","field":"generated","value":"print(\\"hi\\")"}]',
109+
);
110+
final vm = container.read(chatViewmodelProvider.notifier);
111+
112+
await vm.sendMessage(
113+
text: 'Generate code', type: ChatMessageType.generateCode);
114+
115+
final msgs = vm.currentMessages;
116+
// Expect exactly 2 messages: user + system response
117+
expect(msgs.length, equals(2));
118+
final user = msgs.first;
119+
final system = msgs.last;
120+
expect(user.role, MessageRole.user);
121+
expect(system.role, MessageRole.system);
122+
expect(system.actions, isNotNull);
123+
expect(system.actions!.length, equals(1));
124+
expect(system.content, contains('Here is your code'));
125+
expect(promptCapture.lastSystemPrompt, isNotNull);
126+
expect(promptCapture.lastSystemPrompt, contains('Generate'));
127+
});
128+
129+
test('handles empty AI response (adds fallback message)', () async {
130+
container = createTestContainer();
131+
mockRepo.mockResponse = ''; // Explicit empty
132+
final vm = container.read(chatViewmodelProvider.notifier);
133+
134+
await vm.sendMessage(
135+
text: 'Explain', type: ChatMessageType.explainResponse);
136+
137+
final msgs = vm.currentMessages;
138+
expect(msgs, isNotEmpty);
139+
expect(msgs.last.content, contains('No response'));
140+
});
141+
142+
test('handles null AI response (adds fallback message)', () async {
143+
container = createTestContainer();
144+
mockRepo.mockResponse = null; // Explicit null
145+
final vm = container.read(chatViewmodelProvider.notifier);
146+
await vm.sendMessage(text: 'Debug', type: ChatMessageType.debugError);
147+
final msgs = vm.currentMessages;
148+
expect(msgs, isNotEmpty);
149+
expect(msgs.last.content, contains('No response'));
150+
});
151+
152+
test('handles malformed actions field gracefully', () async {
153+
container = createTestContainer();
154+
mockRepo.mockResponse =
155+
'{"explanation":"Something","actions":"not-a-list"}';
156+
final vm = container.read(chatViewmodelProvider.notifier);
157+
await vm.sendMessage(
158+
text: 'Gen test', type: ChatMessageType.generateTest);
159+
final msgs = vm.currentMessages;
160+
expect(msgs, isNotEmpty);
161+
final sys = msgs.last;
162+
expect(sys.content, contains('Something'));
163+
});
164+
165+
test('handles malformed top-level JSON gracefully (no crash, fallback)',
166+
() async {
167+
container = createTestContainer();
168+
// This will cause MessageJson.safeParse to catch and ignore malformed content
169+
mockRepo.mockResponse =
170+
'{"explanation":"ok","actions": [ { invalid json }';
171+
final vm = container.read(chatViewmodelProvider.notifier);
172+
await vm.sendMessage(
173+
text: 'Gen code', type: ChatMessageType.generateCode);
174+
final msgs = vm.currentMessages;
175+
expect(msgs.length, equals(2)); // user + system with raw content
176+
expect(msgs.last.content, contains('explanation'));
177+
});
178+
179+
test('handles missing explanation key (still stores raw response)',
180+
() async {
181+
container = createTestContainer();
182+
mockRepo.mockResponse = '{"note":"Just a note","actions": []}';
183+
final vm = container.read(chatViewmodelProvider.notifier);
184+
await vm.sendMessage(
185+
text: 'Explain', type: ChatMessageType.explainResponse);
186+
final msgs = vm.currentMessages;
187+
expect(msgs.length, equals(2));
188+
expect(msgs.last.content, contains('note'));
189+
});
190+
191+
test('catches repository exception and appends error system message',
192+
() async {
193+
container = createTestContainer();
194+
mockRepo.mockError = Exception('boom');
195+
final vm = container.read(chatViewmodelProvider.notifier);
196+
await vm.sendMessage(text: 'Doc', type: ChatMessageType.generateDoc);
197+
final msgs = vm.currentMessages;
198+
expect(msgs, isNotEmpty);
199+
expect(msgs.last.content, contains('Error:'));
200+
});
201+
});
202+
}

0 commit comments

Comments
 (0)