diff --git a/packages/devtools_app/lib/src/screens/network/har_data_entry.dart b/packages/devtools_app/lib/src/screens/network/har_data_entry.dart index bd9316d0ec3..0176d96256f 100644 --- a/packages/devtools_app/lib/src/screens/network/har_data_entry.dart +++ b/packages/devtools_app/lib/src/screens/network/har_data_entry.dart @@ -6,6 +6,7 @@ import 'dart:convert'; import 'dart:typed_data'; import '../../screens/network/utils/http_utils.dart'; +import '../../shared/http/constants.dart' as shared_http; import '../../shared/http/http_request_data.dart'; import 'constants.dart'; @@ -45,6 +46,15 @@ class HarDataEntry { final requestPostData = responseData[NetworkEventKeys.postData.name]; final responseContent = responseData[NetworkEventKeys.content.name]; + final statusValue = responseData[NetworkEventKeys.status.name]; + final statusCode = switch (statusValue) { + int() => statusValue, + String() => int.tryParse(statusValue), + _ => null, + }; + responseData[shared_http.HttpRequestDataKeys.statusCode.name] = + statusCode ?? -1; + return HarDataEntry( DartIOHttpRequestData.fromJson( modifiedRequestData, @@ -148,7 +158,7 @@ class HarDataEntry { }, // Response NetworkEventKeys.response.name: { - NetworkEventKeys.status.name: e.status, + NetworkEventKeys.status.name: int.tryParse(e.status ?? '') ?? -1, NetworkEventKeys.statusText.name: e.general[NetworkEventKeys.reasonPhrase.name] ?? '', NetworkEventKeys.httpVersion.name: diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index d428e865c5f..381298f27d5 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -40,7 +40,8 @@ TODO: Remove this section if there are not any updates. ## Network profiler updates -TODO: Remove this section if there are not any updates. +- Fixed exported response status in HAR files so that they parse as integers + instead of strings. [#9900](https://github.com/flutter/devtools/pull/9900) ## Logging updates diff --git a/packages/devtools_app/test/screens/network/har_network_test.dart b/packages/devtools_app/test/screens/network/har_network_test.dart index 98a8a7883a7..a1c735f095e 100644 --- a/packages/devtools_app/test/screens/network/har_network_test.dart +++ b/packages/devtools_app/test/screens/network/har_network_test.dart @@ -8,7 +8,6 @@ import 'dart:io'; import 'package:devtools_app/src/screens/network/constants.dart'; import 'package:devtools_app/src/screens/network/har_data_entry.dart'; import 'package:devtools_app/src/screens/network/har_network_data.dart'; - import 'package:flutter_test/flutter_test.dart'; void main() { @@ -58,10 +57,25 @@ void main() { }); group('HarDataEntry', () { + final entryJson = + ((jsonData['log'] as Map)['entries'] as List).first + as Map; + + Map copyWithStatus(Object? status) { + final copy = jsonDecode(jsonEncode(entryJson)) as Map; + final response = copy['response'] as Map; + response['status'] = status; + return copy; + } + + Map copyWithRequestError(String error) { + final copy = jsonDecode(jsonEncode(entryJson)) as Map; + final request = copy['request'] as Map; + request['error'] = error; + return copy; + } + test('fromJson parses correctly', () { - final entryJson = - ((jsonData['log'] as Map)['entries'] as List).first - as Map; final harDataEntry = HarDataEntry.fromJson(entryJson); expect( @@ -71,12 +85,10 @@ void main() { expect(harDataEntry.request.method, 'GET'); expect(harDataEntry.request.requestHeaders, isNotEmpty); expect(harDataEntry.request.requestCookies, isEmpty); + expect(harDataEntry.request.status, '200'); }); test('toJson serializes correctly', () { - final entryJson = - ((jsonData['log'] as Map)['entries'] as List).first - as Map; final harDataEntry = HarDataEntry.fromJson(entryJson); final json = HarDataEntry.toJson(harDataEntry.request); @@ -89,6 +101,11 @@ void main() { expect(request?['cookies'], isEmpty); expect(json['cache'], isEmpty); + final response = json['response'] as Map?; + expect(response, isNotNull); + expect(response?['status'], 200); + expect(response?['statusText'], ''); + final timings = json['timings'] as Map?; expect(timings?['blocked'], NetworkEventDefaults.blocked); expect(timings?['dns'], NetworkEventDefaults.dns); @@ -98,5 +115,68 @@ void main() { expect(timings?['ssl'], NetworkEventDefaults.ssl); expect(json['comment'], ''); }); + + test('handles "Error" status in JSON', () { + final errorJson = copyWithStatus('Error'); + final harDataEntry = HarDataEntry.fromJson(errorJson); + expect(harDataEntry.request.status, '-1'); + + final serialized = HarDataEntry.toJson(harDataEntry.request); + expect((serialized['response'] as Map)['status'], -1); + }); + + test('handles "Cancelled" status in JSON', () { + final cancelledJson = copyWithStatus('Cancelled'); + final harDataEntry = HarDataEntry.fromJson(cancelledJson); + expect(harDataEntry.request.status, '-1'); + + final serialized = HarDataEntry.toJson(harDataEntry.request); + expect((serialized['response'] as Map)['status'], -1); + }); + + test('handles -1 integer status in JSON', () { + final minusOneJson = copyWithStatus(-1); + final harDataEntry = HarDataEntry.fromJson(minusOneJson); + expect(harDataEntry.request.status, '-1'); + + final serialized = HarDataEntry.toJson(harDataEntry.request); + expect((serialized['response'] as Map)['status'], -1); + }); + + test('handles invalid string status in JSON', () { + final invalidJson = copyWithStatus('foo'); + final harDataEntry = HarDataEntry.fromJson(invalidJson); + expect(harDataEntry.request.status, '-1'); + + final serialized = HarDataEntry.toJson(harDataEntry.request); + expect((serialized['response'] as Map)['status'], -1); + }); + + test('handles null status in JSON', () { + final nullJson = copyWithStatus(null); + final harDataEntry = HarDataEntry.fromJson(nullJson); + expect(harDataEntry.request.status, '-1'); + + final serialized = HarDataEntry.toJson(harDataEntry.request); + expect((serialized['response'] as Map)['status'], -1); + }); + + test('handles request connection error', () { + final errorJson = copyWithRequestError('connection failed'); + final harDataEntry = HarDataEntry.fromJson(errorJson); + expect(harDataEntry.request.status, 'Error'); + + final serialized = HarDataEntry.toJson(harDataEntry.request); + expect((serialized['response'] as Map)['status'], -1); + }); + + test('handles request cancellation error', () { + final cancelledJson = copyWithRequestError('cancelled'); + final harDataEntry = HarDataEntry.fromJson(cancelledJson); + expect(harDataEntry.request.status, 'Cancelled'); + + final serialized = HarDataEntry.toJson(harDataEntry.request); + expect((serialized['response'] as Map)['status'], -1); + }); }); }