test Flutter agent SSE heartbeat handling
This commit is contained in:
@@ -1,7 +1,107 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_app/core/auth/session_token_store.dart';
|
||||
import 'package:flutter_app/core/network/api_repository.dart';
|
||||
|
||||
void main() {
|
||||
group('ApiRepository.runAgent SSE stream', () {
|
||||
test(
|
||||
'keeps progress through heartbeat and returns the done result',
|
||||
() async {
|
||||
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
|
||||
final seenRequests =
|
||||
<({String path, String? authorization, String body})>[];
|
||||
final serverDone = Completer<void>();
|
||||
server.listen((request) async {
|
||||
final body = await utf8.decoder.bind(request).join();
|
||||
seenRequests.add((
|
||||
path: request.uri.path,
|
||||
authorization: request.headers.value(
|
||||
HttpHeaders.authorizationHeader,
|
||||
),
|
||||
body: body,
|
||||
));
|
||||
|
||||
if (request.uri.path == '/v1/auth/local-session') {
|
||||
request.response.headers.contentType = ContentType.json;
|
||||
request.response.write(
|
||||
jsonEncode({
|
||||
'access_token': 'test-session',
|
||||
'user': {'mode': 'local'},
|
||||
}),
|
||||
);
|
||||
await request.response.close();
|
||||
return;
|
||||
}
|
||||
if (request.uri.path != '/v1/agent/run/stream') {
|
||||
request.response.statusCode = HttpStatus.notFound;
|
||||
await request.response.close();
|
||||
return;
|
||||
}
|
||||
|
||||
request.response.headers.contentType = ContentType(
|
||||
'text',
|
||||
'event-stream',
|
||||
charset: 'utf-8',
|
||||
);
|
||||
request.response.write(
|
||||
'event: progress\ndata: {"message":"بدأ البحث"}\n\n',
|
||||
);
|
||||
await request.response.flush();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 20));
|
||||
request.response.write('event: heartbeat\ndata: {}\n\n');
|
||||
await request.response.flush();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 20));
|
||||
request.response.write(
|
||||
'event: done\ndata: {"result":"اكتمل البحث","steps":[{"tool":"search_workspace","status":"completed"}],"files":["README.md"]}\n\n',
|
||||
);
|
||||
await request.response.close();
|
||||
if (!serverDone.isCompleted) serverDone.complete();
|
||||
});
|
||||
|
||||
final progress = <String>[];
|
||||
try {
|
||||
final repository = ApiRepository(
|
||||
baseUrl: 'http://${server.address.address}:${server.port}',
|
||||
sessionTokenStore: EphemeralSessionTokenStore(),
|
||||
);
|
||||
final result = await repository.runAgent(
|
||||
'اقرأ README',
|
||||
workspacePath: 'C:/workspace',
|
||||
workspaceFiles: const ['README.md'],
|
||||
skillId: 'code_explain',
|
||||
onProgress: progress.add,
|
||||
);
|
||||
await serverDone.future;
|
||||
|
||||
expect(result.answer, contains('اكتمل البحث'));
|
||||
expect(result.answer, contains('الملفات المقروءة: README.md.'));
|
||||
expect(progress, contains('بدأ البحث'));
|
||||
expect(
|
||||
progress,
|
||||
contains('الوكيل ما زال يعمل، أنتظر النموذج المحلي...'),
|
||||
);
|
||||
expect(seenRequests.map((request) => request.path), [
|
||||
'/v1/auth/local-session',
|
||||
'/v1/agent/run/stream',
|
||||
]);
|
||||
expect(seenRequests.last.authorization, 'Bearer test-session');
|
||||
expect(jsonDecode(seenRequests.last.body), {
|
||||
'task': 'اقرأ README',
|
||||
'workspace_path': 'C:/workspace',
|
||||
'workspace_files': ['README.md'],
|
||||
'skill_id': 'code_explain',
|
||||
});
|
||||
} finally {
|
||||
await server.close(force: true);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('AgentRunResult.fromApiData', () {
|
||||
test('preserves and labels every completed tool step', () {
|
||||
final result = AgentRunResult.fromApiData({
|
||||
|
||||
Reference in New Issue
Block a user