From 74d47e76bceb064f730546f25abc7e833ad01df3 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 10:10:23 +0100 Subject: [PATCH 01/83] Add empty classes for envelopes --- dart/lib/src/sentry_envelope.dart | 3 +++ dart/lib/src/sentry_envelope_header.dart | 3 +++ dart/lib/src/sentry_envelope_item.dart | 1 + dart/lib/src/sentry_envelope_item_header.dart | 3 +++ dart/lib/src/sentry_item_type.dart | 3 +++ 5 files changed, 13 insertions(+) create mode 100644 dart/lib/src/sentry_envelope.dart create mode 100644 dart/lib/src/sentry_envelope_header.dart create mode 100644 dart/lib/src/sentry_envelope_item.dart create mode 100644 dart/lib/src/sentry_envelope_item_header.dart create mode 100644 dart/lib/src/sentry_item_type.dart diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart new file mode 100644 index 0000000000..ecd137ec83 --- /dev/null +++ b/dart/lib/src/sentry_envelope.dart @@ -0,0 +1,3 @@ +class SentryEnvelope { + +} diff --git a/dart/lib/src/sentry_envelope_header.dart b/dart/lib/src/sentry_envelope_header.dart new file mode 100644 index 0000000000..2e2c189d57 --- /dev/null +++ b/dart/lib/src/sentry_envelope_header.dart @@ -0,0 +1,3 @@ +class SentryEnvelopeHeader { + +} \ No newline at end of file diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart new file mode 100644 index 0000000000..f8b8017a3a --- /dev/null +++ b/dart/lib/src/sentry_envelope_item.dart @@ -0,0 +1 @@ +enum SentryItemType { event, unknown } diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart new file mode 100644 index 0000000000..b0a1e0e2c4 --- /dev/null +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -0,0 +1,3 @@ +class SentryEnvelopeItemHeader { + +} diff --git a/dart/lib/src/sentry_item_type.dart b/dart/lib/src/sentry_item_type.dart new file mode 100644 index 0000000000..5559e5a979 --- /dev/null +++ b/dart/lib/src/sentry_item_type.dart @@ -0,0 +1,3 @@ +class SentryItemType { + +} From 8e770aa43222fe534f9a19220302f9ff12ca5c40 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 10:41:28 +0100 Subject: [PATCH 02/83] Add properties --- dart/lib/src/sentry_envelope.dart | 8 +++++++- dart/lib/src/sentry_envelope_header.dart | 8 +++++++- dart/lib/src/sentry_envelope_item.dart | 11 ++++++++++- dart/lib/src/sentry_envelope_item_header.dart | 10 +++++++++- dart/lib/src/sentry_item_type.dart | 4 +--- 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index ecd137ec83..aaf049e2f4 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -1,3 +1,9 @@ +import 'sentry_envelope_header.dart'; +import 'sentry_envelope_item.dart'; + class SentryEnvelope { - + SentryEnvelope(this.header, this.items); + + final SentryEnvelopeHeader header; + final List items; } diff --git a/dart/lib/src/sentry_envelope_header.dart b/dart/lib/src/sentry_envelope_header.dart index 2e2c189d57..fe331a029f 100644 --- a/dart/lib/src/sentry_envelope_header.dart +++ b/dart/lib/src/sentry_envelope_header.dart @@ -1,3 +1,9 @@ +import 'protocol/sentry_id.dart'; +import 'protocol/sdk_version.dart'; + class SentryEnvelopeHeader { + SentryEnvelopeHeader(this.eventId, this.sdkVersion); -} \ No newline at end of file + final SentryId? eventId; + final SdkVersion? sdkVersion; +} diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index f8b8017a3a..2a1002b438 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -1 +1,10 @@ -enum SentryItemType { event, unknown } +import 'dart:typed_data'; + +import 'sentry_envelope_header.dart'; + +class SentryEnvelopeItem { + SentryEnvelopeItem(this.header, this.data); + + final SentryEnvelopeHeader header; + final ByteData data; +} diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart index b0a1e0e2c4..07b37e321a 100644 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -1,3 +1,11 @@ +import 'sentry_item_type.dart'; + class SentryEnvelopeItemHeader { - + SentryEnvelopeItemHeader( + this.contentType, this.fileName, this.type, this.length); + + final String? contentType; + final String? fileName; + final SentryItemType type; + final int length; } diff --git a/dart/lib/src/sentry_item_type.dart b/dart/lib/src/sentry_item_type.dart index 5559e5a979..f8b8017a3a 100644 --- a/dart/lib/src/sentry_item_type.dart +++ b/dart/lib/src/sentry_item_type.dart @@ -1,3 +1 @@ -class SentryItemType { - -} +enum SentryItemType { event, unknown } From 737c6f8d62c5f55428decf7e809db25b23a9dbe4 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 11:10:58 +0100 Subject: [PATCH 03/83] Create envelope classes from event --- dart/lib/src/sentry_envelope.dart | 9 ++++++++ dart/lib/src/sentry_envelope_item.dart | 22 ++++++++++++++----- dart/lib/src/sentry_envelope_item_header.dart | 9 ++++---- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index aaf049e2f4..8f29fcbac4 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -1,9 +1,18 @@ import 'sentry_envelope_header.dart'; import 'sentry_envelope_item.dart'; +import 'protocol/sentry_event.dart'; +import 'protocol/sdk_version.dart'; class SentryEnvelope { SentryEnvelope(this.header, this.items); final SentryEnvelopeHeader header; final List items; + + static SentryEnvelope fromEvent(SentryEvent event, SdkVersion sdkVersion) { + return SentryEnvelope( + SentryEnvelopeHeader(event.eventId, sdkVersion), + [SentryEnvelopeItem.fromEvent(event)] + ); + } } diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index 2a1002b438..c97d3376c2 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -1,10 +1,22 @@ -import 'dart:typed_data'; +import 'dart:convert'; -import 'sentry_envelope_header.dart'; +import 'sentry_item_type.dart'; +import 'protocol/sentry_event.dart'; +import 'sentry_envelope_item_header.dart'; class SentryEnvelopeItem { SentryEnvelopeItem(this.header, this.data); - - final SentryEnvelopeHeader header; - final ByteData data; + + final SentryEnvelopeItemHeader header; + final List data; + + // TODO(denis): Test formatting... + static SentryEnvelopeItem fromEvent(SentryEvent event) { + final jsonEncoded = jsonEncode(event.toJson()); + final data = utf8.encode(jsonEncoded); + return SentryEnvelopeItem( + SentryEnvelopeItemHeader(SentryItemType.event, data.length), + data + ); + } } diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart index 07b37e321a..cc685b4e2e 100644 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -1,11 +1,12 @@ import 'sentry_item_type.dart'; class SentryEnvelopeItemHeader { - SentryEnvelopeItemHeader( - this.contentType, this.fileName, this.type, this.length); + SentryEnvelopeItemHeader(this.type, this.length, + {this.contentType, this.fileName}); - final String? contentType; - final String? fileName; final SentryItemType type; final int length; + + final String? contentType; + final String? fileName; } From c0e4b6812498ae79f3bc27b747b695c15bcca806 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 11:20:36 +0100 Subject: [PATCH 04/83] introduce serialize method --- dart/lib/src/sentry_envelope.dart | 10 +++++---- dart/lib/src/sentry_envelope_header.dart | 12 +++++++---- dart/lib/src/sentry_envelope_item.dart | 4 ++++ dart/lib/src/sentry_envelope_item_header.dart | 4 ++++ dart/lib/src/sentry_item_type.dart | 21 +++++++++++++++++++ 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index 8f29fcbac4..e11d3833f4 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -10,9 +10,11 @@ class SentryEnvelope { final List items; static SentryEnvelope fromEvent(SentryEvent event, SdkVersion sdkVersion) { - return SentryEnvelope( - SentryEnvelopeHeader(event.eventId, sdkVersion), - [SentryEnvelopeItem.fromEvent(event)] - ); + return SentryEnvelope(SentryEnvelopeHeader(event.eventId, sdkVersion), + [SentryEnvelopeItem.fromEvent(event)]); + } + + String serialize() { + return ''; } } diff --git a/dart/lib/src/sentry_envelope_header.dart b/dart/lib/src/sentry_envelope_header.dart index fe331a029f..024caec7d1 100644 --- a/dart/lib/src/sentry_envelope_header.dart +++ b/dart/lib/src/sentry_envelope_header.dart @@ -2,8 +2,12 @@ import 'protocol/sentry_id.dart'; import 'protocol/sdk_version.dart'; class SentryEnvelopeHeader { - SentryEnvelopeHeader(this.eventId, this.sdkVersion); - - final SentryId? eventId; - final SdkVersion? sdkVersion; + SentryEnvelopeHeader(this.eventId, this.sdkVersion); + + final SentryId? eventId; + final SdkVersion? sdkVersion; + + String serialize() { + return ''; + } } diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index c97d3376c2..ae2f63fb28 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -19,4 +19,8 @@ class SentryEnvelopeItem { data ); } + + String serialize() { + return ''; + } } diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart index cc685b4e2e..32b74c7e5a 100644 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -9,4 +9,8 @@ class SentryEnvelopeItemHeader { final String? contentType; final String? fileName; + + String serialize() { + return ''; + } } diff --git a/dart/lib/src/sentry_item_type.dart b/dart/lib/src/sentry_item_type.dart index f8b8017a3a..2606faffc5 100644 --- a/dart/lib/src/sentry_item_type.dart +++ b/dart/lib/src/sentry_item_type.dart @@ -1 +1,22 @@ enum SentryItemType { event, unknown } + +extension SentryItemTypeExtension on SentryItemType { + + static SentryItemType fromStringValue(String stringValue) { + switch (stringValue) { + case 'event': + return SentryItemType.event; + default: + return SentryItemType.unknown; + } + } + + String toStringValue() { + switch (this) { + case SentryItemType.event: + return 'event'; + case SentryItemType.unknown: + return '__unknown__'; + } + } +} From 70150b237c03a768f0fc094d3eece76c68867a28 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 13:34:44 +0100 Subject: [PATCH 05/83] Serialize SentryEnvelopeItemHeader --- dart/lib/src/sentry_envelope_item_header.dart | 10 +++++++++- dart/test/sentry_envelope_item_header_test.dart | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 dart/test/sentry_envelope_item_header_test.dart diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart index 32b74c7e5a..6f2e8c4a85 100644 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'sentry_item_type.dart'; class SentryEnvelopeItemHeader { @@ -11,6 +13,12 @@ class SentryEnvelopeItemHeader { final String? fileName; String serialize() { - return ''; + final serializedMap = {}; + if (contentType != null) { + serializedMap['content_type'] = contentType!; + } + serializedMap['type'] = type.toStringValue(); + serializedMap['length'] = length; + return jsonEncode(serializedMap); } } diff --git a/dart/test/sentry_envelope_item_header_test.dart b/dart/test/sentry_envelope_item_header_test.dart new file mode 100644 index 0000000000..5ec3d83cb0 --- /dev/null +++ b/dart/test/sentry_envelope_item_header_test.dart @@ -0,0 +1,14 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_envelope_item_header.dart'; +import 'package:sentry/src/sentry_item_type.dart'; +import 'package:test/test.dart'; + +void main() { + group('SentryEnvelopeItemHeader', () { + test('serialize', () { + final sut = SentryEnvelopeItemHeader(SentryItemType.event, 3, contentType: 'application/json'); + final expected = '{\"content_type\":\"application/json\",\"type\":\"event\",\"length\":3}'; + expect(sut.serialize(), expected); + }); + }); +} From 446e04abf05c3cf5d0bd6d2d523a9c975e2b2cbc Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 13:42:34 +0100 Subject: [PATCH 06/83] Implement serialize of SentryEnvelopeItem --- dart/lib/src/sentry_envelope_item.dart | 2 +- .../test/sentry_envelope_item_header_test.dart | 1 - dart/test/sentry_envelope_item_test.dart | 18 ++++++++++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 dart/test/sentry_envelope_item_test.dart diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index ae2f63fb28..7cdb6c0eb1 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -21,6 +21,6 @@ class SentryEnvelopeItem { } String serialize() { - return ''; + return '${header.serialize()}\n${utf8.decode(data)}'; } } diff --git a/dart/test/sentry_envelope_item_header_test.dart b/dart/test/sentry_envelope_item_header_test.dart index 5ec3d83cb0..6105ee6b84 100644 --- a/dart/test/sentry_envelope_item_header_test.dart +++ b/dart/test/sentry_envelope_item_header_test.dart @@ -1,4 +1,3 @@ -import 'package:sentry/sentry.dart'; import 'package:sentry/src/sentry_envelope_item_header.dart'; import 'package:sentry/src/sentry_item_type.dart'; import 'package:test/test.dart'; diff --git a/dart/test/sentry_envelope_item_test.dart b/dart/test/sentry_envelope_item_test.dart new file mode 100644 index 0000000000..83ec25e4ca --- /dev/null +++ b/dart/test/sentry_envelope_item_test.dart @@ -0,0 +1,18 @@ +import 'package:sentry/src/sentry_envelope_item_header.dart'; +import 'package:sentry/src/sentry_envelope_item.dart'; +import 'package:sentry/src/sentry_item_type.dart'; +import 'package:test/test.dart'; + +void main() { + group('SentryEnvelopeItem', () { + test('serialize', () { + final header = SentryEnvelopeItemHeader(SentryItemType.event, 9, + contentType: 'application/json'); + final sut = SentryEnvelopeItem( + header, [123, 102, 105, 120, 116, 117, 114, 101, 125]); // {fixture} + + final expected = '${header.serialize()}\n{fixture}'; + expect(sut.serialize(), expected); + }); + }); +} From f8a839e080d2661f0db96951511f874624d14418 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 13:51:06 +0100 Subject: [PATCH 07/83] Implement serialize and fromEvent for SentryEnvelopeItem --- dart/lib/src/sentry_envelope_item.dart | 2 +- dart/test/sentry_envelope_item_test.dart | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index 7cdb6c0eb1..d1d39cb0ce 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -15,7 +15,7 @@ class SentryEnvelopeItem { final jsonEncoded = jsonEncode(event.toJson()); final data = utf8.encode(jsonEncoded); return SentryEnvelopeItem( - SentryEnvelopeItemHeader(SentryItemType.event, data.length), + SentryEnvelopeItemHeader(SentryItemType.event, data.length, contentType: 'application/json'), data ); } diff --git a/dart/test/sentry_envelope_item_test.dart b/dart/test/sentry_envelope_item_test.dart index 83ec25e4ca..5e026d090e 100644 --- a/dart/test/sentry_envelope_item_test.dart +++ b/dart/test/sentry_envelope_item_test.dart @@ -1,6 +1,10 @@ +import 'dart:convert'; + +import 'package:sentry/sentry.dart'; import 'package:sentry/src/sentry_envelope_item_header.dart'; import 'package:sentry/src/sentry_envelope_item.dart'; import 'package:sentry/src/sentry_item_type.dart'; +import 'package:sentry/src/protocol/sentry_id.dart'; import 'package:test/test.dart'; void main() { @@ -14,5 +18,18 @@ void main() { final expected = '${header.serialize()}\n{fixture}'; expect(sut.serialize(), expected); }); + + test('fromEvent', () { + final eventId = SentryId.newId(); + final sentryEvent = SentryEvent(eventId: eventId); + final sut = SentryEnvelopeItem.fromEvent(sentryEvent); + + final expectedData = utf8.encode(jsonEncode(sentryEvent.toJson())); + + expect(sut.header.contentType, 'application/json'); + expect(sut.header.type, SentryItemType.event); + expect(sut.header.length, expectedData.length); + expect(sut.data, expectedData); + }); }); } From 13826d58a06e99e98f00ffba0c4a3f4381b59a5a Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 14:04:04 +0100 Subject: [PATCH 08/83] Implement serialize of SentryEnvelopeItemHeader --- dart/lib/src/sentry_envelope_header.dart | 11 ++++++++- dart/test/sentry_envelope_header_test.dart | 27 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 dart/test/sentry_envelope_header_test.dart diff --git a/dart/lib/src/sentry_envelope_header.dart b/dart/lib/src/sentry_envelope_header.dart index 024caec7d1..91a278c851 100644 --- a/dart/lib/src/sentry_envelope_header.dart +++ b/dart/lib/src/sentry_envelope_header.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'protocol/sentry_id.dart'; import 'protocol/sdk_version.dart'; @@ -8,6 +10,13 @@ class SentryEnvelopeHeader { final SdkVersion? sdkVersion; String serialize() { - return ''; + final serializedMap = {}; + if (eventId != null) { + serializedMap['event_id'] = eventId!.toString(); + } + if (sdkVersion != null) { + serializedMap['sdk'] = sdkVersion!.toJson(); + } + return jsonEncode(serializedMap); } } diff --git a/dart/test/sentry_envelope_header_test.dart b/dart/test/sentry_envelope_header_test.dart new file mode 100644 index 0000000000..922b359a93 --- /dev/null +++ b/dart/test/sentry_envelope_header_test.dart @@ -0,0 +1,27 @@ +import 'dart:convert'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_envelope_header.dart'; +import 'package:test/test.dart'; + +void main() { + group('SentryEnvelopeItemHeader', () { + test('serialize empty', () { + final sut = SentryEnvelopeHeader(null, null); + final expected = '{}'; + expect(sut.serialize(), expected); + }); + + test('serialize', () { + final eventId = SentryId.newId(); + final sdkVersion = SdkVersion( + name: 'fixture-sdkName', + version: 'fixture-version', + ); + final sut = SentryEnvelopeHeader(eventId, sdkVersion); + final expextedSkd = jsonEncode(sdkVersion.toJson()); + final expected = '{\"event_id\":\"$eventId\",\"sdk\":$expextedSkd}'; + expect(sut.serialize(), expected); + }); + }); +} From 42b6072762fbfb05980085f2002fbff6a4c95e72 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 14:17:39 +0100 Subject: [PATCH 09/83] Implement SentryEnvelope --- dart/lib/src/sentry_envelope.dart | 7 ++++- dart/test/sentry_envelope_test.dart | 49 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 dart/test/sentry_envelope_test.dart diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index e11d3833f4..8d82bc3344 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -15,6 +15,11 @@ class SentryEnvelope { } String serialize() { - return ''; + final lines = []; + lines.add(header.serialize()); + for (final item in items) { + lines.add(item.serialize()); + } + return lines.join('\n'); } } diff --git a/dart/test/sentry_envelope_test.dart b/dart/test/sentry_envelope_test.dart new file mode 100644 index 0000000000..037307c19d --- /dev/null +++ b/dart/test/sentry_envelope_test.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_envelope.dart'; +import 'package:sentry/src/sentry_envelope_header.dart'; +import 'package:sentry/src/sentry_envelope_item_header.dart'; +import 'package:sentry/src/sentry_envelope_item.dart'; +import 'package:sentry/src/sentry_item_type.dart'; +import 'package:sentry/src/protocol/sentry_id.dart'; +import 'package:test/test.dart'; + +void main() { + group('SentryEnvelopeItem', () { + test('serialize', () { + final eventId = SentryId.newId(); + + final itemHeader = SentryEnvelopeItemHeader(SentryItemType.event, 9, + contentType: 'application/json'); + final item = + SentryEnvelopeItem(itemHeader, utf8.encode('{fixture}')); // {fixture} + + final header = SentryEnvelopeHeader(eventId, null); + final sut = SentryEnvelope(header, [item, item]); + + final expected = + '${header.serialize()}\n${itemHeader.serialize()}\n{fixture}\n${itemHeader.serialize()}\n{fixture}'; + expect(sut.serialize(), expected); + }); + + test('fromEvent', () { + final eventId = SentryId.newId(); + final sentryEvent = SentryEvent(eventId: eventId); + final sdkVersion = SdkVersion( + name: 'fixture-name', + version: 'fixture-version' + ); + final sut = SentryEnvelope.fromEvent(sentryEvent, sdkVersion); + + final expectedEnvelopeItem = SentryEnvelopeItem.fromEvent(sentryEvent); + + expect(sut.header.eventId, eventId); + expect(sut.header.sdkVersion, sdkVersion); + expect(sut.items[0].header.contentType, expectedEnvelopeItem.header.contentType); + expect(sut.items[0].header.type, expectedEnvelopeItem.header.type); + expect(sut.items[0].header.length, expectedEnvelopeItem.header.length); + expect(sut.items[0].data, expectedEnvelopeItem.data); + }); + }); +} From c99aa8c9815ad49afc445d673efd8f382166618e Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 14:53:06 +0100 Subject: [PATCH 10/83] Add rste limit category --- .../src/transport/rate_limit_category.dart | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 dart/lib/src/transport/rate_limit_category.dart diff --git a/dart/lib/src/transport/rate_limit_category.dart b/dart/lib/src/transport/rate_limit_category.dart new file mode 100644 index 0000000000..01f465dbb3 --- /dev/null +++ b/dart/lib/src/transport/rate_limit_category.dart @@ -0,0 +1,53 @@ +enum RateLimitCategory { + all, + rate_limit_default, // default + error, + session, + transaction, + attachment, + security, + unknown +} + +extension RateLimitCategoryExtension on RateLimitCategory { + static RateLimitCategory fromStringValue(String stringValue) { + switch (stringValue) { + case '__all__': + return RateLimitCategory.all; + case 'default': + return RateLimitCategory.rate_limit_default; + case 'error': + return RateLimitCategory.error; + case 'session': + return RateLimitCategory.session; + case 'transaction': + return RateLimitCategory.transaction; + case 'attachment': + return RateLimitCategory.attachment; + case 'security': + return RateLimitCategory.security; + } + return RateLimitCategory.unknown; + } + + String toStringValue() { + switch (this) { + case RateLimitCategory.all: + return '__all__'; + case RateLimitCategory.rate_limit_default: + return 'default'; + case RateLimitCategory.error: + return 'error'; + case RateLimitCategory.session: + return 'session'; + case RateLimitCategory.transaction: + return 'transaction'; + case RateLimitCategory.attachment: + return 'attachment'; + case RateLimitCategory.security: + return 'security'; + case RateLimitCategory.unknown: + return 'unknown'; + } + } +} From 47a01d7db12da0b6dffc99be84493bbb4c098db4 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 14:53:17 +0100 Subject: [PATCH 11/83] add empty rate limiter --- dart/lib/src/transport/rate_limiter.dart | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 dart/lib/src/transport/rate_limiter.dart diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart new file mode 100644 index 0000000000..701b3070d6 --- /dev/null +++ b/dart/lib/src/transport/rate_limiter.dart @@ -0,0 +1,5 @@ +/// Controls retry limits on different category types sent to Sentry. +class RateLimiter { + +} + From 0831a4cbafdcce666ec6f2991c15d627a91fa672 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 15:29:45 +0100 Subject: [PATCH 12/83] Add empty tests for rate limiter --- dart/lib/src/transport/rate_limiter.dart | 10 ++++- dart/test/protocol/rate_limiter_tests.dart | 48 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 dart/test/protocol/rate_limiter_tests.dart diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index 701b3070d6..19f06f4082 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -1,5 +1,11 @@ +import '../sentry_envelope.dart'; +import 'rate_limit_category.dart'; + /// Controls retry limits on different category types sent to Sentry. class RateLimiter { - -} + final rateLimitedUntil = {}; + SentryEnvelope? filter(SentryEnvelope envelope) { + return null; + } +} diff --git a/dart/test/protocol/rate_limiter_tests.dart b/dart/test/protocol/rate_limiter_tests.dart new file mode 100644 index 0000000000..7261218569 --- /dev/null +++ b/dart/test/protocol/rate_limiter_tests.dart @@ -0,0 +1,48 @@ +import 'package:sentry/src/transport/rate_limiter.dart'; +import 'package:test/test.dart'; + +void main() { + var fixture = Fixture(); + + setUp(() { + fixture = Fixture(); + }); + + group('RateLimiter Tests', () { + test('uses X-Sentry-Rate-Limit and allows sending if time has passed', () { + //TODO(denis): Implement test + }); + + test('parse X-Sentry-Rate-Limit and set its values and retry after should be true', () { + //TODO(denis): Implement test + }); + + test('parse X-Sentry-Rate-Limit and set its values and retry after should be false', () { + //TODO(denis): Implement test + }); + + test('When X-Sentry-Rate-Limit categories are empty, applies to all the categories', () { + //TODO(denis): Implement test + }); + + test('When all categories is set but expired, applies only for specific category', () { + //TODO(denis): Implement test + }); + + test('When category has shorter rate limiting, do not apply new timestamp', () { + //TODO(denis): Implement test + }); + + test('When category has longer rate limiting, apply new timestamp', () { + //TODO(denis): Implement test + }); + + test('When both retry headers are not present, default delay is set', () { + //TODO(denis): Implement test + }); + }); +} + +class Fixture { + final sut = RateLimiter(); +} From d9752d314235645c0b01a82504b91f74e2db5c73 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 16:05:51 +0100 Subject: [PATCH 13/83] parse rate limit header --- dart/lib/src/transport/rate_limit.dart | 39 ++++++++++++ .../src/transport/rate_limit_category.dart | 1 + dart/test/protocol/rate_limit_test.dart | 61 +++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 dart/lib/src/transport/rate_limit.dart create mode 100644 dart/test/protocol/rate_limit_test.dart diff --git a/dart/lib/src/transport/rate_limit.dart b/dart/lib/src/transport/rate_limit.dart new file mode 100644 index 0000000000..1cc8dd43ba --- /dev/null +++ b/dart/lib/src/transport/rate_limit.dart @@ -0,0 +1,39 @@ +import 'rate_limit_category.dart'; + +class RateLimit { + RateLimit(this.durationInMillis, this.category); + + final RateLimitCategory category; + final int durationInMillis; + + static List parseRateLimitHeader(String rateLimitHeader) { + final rateLimits = []; + + final rateLimitValues = rateLimitHeader.toLowerCase().split(','); + for (final rateLimitValue in rateLimitValues) { + final durationAndCategories = rateLimitValue.trim().split(':'); + + if (durationAndCategories.isNotEmpty) { + final durationInMillis = int.parse(durationAndCategories[0]); + + if (durationAndCategories.length > 1) { + final categoryValues = durationAndCategories[1].split(';'); + for (final categoryValue in categoryValues) { + final category = + RateLimitCategoryExtension.fromStringValue(categoryValue); + if (category != RateLimitCategory.unknown) { + rateLimits.add(RateLimit(durationInMillis, category)); + } + } + } else { + rateLimits.add(RateLimit(durationInMillis, RateLimitCategory.all)); + } + } + } + return rateLimits; + } + + static List parseRetryAfterHeader(String retryAfterHeader) { + return []; + } +} diff --git a/dart/lib/src/transport/rate_limit_category.dart b/dart/lib/src/transport/rate_limit_category.dart index 01f465dbb3..027aad8b76 100644 --- a/dart/lib/src/transport/rate_limit_category.dart +++ b/dart/lib/src/transport/rate_limit_category.dart @@ -10,6 +10,7 @@ enum RateLimitCategory { } extension RateLimitCategoryExtension on RateLimitCategory { + static RateLimitCategory fromStringValue(String stringValue) { switch (stringValue) { case '__all__': diff --git a/dart/test/protocol/rate_limit_test.dart b/dart/test/protocol/rate_limit_test.dart new file mode 100644 index 0000000000..263d07b5b7 --- /dev/null +++ b/dart/test/protocol/rate_limit_test.dart @@ -0,0 +1,61 @@ +import 'package:sentry/src/transport/rate_limit.dart'; +import 'package:sentry/src/transport/rate_limit_category.dart'; +import 'package:test/test.dart'; + +void main() { + group('parseRateLimitHeader', () { + test('single rate limit with single category', () { + final sut = RateLimit.parseRateLimitHeader('50:transaction'); + + expect(sut.length, 1); + expect(sut[0].category, RateLimitCategory.transaction); + expect(sut[0].durationInMillis, 50); + }); + + test('single rate limit with multiple categories', () { + final sut = RateLimit.parseRateLimitHeader('50:transaction;session'); + + expect(sut.length, 2); + + expect(sut[0].category, RateLimitCategory.transaction); + expect(sut[0].durationInMillis, 50); + + expect(sut[1].category, RateLimitCategory.session); + expect(sut[1].durationInMillis, 50); + }); + + test('don`t apply rate limit for unknown categories ', () { + final sut = RateLimit.parseRateLimitHeader('50:somethingunknown'); + + expect(sut.length, 0); + }); + + test('apply all if there are no categories', () { + final sut = RateLimit.parseRateLimitHeader('50'); + + expect(sut.length, 1); + expect(sut[0].category, RateLimitCategory.all); + expect(sut[0].durationInMillis, 50); + }); + + test('multiple rate limits', () { + final sut = RateLimit.parseRateLimitHeader('50:transaction, 70:session'); + + expect(sut.length, 2); + + expect(sut[0].category, RateLimitCategory.transaction); + expect(sut[0].durationInMillis, 50); + + expect(sut[1].category, RateLimitCategory.session); + expect(sut[1].durationInMillis, 70); + }); + + test('ignore case', () { + final sut = RateLimit.parseRateLimitHeader('50:TRANSACTION'); + + expect(sut.length, 1); + expect(sut[0].category, RateLimitCategory.transaction); + expect(sut[0].durationInMillis, 50); + }); + }); +} From e60c8d4a3dd22f457cf1bb67e69165a0d538dacc Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 16:28:34 +0100 Subject: [PATCH 14/83] Implement RateLimitParser --- dart/lib/src/transport/rate_limit.dart | 37 +++++++++++++++++-- dart/test/protocol/rate_limit_test.dart | 48 +++++++++++++++++++++---- 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/dart/lib/src/transport/rate_limit.dart b/dart/lib/src/transport/rate_limit.dart index 1cc8dd43ba..333c1e555d 100644 --- a/dart/lib/src/transport/rate_limit.dart +++ b/dart/lib/src/transport/rate_limit.dart @@ -3,8 +3,24 @@ import 'rate_limit_category.dart'; class RateLimit { RateLimit(this.durationInMillis, this.category); + static const HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS = 60000; + final RateLimitCategory category; final int durationInMillis; +} + +extension RateLimitParser on RateLimit { + + static List parse( + String? rateLimitHeader, String? retryAfterHeader, int errorCode) { + if (rateLimitHeader != null) { + return parseRateLimitHeader(rateLimitHeader); + } else if (errorCode == 429) { + return parseRetryAfterHeader(retryAfterHeader); + } else { + return []; + } + } static List parseRateLimitHeader(String rateLimitHeader) { final rateLimits = []; @@ -14,7 +30,8 @@ class RateLimit { final durationAndCategories = rateLimitValue.trim().split(':'); if (durationAndCategories.isNotEmpty) { - final durationInMillis = int.parse(durationAndCategories[0]); + final durationInMillis = + _parseRetryAfterOrDefault(durationAndCategories[0]); if (durationAndCategories.length > 1) { final categoryValues = durationAndCategories[1].split(';'); @@ -33,7 +50,21 @@ class RateLimit { return rateLimits; } - static List parseRetryAfterHeader(String retryAfterHeader) { - return []; + static List parseRetryAfterHeader(String? retryAfterHeader) { + return [ + RateLimit( + _parseRetryAfterOrDefault(retryAfterHeader), RateLimitCategory.all) + ]; + } + + // Helper + + static int _parseRetryAfterOrDefault(String? value) { + final durationInSeconds = int.tryParse(value ?? ''); + if (durationInSeconds != null) { + return durationInSeconds * 1000; + } else { + return RateLimit.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS; + } } } diff --git a/dart/test/protocol/rate_limit_test.dart b/dart/test/protocol/rate_limit_test.dart index 263d07b5b7..3786d3ce11 100644 --- a/dart/test/protocol/rate_limit_test.dart +++ b/dart/test/protocol/rate_limit_test.dart @@ -9,7 +9,7 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, 50); + expect(sut[0].durationInMillis, 50000); }); test('single rate limit with multiple categories', () { @@ -18,10 +18,10 @@ void main() { expect(sut.length, 2); expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, 50); + expect(sut[0].durationInMillis, 50000); expect(sut[1].category, RateLimitCategory.session); - expect(sut[1].durationInMillis, 50); + expect(sut[1].durationInMillis, 50000); }); test('don`t apply rate limit for unknown categories ', () { @@ -35,7 +35,7 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.all); - expect(sut[0].durationInMillis, 50); + expect(sut[0].durationInMillis, 50000); }); test('multiple rate limits', () { @@ -44,10 +44,10 @@ void main() { expect(sut.length, 2); expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, 50); + expect(sut[0].durationInMillis, 50000); expect(sut[1].category, RateLimitCategory.session); - expect(sut[1].durationInMillis, 70); + expect(sut[1].durationInMillis, 70000); }); test('ignore case', () { @@ -55,7 +55,41 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, 50); + expect(sut[0].durationInMillis, 50000); + }); + + test('un-parseable returns default duration', () { + final sut = RateLimit.parseRateLimitHeader('foobar:transaction'); + + expect(sut.length, 1); + expect(sut[0].category, RateLimitCategory.transaction); + expect(sut[0].durationInMillis, RateLimit.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + }); + }); + + group('parseRetryAfterHeader', () { + test('null returns default category all with default duration', () { + final sut = RateLimit.parseRetryAfterHeader(null); + + expect(sut.length, 1); + expect(sut[0].category, RateLimitCategory.all); + expect(sut[0].durationInMillis, RateLimit.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + }); + + test('parseable returns default category with duration in millis', () { + final sut = RateLimit.parseRetryAfterHeader('8'); + + expect(sut.length, 1); + expect(sut[0].category, RateLimitCategory.all); + expect(sut[0].durationInMillis, 8000); + }); + + test('un-parseable returns default category with default duration', () { + final sut = RateLimit.parseRetryAfterHeader('foobar'); + + expect(sut.length, 1); + expect(sut[0].category, RateLimitCategory.all); + expect(sut[0].durationInMillis, RateLimit.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); }); }); } From 5136095b191ceb681b2eaf5f48ba9ea1b3d09a22 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 26 Mar 2021 16:38:43 +0100 Subject: [PATCH 15/83] Move out decision what to parse --- dart/lib/src/transport/rate_limit.dart | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/dart/lib/src/transport/rate_limit.dart b/dart/lib/src/transport/rate_limit.dart index 333c1e555d..a21cf4c19b 100644 --- a/dart/lib/src/transport/rate_limit.dart +++ b/dart/lib/src/transport/rate_limit.dart @@ -10,17 +10,6 @@ class RateLimit { } extension RateLimitParser on RateLimit { - - static List parse( - String? rateLimitHeader, String? retryAfterHeader, int errorCode) { - if (rateLimitHeader != null) { - return parseRateLimitHeader(rateLimitHeader); - } else if (errorCode == 429) { - return parseRetryAfterHeader(retryAfterHeader); - } else { - return []; - } - } static List parseRateLimitHeader(String rateLimitHeader) { final rateLimits = []; From 2c44b1581c9b6f10e45c96c0c054256269b9bc72 Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 29 Mar 2021 10:07:57 +0200 Subject: [PATCH 16/83] Own class for RateLimitParser, return map instead of list of RateLimit objects --- ...rate_limit.dart => rate_limit_parser.dart} | 32 +++---- .../test/protocol/rate_limit_parser_test.dart | 80 ++++++++++++++++ dart/test/protocol/rate_limit_test.dart | 95 ------------------- 3 files changed, 96 insertions(+), 111 deletions(-) rename dart/lib/src/transport/{rate_limit.dart => rate_limit_parser.dart} (61%) create mode 100644 dart/test/protocol/rate_limit_parser_test.dart delete mode 100644 dart/test/protocol/rate_limit_test.dart diff --git a/dart/lib/src/transport/rate_limit.dart b/dart/lib/src/transport/rate_limit_parser.dart similarity index 61% rename from dart/lib/src/transport/rate_limit.dart rename to dart/lib/src/transport/rate_limit_parser.dart index a21cf4c19b..3c2adaf633 100644 --- a/dart/lib/src/transport/rate_limit.dart +++ b/dart/lib/src/transport/rate_limit_parser.dart @@ -1,18 +1,19 @@ import 'rate_limit_category.dart'; -class RateLimit { - RateLimit(this.durationInMillis, this.category); +class RateLimitParser { + RateLimitParser(this.header); static const HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS = 60000; - final RateLimitCategory category; - final int durationInMillis; -} + String? header; -extension RateLimitParser on RateLimit { + Map parseRateLimitHeader() { + final rateLimitHeader = header; + if (rateLimitHeader == null) { + return {}; + } - static List parseRateLimitHeader(String rateLimitHeader) { - final rateLimits = []; + final rateLimits = {}; final rateLimitValues = rateLimitHeader.toLowerCase().split(','); for (final rateLimitValue in rateLimitValues) { @@ -28,22 +29,21 @@ extension RateLimitParser on RateLimit { final category = RateLimitCategoryExtension.fromStringValue(categoryValue); if (category != RateLimitCategory.unknown) { - rateLimits.add(RateLimit(durationInMillis, category)); + rateLimits[category] = durationInMillis; } } } else { - rateLimits.add(RateLimit(durationInMillis, RateLimitCategory.all)); + rateLimits[RateLimitCategory.all] = durationInMillis; } } } return rateLimits; } - static List parseRetryAfterHeader(String? retryAfterHeader) { - return [ - RateLimit( - _parseRetryAfterOrDefault(retryAfterHeader), RateLimitCategory.all) - ]; + Map parseRetryAfterHeader() { + return { + RateLimitCategory.all: _parseRetryAfterOrDefault(header) + }; } // Helper @@ -53,7 +53,7 @@ extension RateLimitParser on RateLimit { if (durationInSeconds != null) { return durationInSeconds * 1000; } else { - return RateLimit.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS; + return RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS; } } } diff --git a/dart/test/protocol/rate_limit_parser_test.dart b/dart/test/protocol/rate_limit_parser_test.dart new file mode 100644 index 0000000000..edcc3f5ccf --- /dev/null +++ b/dart/test/protocol/rate_limit_parser_test.dart @@ -0,0 +1,80 @@ +import 'package:sentry/src/transport/rate_limit_parser.dart'; +import 'package:sentry/src/transport/rate_limit_category.dart'; +import 'package:test/test.dart'; + +void main() { + group('parseRateLimitHeader', () { + test('single rate limit with single category', () { + final sut = RateLimitParser('50:transaction').parseRateLimitHeader(); + + expect(sut.length, 1); + expect(sut[RateLimitCategory.transaction], 50000); + }); + + test('single rate limit with multiple categories', () { + final sut = RateLimitParser('50:transaction;session').parseRateLimitHeader(); + + expect(sut.length, 2); + expect(sut[RateLimitCategory.transaction], 50000); + expect(sut[RateLimitCategory.session], 50000); + }); + + test('don`t apply rate limit for unknown categories ', () { + final sut = RateLimitParser('50:somethingunknown').parseRateLimitHeader(); + + expect(sut.length, 0); + }); + + test('apply all if there are no categories', () { + final sut = RateLimitParser('50').parseRateLimitHeader(); + + expect(sut.length, 1); + expect(sut[RateLimitCategory.all], 50000); + }); + + test('multiple rate limits', () { + final sut = RateLimitParser('50:transaction, 70:session').parseRateLimitHeader(); + + expect(sut.length, 2); + expect(sut[RateLimitCategory.transaction], 50000); + expect(sut[RateLimitCategory.session], 70000); + }); + + test('ignore case', () { + final sut = RateLimitParser('50:TRANSACTION').parseRateLimitHeader(); + + expect(sut.length, 1); + expect(sut[RateLimitCategory.transaction], 50000); + }); + + test('un-parseable returns default duration', () { + final sut = RateLimitParser('foobar:transaction').parseRateLimitHeader(); + + expect(sut.length, 1); + expect(sut[RateLimitCategory.transaction], RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + }); + }); + + group('parseRetryAfterHeader', () { + test('null returns default category all with default duration', () { + final sut = RateLimitParser(null).parseRetryAfterHeader(); + + expect(sut.length, 1); + expect(sut[RateLimitCategory.all], RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + }); + + test('parseable returns default category with duration in millis', () { + final sut = RateLimitParser('8').parseRetryAfterHeader(); + + expect(sut.length, 1); + expect(sut[RateLimitCategory.all], 8000); + }); + + test('un-parseable returns default category with default duration', () { + final sut = RateLimitParser('foobar').parseRetryAfterHeader(); + + expect(sut.length, 1); + expect(sut[RateLimitCategory.all], RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + }); + }); +} diff --git a/dart/test/protocol/rate_limit_test.dart b/dart/test/protocol/rate_limit_test.dart deleted file mode 100644 index 3786d3ce11..0000000000 --- a/dart/test/protocol/rate_limit_test.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'package:sentry/src/transport/rate_limit.dart'; -import 'package:sentry/src/transport/rate_limit_category.dart'; -import 'package:test/test.dart'; - -void main() { - group('parseRateLimitHeader', () { - test('single rate limit with single category', () { - final sut = RateLimit.parseRateLimitHeader('50:transaction'); - - expect(sut.length, 1); - expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, 50000); - }); - - test('single rate limit with multiple categories', () { - final sut = RateLimit.parseRateLimitHeader('50:transaction;session'); - - expect(sut.length, 2); - - expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, 50000); - - expect(sut[1].category, RateLimitCategory.session); - expect(sut[1].durationInMillis, 50000); - }); - - test('don`t apply rate limit for unknown categories ', () { - final sut = RateLimit.parseRateLimitHeader('50:somethingunknown'); - - expect(sut.length, 0); - }); - - test('apply all if there are no categories', () { - final sut = RateLimit.parseRateLimitHeader('50'); - - expect(sut.length, 1); - expect(sut[0].category, RateLimitCategory.all); - expect(sut[0].durationInMillis, 50000); - }); - - test('multiple rate limits', () { - final sut = RateLimit.parseRateLimitHeader('50:transaction, 70:session'); - - expect(sut.length, 2); - - expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, 50000); - - expect(sut[1].category, RateLimitCategory.session); - expect(sut[1].durationInMillis, 70000); - }); - - test('ignore case', () { - final sut = RateLimit.parseRateLimitHeader('50:TRANSACTION'); - - expect(sut.length, 1); - expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, 50000); - }); - - test('un-parseable returns default duration', () { - final sut = RateLimit.parseRateLimitHeader('foobar:transaction'); - - expect(sut.length, 1); - expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, RateLimit.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); - }); - }); - - group('parseRetryAfterHeader', () { - test('null returns default category all with default duration', () { - final sut = RateLimit.parseRetryAfterHeader(null); - - expect(sut.length, 1); - expect(sut[0].category, RateLimitCategory.all); - expect(sut[0].durationInMillis, RateLimit.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); - }); - - test('parseable returns default category with duration in millis', () { - final sut = RateLimit.parseRetryAfterHeader('8'); - - expect(sut.length, 1); - expect(sut[0].category, RateLimitCategory.all); - expect(sut[0].durationInMillis, 8000); - }); - - test('un-parseable returns default category with default duration', () { - final sut = RateLimit.parseRetryAfterHeader('foobar'); - - expect(sut.length, 1); - expect(sut[0].category, RateLimitCategory.all); - expect(sut[0].durationInMillis, RateLimit.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); - }); - }); -} From a34e1c3dd63cb43b2b2560fc6c9295ea2d598f0f Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 29 Mar 2021 11:30:18 +0200 Subject: [PATCH 17/83] Port java rate limiter tests --- dart/lib/src/current_date_provider.dart | 5 + dart/lib/src/sentry_envelope_header.dart | 3 + dart/lib/src/transport/rate_limiter.dart | 10 ++ dart/test/mocks.dart | 5 + .../mock_current_data_time_provider.dart | 15 ++ dart/test/protocol/rate_limiter_test.dart | 151 ++++++++++++++++++ dart/test/protocol/rate_limiter_tests.dart | 48 ------ 7 files changed, 189 insertions(+), 48 deletions(-) create mode 100644 dart/lib/src/current_date_provider.dart create mode 100644 dart/test/mocks/mock_current_data_time_provider.dart create mode 100644 dart/test/protocol/rate_limiter_test.dart delete mode 100644 dart/test/protocol/rate_limiter_tests.dart diff --git a/dart/lib/src/current_date_provider.dart b/dart/lib/src/current_date_provider.dart new file mode 100644 index 0000000000..3572860546 --- /dev/null +++ b/dart/lib/src/current_date_provider.dart @@ -0,0 +1,5 @@ +class CurrentDateTimeProvider { + int currentDateTime() { + return DateTime.now().millisecondsSinceEpoch; + } +} diff --git a/dart/lib/src/sentry_envelope_header.dart b/dart/lib/src/sentry_envelope_header.dart index 91a278c851..897b15b976 100644 --- a/dart/lib/src/sentry_envelope_header.dart +++ b/dart/lib/src/sentry_envelope_header.dart @@ -5,6 +5,9 @@ import 'protocol/sdk_version.dart'; class SentryEnvelopeHeader { SentryEnvelopeHeader(this.eventId, this.sdkVersion); + SentryEnvelopeHeader.newEventId() + : eventId = SentryId.newId(), + sdkVersion = null; final SentryId? eventId; final SdkVersion? sdkVersion; diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index 19f06f4082..a75693eb86 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -1,11 +1,21 @@ +import '../current_date_provider.dart'; import '../sentry_envelope.dart'; import 'rate_limit_category.dart'; /// Controls retry limits on different category types sent to Sentry. class RateLimiter { + RateLimiter(this.currentDateTimeProvider); + + final CurrentDateTimeProvider currentDateTimeProvider; final rateLimitedUntil = {}; SentryEnvelope? filter(SentryEnvelope envelope) { return null; } + + void updateRetryAfterLimits( + String? sentryRateLimitHeader, String? retryAfterHeader, int errorCode) { + + + } } diff --git a/dart/test/mocks.dart b/dart/test/mocks.dart index e1d8b87d5c..7fb9a03ba8 100644 --- a/dart/test/mocks.dart +++ b/dart/test/mocks.dart @@ -1,5 +1,10 @@ +import 'package:mockito/annotations.dart'; import 'package:sentry/sentry.dart'; import 'package:sentry/src/protocol.dart'; +import 'package:sentry/src/current_date_provider.dart'; + +@GenerateMocks([CurrentDateTimeProvider]) +void main() {} final fakeDsn = 'https://abc@def.ingest.sentry.io/1234567'; diff --git a/dart/test/mocks/mock_current_data_time_provider.dart b/dart/test/mocks/mock_current_data_time_provider.dart new file mode 100644 index 0000000000..76f3c59266 --- /dev/null +++ b/dart/test/mocks/mock_current_data_time_provider.dart @@ -0,0 +1,15 @@ +import 'package:sentry/src/current_date_provider.dart'; + +class MockCurrentDateTimeProvider implements CurrentDateTimeProvider { + var dateTimesToReturn = []; + + @override + int currentDateTime() { + // TODO: implement currentDateTime + if (dateTimesToReturn.length > 1) { + return dateTimesToReturn.removeAt(0); + } else { + return dateTimesToReturn.first; + } + } +} diff --git a/dart/test/protocol/rate_limiter_test.dart b/dart/test/protocol/rate_limiter_test.dart new file mode 100644 index 0000000000..69279a6e8d --- /dev/null +++ b/dart/test/protocol/rate_limiter_test.dart @@ -0,0 +1,151 @@ +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +import 'package:sentry/src/transport/rate_limiter.dart'; +import 'package:sentry/src/protocol/sentry_event.dart'; +import 'package:sentry/src/sentry_envelope.dart'; +import 'package:sentry/src/sentry_envelope_header.dart'; +import 'package:sentry/src/sentry_envelope_item.dart'; + +import '../mocks/mock_current_data_time_provider.dart'; + +void main() { + var fixture = Fixture(); + + setUp(() { + fixture = Fixture(); + }); + + test('uses X-Sentry-Rate-Limit and allows sending if time has passed', () { + final rateLimiter = fixture.getSUT(); + fixture.currentDateProvider.dateTimesToReturn = [0, 0, 1001]; + + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + + rateLimiter.updateRetryAfterLimits( + '50:transaction:key, 1:default;error;security:organization', null, 1); + + final result = rateLimiter.filter(envelope); + expect(result, isNotNull); + expect(result!.items.length, 1); + }); + + // test( + // 'parse X-Sentry-Rate-Limit and set its values and retry after should be true', + // () { + // final rateLimiter = fixture.getSUT(); + // fixture.currentDateProvider.dateTimesToReturn = [0]; + // final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + // final transaction = SentryTransaction( + // SentryTracer(TransactionContext('name', 'op'), mock())); + // final transactionItem = + // SentryEnvelopeItem.fromEvent(fixture.serializer, transaction); + // final envelope = SentryEnvelope( + // SentryEnvelopeHeader.newEventId(), [eventItem, transactionItem]); + + // rateLimiter.updateRetryAfterLimits( + // "50:transaction:key, 2700:default;error;security:organization", + // null, + // 1); + + // final result = rateLimiter.filter(envelope); + // expect(result, isNull); + // }); + + // test( + // 'parse X-Sentry-Rate-Limit and set its values and retry after should be false', + // () { + // final rateLimiter = fixture.getSUT(); + // fixture.currentDateProvider.dateTimesToReturn = [0, 0, 1001]; + // final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + // final transaction = SentryTransaction( + // SentryTracer(TransactionContext('name', 'op'), mock())); + // final transactionItem = SentryEnvelopeItem.fromEvent(transaction); + // final envelope = SentryEnvelope( + // SentryEnvelopeHeader.newEventId(), [eventItem, transactionItem]); + + // rateLimiter.updateRetryAfterLimits( + // '1:transaction:key, 1:default;error;security:organization', null, 1); + + // final result = rateLimiter.filter(envelope); + // expect(result, isNotNull); + // expect(2, result!.items.length); + // }); + + test( + 'When X-Sentry-Rate-Limit categories are empty, applies to all the categories', + () { + final rateLimiter = fixture.getSUT(); + fixture.currentDateProvider.dateTimesToReturn = [0]; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + + rateLimiter.updateRetryAfterLimits('50::key', null, 1); + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + + test( + 'When all categories is set but expired, applies only for specific category', + () { + final rateLimiter = fixture.getSUT(); + fixture.currentDateProvider.dateTimesToReturn = [0, 0, 1001]; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + + rateLimiter.updateRetryAfterLimits( + '1::key, 60:default;error;security:organization', null, 1); + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + + test('When category has shorter rate limiting, do not apply new timestamp', + () { + final rateLimiter = fixture.getSUT(); + fixture.currentDateProvider.dateTimesToReturn = [0, 0, 1001]; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + + rateLimiter.updateRetryAfterLimits( + '60:error:key, 1:error:organization', null, 1); + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + + test('When category has longer rate limiting, apply new timestamp', () { + final rateLimiter = fixture.getSUT(); + fixture.currentDateProvider.dateTimesToReturn = [0, 0, 1001]; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + + rateLimiter.updateRetryAfterLimits( + '1:error:key, 5:error:organization', null, 1); + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + + test('When both retry headers are not present, default delay is set', () { + final rateLimiter = fixture.getSUT(); + fixture.currentDateProvider.dateTimesToReturn = [0, 0, 1001]; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + + rateLimiter.updateRetryAfterLimits(null, null, 429); + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); +} + +class Fixture { + final currentDateProvider = MockCurrentDateTimeProvider(); + + RateLimiter getSUT() { + return RateLimiter(currentDateProvider); + } +} diff --git a/dart/test/protocol/rate_limiter_tests.dart b/dart/test/protocol/rate_limiter_tests.dart deleted file mode 100644 index 7261218569..0000000000 --- a/dart/test/protocol/rate_limiter_tests.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:sentry/src/transport/rate_limiter.dart'; -import 'package:test/test.dart'; - -void main() { - var fixture = Fixture(); - - setUp(() { - fixture = Fixture(); - }); - - group('RateLimiter Tests', () { - test('uses X-Sentry-Rate-Limit and allows sending if time has passed', () { - //TODO(denis): Implement test - }); - - test('parse X-Sentry-Rate-Limit and set its values and retry after should be true', () { - //TODO(denis): Implement test - }); - - test('parse X-Sentry-Rate-Limit and set its values and retry after should be false', () { - //TODO(denis): Implement test - }); - - test('When X-Sentry-Rate-Limit categories are empty, applies to all the categories', () { - //TODO(denis): Implement test - }); - - test('When all categories is set but expired, applies only for specific category', () { - //TODO(denis): Implement test - }); - - test('When category has shorter rate limiting, do not apply new timestamp', () { - //TODO(denis): Implement test - }); - - test('When category has longer rate limiting, apply new timestamp', () { - //TODO(denis): Implement test - }); - - test('When both retry headers are not present, default delay is set', () { - //TODO(denis): Implement test - }); - }); -} - -class Fixture { - final sut = RateLimiter(); -} From ab88d4442f233a78e277dd9523ae3b266fe6b482 Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 29 Mar 2021 13:46:15 +0200 Subject: [PATCH 18/83] implement rate limiter, fix rate limit parser --- dart/lib/src/transport/rate_limit_parser.dart | 23 ++-- dart/lib/src/transport/rate_limiter.dart | 112 +++++++++++++++++- .../mock_current_data_time_provider.dart | 9 +- .../test/protocol/rate_limit_parser_test.dart | 2 +- dart/test/protocol/rate_limiter_test.dart | 23 ++-- 5 files changed, 141 insertions(+), 28 deletions(-) diff --git a/dart/lib/src/transport/rate_limit_parser.dart b/dart/lib/src/transport/rate_limit_parser.dart index 3c2adaf633..74cc1044d1 100644 --- a/dart/lib/src/transport/rate_limit_parser.dart +++ b/dart/lib/src/transport/rate_limit_parser.dart @@ -24,16 +24,19 @@ class RateLimitParser { _parseRetryAfterOrDefault(durationAndCategories[0]); if (durationAndCategories.length > 1) { - final categoryValues = durationAndCategories[1].split(';'); - for (final categoryValue in categoryValues) { - final category = - RateLimitCategoryExtension.fromStringValue(categoryValue); - if (category != RateLimitCategory.unknown) { - rateLimits[category] = durationInMillis; + final allCategories = durationAndCategories[1]; + if (allCategories.isNotEmpty) { + final categoryValues = durationAndCategories[1].split(';'); + for (final categoryValue in categoryValues) { + final category = + RateLimitCategoryExtension.fromStringValue(categoryValue); + if (category != RateLimitCategory.unknown) { + rateLimits[category] = durationInMillis; + } } + } else { + rateLimits[RateLimitCategory.all] = durationInMillis; } - } else { - rateLimits[RateLimitCategory.all] = durationInMillis; } } } @@ -41,9 +44,7 @@ class RateLimitParser { } Map parseRetryAfterHeader() { - return { - RateLimitCategory.all: _parseRetryAfterOrDefault(header) - }; + return {RateLimitCategory.all: _parseRetryAfterOrDefault(header)}; } // Helper diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index a75693eb86..b62a1af52b 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -1,5 +1,9 @@ +import '../transport/rate_limit_parser.dart'; + import '../current_date_provider.dart'; import '../sentry_envelope.dart'; +import '../sentry_envelope_item.dart'; +import '../sentry_item_type.dart'; import 'rate_limit_category.dart'; /// Controls retry limits on different category types sent to Sentry. @@ -10,12 +14,116 @@ class RateLimiter { final rateLimitedUntil = {}; SentryEnvelope? filter(SentryEnvelope envelope) { - return null; + // Optimize for/No allocations if no items are under 429 + List? dropItems; + for (final item in envelope.items) { + // using the raw value of the enum to not expose SentryEnvelopeItemType + if (_isRetryAfter(item.header.type.toStringValue())) { + dropItems ??= []; + dropItems.add(item); + } + } + + if (dropItems != null) { + //logger.log(SentryLevel.INFO, "%d items will be dropped due rate limiting.", dropItems.size()); + + // Need a new envelope + final toSend = []; + for (final item in envelope.items) { + if (!dropItems.contains(item)) { + toSend.add(item); + } + } + + // no reason to continue + if (toSend.isEmpty) { + //logger.log(SentryLevel.INFO, "Envelope discarded due all items rate limited."); + + //markHintWhenSendingFailed(hint, false); + return null; + } + + return SentryEnvelope(envelope.header, toSend); + } else { + return envelope; + } } void updateRetryAfterLimits( String? sentryRateLimitHeader, String? retryAfterHeader, int errorCode) { + final currentDateTime = currentDateTimeProvider.currentDateTime(); + var rateLimits = {}; + + if (sentryRateLimitHeader != null) { + rateLimits = + RateLimitParser(sentryRateLimitHeader).parseRateLimitHeader(); + } else if (errorCode == 429) { + rateLimits = + RateLimitParser(sentryRateLimitHeader).parseRetryAfterHeader(); + } + + for (final rateLimitEntry in rateLimits.entries) { + _applyRetryAfterOnlyIfLonger( + rateLimitEntry.key, + DateTime.fromMillisecondsSinceEpoch( + currentDateTime + rateLimitEntry.value)); + } + } + + // Private + + bool _isRetryAfter(String itemType) { + final dataCategory = _categoryFromItemType(itemType); + final currentDate = DateTime.fromMillisecondsSinceEpoch( + currentDateTimeProvider.currentDateTime()); + + // check all categories + final dateAllCategories = rateLimitedUntil[RateLimitCategory.all]; + if (dateAllCategories != null) { + if (!(currentDate.millisecondsSinceEpoch > + dateAllCategories.millisecondsSinceEpoch)) { + return true; + } + } + + // Unknown should not be rate limited + if (RateLimitCategory.unknown == dataCategory) { + return false; + } + + // check for specific dataCategory + final dateCategory = rateLimitedUntil[dataCategory]; + if (dateCategory != null) { + return !(currentDate.millisecondsSinceEpoch > + dateCategory.millisecondsSinceEpoch); + } + + return false; + } + + RateLimitCategory _categoryFromItemType(String itemType) { + switch (itemType) { + case 'event': + return RateLimitCategory.error; + case 'session': + return RateLimitCategory.session; + case 'attachment': + return RateLimitCategory.attachment; + case 'transaction': + return RateLimitCategory.transaction; + default: + return RateLimitCategory.unknown; + } + } + + void _applyRetryAfterOnlyIfLonger( + RateLimitCategory rateLimitCategory, DateTime date) { + final oldDate = rateLimitedUntil[rateLimitCategory]; - + // only overwrite its previous date if the limit is even longer + if (oldDate == null || + date.millisecondsSinceEpoch > oldDate.millisecondsSinceEpoch) { + rateLimitedUntil[rateLimitCategory] = date; + } } } diff --git a/dart/test/mocks/mock_current_data_time_provider.dart b/dart/test/mocks/mock_current_data_time_provider.dart index 76f3c59266..5e89d82587 100644 --- a/dart/test/mocks/mock_current_data_time_provider.dart +++ b/dart/test/mocks/mock_current_data_time_provider.dart @@ -1,15 +1,10 @@ import 'package:sentry/src/current_date_provider.dart'; class MockCurrentDateTimeProvider implements CurrentDateTimeProvider { - var dateTimesToReturn = []; + var dateTimeToReturn = 0; @override int currentDateTime() { - // TODO: implement currentDateTime - if (dateTimesToReturn.length > 1) { - return dateTimesToReturn.removeAt(0); - } else { - return dateTimesToReturn.first; - } + return dateTimeToReturn; } } diff --git a/dart/test/protocol/rate_limit_parser_test.dart b/dart/test/protocol/rate_limit_parser_test.dart index edcc3f5ccf..230b8c0895 100644 --- a/dart/test/protocol/rate_limit_parser_test.dart +++ b/dart/test/protocol/rate_limit_parser_test.dart @@ -26,7 +26,7 @@ void main() { }); test('apply all if there are no categories', () { - final sut = RateLimitParser('50').parseRateLimitHeader(); + final sut = RateLimitParser('50::key').parseRateLimitHeader(); expect(sut.length, 1); expect(sut[RateLimitCategory.all], 50000); diff --git a/dart/test/protocol/rate_limiter_test.dart b/dart/test/protocol/rate_limiter_test.dart index 69279a6e8d..74b30b2775 100644 --- a/dart/test/protocol/rate_limiter_test.dart +++ b/dart/test/protocol/rate_limiter_test.dart @@ -1,4 +1,3 @@ -import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import 'package:sentry/src/transport/rate_limiter.dart'; @@ -18,7 +17,7 @@ void main() { test('uses X-Sentry-Rate-Limit and allows sending if time has passed', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimesToReturn = [0, 0, 1001]; + fixture.currentDateProvider.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); @@ -26,6 +25,8 @@ void main() { rateLimiter.updateRetryAfterLimits( '50:transaction:key, 1:default;error;security:organization', null, 1); + fixture.currentDateProvider.dateTimeToReturn = 1001; + final result = rateLimiter.filter(envelope); expect(result, isNotNull); expect(result!.items.length, 1); @@ -77,7 +78,7 @@ void main() { 'When X-Sentry-Rate-Limit categories are empty, applies to all the categories', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimesToReturn = [0]; + fixture.currentDateProvider.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); @@ -91,13 +92,15 @@ void main() { 'When all categories is set but expired, applies only for specific category', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimesToReturn = [0, 0, 1001]; + fixture.currentDateProvider.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); rateLimiter.updateRetryAfterLimits( '1::key, 60:default;error;security:organization', null, 1); + fixture.currentDateProvider.dateTimeToReturn = 1001; + final result = rateLimiter.filter(envelope); expect(result, isNull); }); @@ -105,38 +108,44 @@ void main() { test('When category has shorter rate limiting, do not apply new timestamp', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimesToReturn = [0, 0, 1001]; + fixture.currentDateProvider.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); rateLimiter.updateRetryAfterLimits( '60:error:key, 1:error:organization', null, 1); + fixture.currentDateProvider.dateTimeToReturn = 1001; + final result = rateLimiter.filter(envelope); expect(result, isNull); }); test('When category has longer rate limiting, apply new timestamp', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimesToReturn = [0, 0, 1001]; + fixture.currentDateProvider.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); rateLimiter.updateRetryAfterLimits( '1:error:key, 5:error:organization', null, 1); + fixture.currentDateProvider.dateTimeToReturn = 1001; + final result = rateLimiter.filter(envelope); expect(result, isNull); }); test('When both retry headers are not present, default delay is set', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimesToReturn = [0, 0, 1001]; + fixture.currentDateProvider.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); rateLimiter.updateRetryAfterLimits(null, null, 429); + fixture.currentDateProvider.dateTimeToReturn = 1001; + final result = rateLimiter.filter(envelope); expect(result, isNull); }); From 36efe0366b76a1ae84218188c79555d483eb1a1a Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 29 Mar 2021 14:06:53 +0200 Subject: [PATCH 19/83] Support multiple rate limits for same category --- dart/lib/src/transport/rate_limit.dart | 8 ++ dart/lib/src/transport/rate_limit_parser.dart | 15 +-- dart/lib/src/transport/rate_limiter.dart | 9 +- .../test/protocol/rate_limit_parser_test.dart | 43 ++++++--- dart/test/protocol/rate_limiter_test.dart | 91 ++++++++++--------- 5 files changed, 103 insertions(+), 63 deletions(-) create mode 100644 dart/lib/src/transport/rate_limit.dart diff --git a/dart/lib/src/transport/rate_limit.dart b/dart/lib/src/transport/rate_limit.dart new file mode 100644 index 0000000000..7db464147a --- /dev/null +++ b/dart/lib/src/transport/rate_limit.dart @@ -0,0 +1,8 @@ +import 'rate_limit_category.dart'; + +class RateLimit { + RateLimit(this.category, this.durationInMillis); + + final RateLimitCategory category; + final int durationInMillis; +} diff --git a/dart/lib/src/transport/rate_limit_parser.dart b/dart/lib/src/transport/rate_limit_parser.dart index 74cc1044d1..416e13a336 100644 --- a/dart/lib/src/transport/rate_limit_parser.dart +++ b/dart/lib/src/transport/rate_limit_parser.dart @@ -1,4 +1,5 @@ import 'rate_limit_category.dart'; +import 'rate_limit.dart'; class RateLimitParser { RateLimitParser(this.header); @@ -7,13 +8,13 @@ class RateLimitParser { String? header; - Map parseRateLimitHeader() { + List parseRateLimitHeader() { final rateLimitHeader = header; if (rateLimitHeader == null) { - return {}; + return []; } - final rateLimits = {}; + final rateLimits = []; final rateLimitValues = rateLimitHeader.toLowerCase().split(','); for (final rateLimitValue in rateLimitValues) { @@ -31,11 +32,11 @@ class RateLimitParser { final category = RateLimitCategoryExtension.fromStringValue(categoryValue); if (category != RateLimitCategory.unknown) { - rateLimits[category] = durationInMillis; + rateLimits.add(RateLimit(category, durationInMillis)); } } } else { - rateLimits[RateLimitCategory.all] = durationInMillis; + rateLimits.add(RateLimit(RateLimitCategory.all, durationInMillis)); } } } @@ -43,8 +44,8 @@ class RateLimitParser { return rateLimits; } - Map parseRetryAfterHeader() { - return {RateLimitCategory.all: _parseRetryAfterOrDefault(header)}; + List parseRetryAfterHeader() { + return [RateLimit(RateLimitCategory.all, _parseRetryAfterOrDefault(header))]; } // Helper diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index b62a1af52b..1267bac37a 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -4,6 +4,7 @@ import '../current_date_provider.dart'; import '../sentry_envelope.dart'; import '../sentry_envelope_item.dart'; import '../sentry_item_type.dart'; +import 'rate_limit.dart'; import 'rate_limit_category.dart'; /// Controls retry limits on different category types sent to Sentry. @@ -52,7 +53,7 @@ class RateLimiter { void updateRetryAfterLimits( String? sentryRateLimitHeader, String? retryAfterHeader, int errorCode) { final currentDateTime = currentDateTimeProvider.currentDateTime(); - var rateLimits = {}; + var rateLimits = []; if (sentryRateLimitHeader != null) { rateLimits = @@ -62,11 +63,11 @@ class RateLimiter { RateLimitParser(sentryRateLimitHeader).parseRetryAfterHeader(); } - for (final rateLimitEntry in rateLimits.entries) { + for (final rateLimit in rateLimits) { _applyRetryAfterOnlyIfLonger( - rateLimitEntry.key, + rateLimit.category, DateTime.fromMillisecondsSinceEpoch( - currentDateTime + rateLimitEntry.value)); + currentDateTime + rateLimit.durationInMillis)); } } diff --git a/dart/test/protocol/rate_limit_parser_test.dart b/dart/test/protocol/rate_limit_parser_test.dart index 230b8c0895..548985ade4 100644 --- a/dart/test/protocol/rate_limit_parser_test.dart +++ b/dart/test/protocol/rate_limit_parser_test.dart @@ -8,15 +8,18 @@ void main() { final sut = RateLimitParser('50:transaction').parseRateLimitHeader(); expect(sut.length, 1); - expect(sut[RateLimitCategory.transaction], 50000); + expect(sut[0].category, RateLimitCategory.transaction); + expect(sut[0].durationInMillis, 50000); }); test('single rate limit with multiple categories', () { final sut = RateLimitParser('50:transaction;session').parseRateLimitHeader(); expect(sut.length, 2); - expect(sut[RateLimitCategory.transaction], 50000); - expect(sut[RateLimitCategory.session], 50000); + expect(sut[0].category, RateLimitCategory.transaction); + expect(sut[0].durationInMillis, 50000); + expect(sut[1].category, RateLimitCategory.session); + expect(sut[1].durationInMillis, 50000); }); test('don`t apply rate limit for unknown categories ', () { @@ -29,29 +32,44 @@ void main() { final sut = RateLimitParser('50::key').parseRateLimitHeader(); expect(sut.length, 1); - expect(sut[RateLimitCategory.all], 50000); + expect(sut[0].category, RateLimitCategory.all); + expect(sut[0].durationInMillis, 50000); }); test('multiple rate limits', () { final sut = RateLimitParser('50:transaction, 70:session').parseRateLimitHeader(); expect(sut.length, 2); - expect(sut[RateLimitCategory.transaction], 50000); - expect(sut[RateLimitCategory.session], 70000); + expect(sut[0].category, RateLimitCategory.transaction); + expect(sut[0].durationInMillis, 50000); + expect(sut[1].category, RateLimitCategory.session); + expect(sut[1].durationInMillis, 70000); + }); + + test('multiple rate limits with same category', () { + final sut = RateLimitParser('50:transaction, 70:transaction').parseRateLimitHeader(); + + expect(sut.length, 2); + expect(sut[0].category, RateLimitCategory.transaction); + expect(sut[0].durationInMillis, 50000); + expect(sut[1].category, RateLimitCategory.transaction); + expect(sut[1].durationInMillis, 70000); }); test('ignore case', () { final sut = RateLimitParser('50:TRANSACTION').parseRateLimitHeader(); expect(sut.length, 1); - expect(sut[RateLimitCategory.transaction], 50000); + expect(sut[0].category, RateLimitCategory.transaction); + expect(sut[0].durationInMillis, 50000); }); test('un-parseable returns default duration', () { final sut = RateLimitParser('foobar:transaction').parseRateLimitHeader(); expect(sut.length, 1); - expect(sut[RateLimitCategory.transaction], RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + expect(sut[0].category, RateLimitCategory.transaction); + expect(sut[0].durationInMillis, RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); }); }); @@ -60,21 +78,24 @@ void main() { final sut = RateLimitParser(null).parseRetryAfterHeader(); expect(sut.length, 1); - expect(sut[RateLimitCategory.all], RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + expect(sut[0].category, RateLimitCategory.all); + expect(sut[0].durationInMillis, RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); }); test('parseable returns default category with duration in millis', () { final sut = RateLimitParser('8').parseRetryAfterHeader(); expect(sut.length, 1); - expect(sut[RateLimitCategory.all], 8000); + expect(sut[0].category, RateLimitCategory.all); + expect(sut[0].durationInMillis, 8000); }); test('un-parseable returns default category with default duration', () { final sut = RateLimitParser('foobar').parseRetryAfterHeader(); expect(sut.length, 1); - expect(sut[RateLimitCategory.all], RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + expect(sut[0].category, RateLimitCategory.all); + expect(sut[0].durationInMillis, RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); }); }); } diff --git a/dart/test/protocol/rate_limiter_test.dart b/dart/test/protocol/rate_limiter_test.dart index 74b30b2775..2a8e84bf29 100644 --- a/dart/test/protocol/rate_limiter_test.dart +++ b/dart/test/protocol/rate_limiter_test.dart @@ -32,47 +32,56 @@ void main() { expect(result!.items.length, 1); }); - // test( - // 'parse X-Sentry-Rate-Limit and set its values and retry after should be true', - // () { - // final rateLimiter = fixture.getSUT(); - // fixture.currentDateProvider.dateTimesToReturn = [0]; - // final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - // final transaction = SentryTransaction( - // SentryTracer(TransactionContext('name', 'op'), mock())); - // final transactionItem = - // SentryEnvelopeItem.fromEvent(fixture.serializer, transaction); - // final envelope = SentryEnvelope( - // SentryEnvelopeHeader.newEventId(), [eventItem, transactionItem]); - - // rateLimiter.updateRetryAfterLimits( - // "50:transaction:key, 2700:default;error;security:organization", - // null, - // 1); - - // final result = rateLimiter.filter(envelope); - // expect(result, isNull); - // }); - - // test( - // 'parse X-Sentry-Rate-Limit and set its values and retry after should be false', - // () { - // final rateLimiter = fixture.getSUT(); - // fixture.currentDateProvider.dateTimesToReturn = [0, 0, 1001]; - // final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - // final transaction = SentryTransaction( - // SentryTracer(TransactionContext('name', 'op'), mock())); - // final transactionItem = SentryEnvelopeItem.fromEvent(transaction); - // final envelope = SentryEnvelope( - // SentryEnvelopeHeader.newEventId(), [eventItem, transactionItem]); - - // rateLimiter.updateRetryAfterLimits( - // '1:transaction:key, 1:default;error;security:organization', null, 1); - - // final result = rateLimiter.filter(envelope); - // expect(result, isNotNull); - // expect(2, result!.items.length); - // }); + test( + 'parse X-Sentry-Rate-Limit and set its values and retry after should be true', + () { + final rateLimiter = fixture.getSUT(); + fixture.currentDateProvider.dateTimeToReturn = 0; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + + // TODO Add another envelope item with different type and update rate limit header + + // final transaction = SentryTransaction( + // SentryTracer(TransactionContext('name', 'op'), mock())); + // final transactionItem = + // SentryEnvelopeItem.fromEvent(fixture.serializer, transaction); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), [eventItem/*, transactionItem*/]); + + rateLimiter.updateRetryAfterLimits( + '50:transaction:key, 2700:default;error;security:organization', + null, + 1); + + final result = rateLimiter.filter(envelope); + expect(result, isNull); + }); + + test( + 'parse X-Sentry-Rate-Limit and set its values and retry after should be false', + () { + final rateLimiter = fixture.getSUT(); + fixture.currentDateProvider.dateTimeToReturn = 0; + final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); + + // TODO Add another envelope item with different type and update rate limit header + + // final transaction = SentryTransaction( + // SentryTracer(TransactionContext('name', 'op'), mock())); + // final transactionItem = SentryEnvelopeItem.fromEvent(transaction); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), [eventItem/*, transactionItem*/]); + + rateLimiter.updateRetryAfterLimits( + '1:transaction:key, 1:default;error;security:organization', null, 1); + + fixture.currentDateProvider.dateTimeToReturn = 1001; + + final result = rateLimiter.filter(envelope); + expect(result, isNotNull); + expect(1, result!.items.length); + //expect(2, result!.items.length); // TODO Update after added second item + }); test( 'When X-Sentry-Rate-Limit categories are empty, applies to all the categories', From c6d3b35fd6b5c00605db97b45a1aa450f905064f Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 29 Mar 2021 14:21:15 +0200 Subject: [PATCH 20/83] No generated mock for cdtp --- dart/test/mocks.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/dart/test/mocks.dart b/dart/test/mocks.dart index 7fb9a03ba8..c4472371f8 100644 --- a/dart/test/mocks.dart +++ b/dart/test/mocks.dart @@ -1,9 +1,6 @@ -import 'package:mockito/annotations.dart'; import 'package:sentry/sentry.dart'; import 'package:sentry/src/protocol.dart'; -import 'package:sentry/src/current_date_provider.dart'; -@GenerateMocks([CurrentDateTimeProvider]) void main() {} final fakeDsn = 'https://abc@def.ingest.sentry.io/1234567'; From 53e31b3580854bbb7f25bf42e6038ef0a913ec1e Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 6 Apr 2021 14:45:24 +0200 Subject: [PATCH 21/83] Use envelope endpoint --- dart/lib/sentry.dart | 1 + dart/lib/src/noop_sentry_client.dart | 5 + dart/lib/src/protocol/dsn.dart | 2 +- dart/lib/src/sentry_client.dart | 8 +- dart/lib/src/transport/http_transport.dart | 23 +- dart/lib/src/transport/noop_transport.dart | 8 +- dart/lib/src/transport/transport.dart | 6 +- dart/pubspec.yaml | 2 +- dart/test/mocks/mock_sentry_client.dart | 19 + dart/test/mocks/mock_transport.dart | 9 +- dart/test/test_utils.dart | 449 ++++++++++--------- flutter/lib/src/file_system_transport.dart | 30 +- flutter/lib/src/sentry_flutter.dart | 4 +- flutter/pubspec.yaml | 2 +- flutter/test/file_system_transport_test.dart | 6 +- flutter/test/mocks.mocks.dart | 17 +- flutter/test/sentry_flutter_test.dart | 8 +- flutter/test/sentry_flutter_util.dart | 4 +- 18 files changed, 329 insertions(+), 274 deletions(-) diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index 5b057ac928..649ec6d31e 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -12,6 +12,7 @@ export 'src/noop_isolate_error_integration.dart' export 'src/protocol.dart'; export 'src/scope.dart'; export 'src/sentry.dart'; +export 'src/sentry_envelope.dart'; //TODO(denis) Remove this again if we will remove file system transport. export 'src/sentry_client.dart'; export 'src/sentry_options.dart'; // useful for integrations diff --git a/dart/lib/src/noop_sentry_client.dart b/dart/lib/src/noop_sentry_client.dart index be34d6a18f..40eb864597 100644 --- a/dart/lib/src/noop_sentry_client.dart +++ b/dart/lib/src/noop_sentry_client.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'protocol.dart'; import 'scope.dart'; import 'sentry_client.dart'; +import 'sentry_envelope.dart'; class NoOpSentryClient implements SentryClient { NoOpSentryClient._(); @@ -42,6 +43,10 @@ class NoOpSentryClient implements SentryClient { }) => Future.value(SentryId.empty()); + @override + Future captureEnvelope(SentryEnvelope envelope) => + Future.value(SentryId.empty()); + @override Future close() async { return; diff --git a/dart/lib/src/protocol/dsn.dart b/dart/lib/src/protocol/dsn.dart index 9f60755b40..c3ec5093c8 100644 --- a/dart/lib/src/protocol/dsn.dart +++ b/dart/lib/src/protocol/dsn.dart @@ -43,7 +43,7 @@ class Dsn { apiPath = 'api'; } return Uri.parse( - '${uriCopy.scheme}://${uriCopy.host}$port/$apiPath/$projectId/store/', + '${uriCopy.scheme}://${uriCopy.host}$port/$apiPath/$projectId/envelope/', ); } diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index f5637043fe..de84c175a7 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -9,6 +9,7 @@ import 'sentry_stack_trace_factory.dart'; import 'transport/http_transport.dart'; import 'transport/noop_transport.dart'; import 'version.dart'; +import 'sentry_envelope.dart'; /// Default value for [User.ipAddress]. It gets set when an event does not have /// a user and IP address. Only applies if [SentryOptions.sendDefaultPii] is set @@ -104,7 +105,8 @@ class SentryClient { } } - return _options.transport.send(preparedEvent); + final eventId = await _options.transport.sendSentryEvent(preparedEvent); + return eventId ?? preparedEvent.eventId; } SentryEvent _prepareEvent(SentryEvent event, {dynamic stackTrace}) { @@ -195,6 +197,10 @@ class SentryClient { return captureEvent(event, scope: scope, hint: hint); } + Future captureEnvelope(SentryEnvelope envelope) { + return _options.transport.sendSentryEnvelope(envelope); + } + void close() => _options.httpClient.close(); Future _processEvent( diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index b21f0fb67c..7b10f973d8 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -7,8 +7,12 @@ import '../noop_client.dart'; import '../protocol.dart'; import '../sentry_options.dart'; import '../utils.dart'; +import '../current_date_provider.dart'; +import '../sentry_envelope.dart'; + import 'noop_encode.dart' if (dart.library.io) 'encode.dart'; import 'transport.dart'; +import 'rate_limiter.dart'; /// A transport is in charge of sending the event to the Sentry server. class HttpTransport implements Transport { @@ -16,6 +20,8 @@ class HttpTransport implements Transport { final Dsn _dsn; + final RateLimiter _rateLimiter; + late _CredentialBuilder _credentialBuilder; final Map _headers; @@ -30,6 +36,7 @@ class HttpTransport implements Transport { HttpTransport._(this._options) : _dsn = Dsn.parse(_options.dsn!), + _rateLimiter = RateLimiter(CurrentDateTimeProvider()), _headers = _buildHeaders(_options.sdk.identifier) { _credentialBuilder = _CredentialBuilder( _dsn, @@ -39,8 +46,14 @@ class HttpTransport implements Transport { } @override - Future send(SentryEvent event) async { - final data = event.toJson(); + Future sendSentryEvent(SentryEvent event) async { + final envelope = SentryEnvelope.fromEvent(event, _options.sdk); + return await sendSentryEnvelope(envelope); + } + + @override + Future sendSentryEnvelope(SentryEnvelope envelope) async { + final data = envelope.serialize(); final body = _bodyEncoder( data, @@ -68,7 +81,7 @@ class HttpTransport implements Transport { } else { _options.logger( SentryLevel.debug, - 'Event ${event.eventId} was sent successfully.', + 'Envelope ${envelope.header.eventId ?? "--"} was sent successfully.', ); } @@ -77,13 +90,13 @@ class HttpTransport implements Transport { } List _bodyEncoder( - Map data, + String data, Map headers, { required bool compressPayload, }) { // [SentryIOClient] implement gzip compression // gzip compression is not available on browser - var body = utf8.encode(json.encode(data)); + var body = utf8.encode(data); if (compressPayload) { body = compressBody(body, headers); } diff --git a/dart/lib/src/transport/noop_transport.dart b/dart/lib/src/transport/noop_transport.dart index 9c657ba0a7..525bfdcadb 100644 --- a/dart/lib/src/transport/noop_transport.dart +++ b/dart/lib/src/transport/noop_transport.dart @@ -1,9 +1,15 @@ import 'dart:async'; +import '../sentry_envelope.dart'; + import '../protocol.dart'; import 'transport.dart'; class NoOpTransport implements Transport { + + @override + Future sendSentryEvent(SentryEvent event) => Future.value(SentryId.empty()); + @override - Future send(SentryEvent event) => Future.value(SentryId.empty()); + Future sendSentryEnvelope(SentryEnvelope envelope) => Future.value(SentryId.empty()); } diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 295181cbc2..c5d1987b8b 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -1,9 +1,11 @@ import 'dart:async'; +import '../sentry_envelope.dart'; import '../protocol.dart'; -/// A transport is in charge of sending the event either via http +/// A transport is in charge of sending the events/envelope either via http /// or caching in the disk. abstract class Transport { - Future send(SentryEvent event); + Future sendSentryEvent(SentryEvent event); + Future sendSentryEnvelope(SentryEnvelope envelope); } diff --git a/dart/pubspec.yaml b/dart/pubspec.yaml index 95f3027085..d8088f7523 100644 --- a/dart/pubspec.yaml +++ b/dart/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: uuid: ^3.0.0 dev_dependencies: - mockito: ^5.0.0 + mockito: ^5.0.3 pedantic: ^1.11.0 test: ^1.16.5 yaml: ^3.1.0 # needed for version match (code and pubspec) diff --git a/dart/test/mocks/mock_sentry_client.dart b/dart/test/mocks/mock_sentry_client.dart index d7e54fca23..9328f30578 100644 --- a/dart/test/mocks/mock_sentry_client.dart +++ b/dart/test/mocks/mock_sentry_client.dart @@ -1,9 +1,11 @@ import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_envelope.dart'; class MockSentryClient implements SentryClient { List captureEventCalls = []; List captureExceptionCalls = []; List captureMessageCalls = []; + List captureEnvelopeCalls = []; int closeCalls = 0; @override @@ -58,6 +60,15 @@ class MockSentryClient implements SentryClient { return SentryId.newId(); } + @override + Future captureEnvelope( + SentryEnvelope envelope) async { + captureEnvelopeCalls.add(CaptureEnvelopeCall( + envelope + )); + return SentryId.newId(); + } + @override void close() { closeCalls = closeCalls + 1; @@ -109,3 +120,11 @@ class CaptureMessageCall { this.hint, ); } + +class CaptureEnvelopeCall { + final SentryEnvelope envelope; + + CaptureEnvelopeCall( + this.envelope + ); +} diff --git a/dart/test/mocks/mock_transport.dart b/dart/test/mocks/mock_transport.dart index 313ed7eb72..615947d7c1 100644 --- a/dart/test/mocks/mock_transport.dart +++ b/dart/test/mocks/mock_transport.dart @@ -2,14 +2,21 @@ import 'package:sentry/sentry.dart'; class MockTransport implements Transport { List events = []; + List envelopes = []; bool called(int calls) { return events.length == calls; } @override - Future send(SentryEvent event) async { + Future sendSentryEvent(SentryEvent event) async { events.add(event); return event.eventId; } + + @override + Future sendSentryEnvelope(SentryEnvelope envelope) async { + envelopes.add(envelope); + return envelope.header.eventId; + } } diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index d4e987ac85..b9c05a43e7 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -109,6 +109,9 @@ Future testCaptureException( data = json.decode(utf8.decode(body!)) as Map?; } + // TODO(denis): Update the expectations, as we are transporting envelope data + // instead of event data. + // so we assert the generated and returned id data!['event_id'] = sentryId.toString(); @@ -184,116 +187,116 @@ Future testCaptureException( } void runTest({Codec, List?>? gzip, bool isWeb = false}) { - test('can parse DSN', () async { - final options = SentryOptions(dsn: testDsn); - final client = SentryClient(options); - - final dsn = Dsn.parse(options.dsn!); - - expect(dsn.uri, Uri.parse(testDsn)); - expect( - dsn.postUri, - Uri.parse('https://sentry.example.com/api/1/store/'), - ); - expect(dsn.publicKey, 'public'); - expect(dsn.secretKey, 'secret'); - expect(dsn.projectId, '1'); - client.close(); - }); - - test('can parse DSN without secret', () async { - final options = SentryOptions(dsn: _testDsnWithoutSecret); - final client = SentryClient(options); - - final dsn = Dsn.parse(options.dsn!); - - expect(dsn.uri, Uri.parse(_testDsnWithoutSecret)); - expect( - dsn.postUri, - Uri.parse('https://sentry.example.com/api/1/store/'), - ); - expect(dsn.publicKey, 'public'); - expect(dsn.secretKey, null); - expect(dsn.projectId, '1'); - client.close(); - }); - - test('can parse DSN with path', () async { - final options = SentryOptions(dsn: _testDsnWithPath); - final client = SentryClient(options); - - final dsn = Dsn.parse(options.dsn!); - - expect(dsn.uri, Uri.parse(_testDsnWithPath)); - expect( - dsn.postUri, - Uri.parse('https://sentry.example.com/path/api/1/store/'), - ); - expect(dsn.publicKey, 'public'); - expect(dsn.secretKey, 'secret'); - expect(dsn.projectId, '1'); - client.close(); - }); - test('can parse DSN with port', () async { - final options = SentryOptions(dsn: _testDsnWithPort); - final client = SentryClient(options); - - final dsn = Dsn.parse(options.dsn!); - - expect(dsn.uri, Uri.parse(_testDsnWithPort)); - expect( - dsn.postUri, - Uri.parse('https://sentry.example.com:8888/api/1/store/'), - ); - expect(dsn.publicKey, 'public'); - expect(dsn.secretKey, 'secret'); - expect(dsn.projectId, '1'); - client.close(); - }); - test('sends client auth header without secret', () async { - final fakeClockProvider = () => DateTime.utc(2017, 1, 2); - - Map? headers; - - final httpMock = MockClient((http.Request request) async { - if (request.method == 'POST') { - headers = request.headers; - return http.Response('{"id": "testeventid"}', 200); - } - fail( - 'Unexpected request on ${request.method} ${request.url} in HttpMock', - ); - }); - - final client = SentryClient( - SentryOptions(dsn: _testDsnWithoutSecret) - ..httpClient = httpMock - ..clock = fakeClockProvider - ..compressPayload = false - ..serverName = 'test.server.com' - ..release = '1.2.3' - ..environment = 'staging', - ); - - try { - throw ArgumentError('Test error'); - } catch (error, stackTrace) { - final sentryId = - await client.captureException(error, stackTrace: stackTrace); - expect('$sentryId', 'testeventid'); - } - - testHeaders( - headers, - fakeClockProvider, - withUserAgent: !isWeb, - compressPayload: false, - withSecret: false, - sdkName: sdkName, - ); - - client.close(); - }); + // test('can parse DSN', () async { + // final options = SentryOptions(dsn: testDsn); + // final client = SentryClient(options); + + // final dsn = Dsn.parse(options.dsn!); + + // expect(dsn.uri, Uri.parse(testDsn)); + // expect( + // dsn.postUri, + // Uri.parse('https://sentry.example.com/api/1/envelope/'), + // ); + // expect(dsn.publicKey, 'public'); + // expect(dsn.secretKey, 'secret'); + // expect(dsn.projectId, '1'); + // client.close(); + // }); + + // test('can parse DSN without secret', () async { + // final options = SentryOptions(dsn: _testDsnWithoutSecret); + // final client = SentryClient(options); + + // final dsn = Dsn.parse(options.dsn!); + + // expect(dsn.uri, Uri.parse(_testDsnWithoutSecret)); + // expect( + // dsn.postUri, + // Uri.parse('https://sentry.example.com/api/1/envelope/'), + // ); + // expect(dsn.publicKey, 'public'); + // expect(dsn.secretKey, null); + // expect(dsn.projectId, '1'); + // client.close(); + // }); + + // test('can parse DSN with path', () async { + // final options = SentryOptions(dsn: _testDsnWithPath); + // final client = SentryClient(options); + + // final dsn = Dsn.parse(options.dsn!); + + // expect(dsn.uri, Uri.parse(_testDsnWithPath)); + // expect( + // dsn.postUri, + // Uri.parse('https://sentry.example.com/path/api/1/envelope/'), + // ); + // expect(dsn.publicKey, 'public'); + // expect(dsn.secretKey, 'secret'); + // expect(dsn.projectId, '1'); + // client.close(); + // }); + // test('can parse DSN with port', () async { + // final options = SentryOptions(dsn: _testDsnWithPort); + // final client = SentryClient(options); + + // final dsn = Dsn.parse(options.dsn!); + + // expect(dsn.uri, Uri.parse(_testDsnWithPort)); + // expect( + // dsn.postUri, + // Uri.parse('https://sentry.example.com:8888/api/1/envelope/'), + // ); + // expect(dsn.publicKey, 'public'); + // expect(dsn.secretKey, 'secret'); + // expect(dsn.projectId, '1'); + // client.close(); + // }); + // test('sends client auth header without secret', () async { + // final fakeClockProvider = () => DateTime.utc(2017, 1, 2); + + // Map? headers; + + // final httpMock = MockClient((http.Request request) async { + // if (request.method == 'POST') { + // headers = request.headers; + // return http.Response('{"id": "testeventid"}', 200); + // } + // fail( + // 'Unexpected request on ${request.method} ${request.url} in HttpMock', + // ); + // }); + + // final client = SentryClient( + // SentryOptions(dsn: _testDsnWithoutSecret) + // ..httpClient = httpMock + // ..clock = fakeClockProvider + // ..compressPayload = false + // ..serverName = 'test.server.com' + // ..release = '1.2.3' + // ..environment = 'staging', + // ); + + // try { + // throw ArgumentError('Test error'); + // } catch (error, stackTrace) { + // final sentryId = + // await client.captureException(error, stackTrace: stackTrace); + // expect('$sentryId', 'testeventid'); + // } + + // testHeaders( + // headers, + // fakeClockProvider, + // withUserAgent: !isWeb, + // compressPayload: false, + // withSecret: false, + // sdkName: sdkName, + // ); + + // client.close(); + // }); test('sends an exception report (compressed)', () async { await testCaptureException(true, gzip, isWeb); @@ -305,117 +308,117 @@ void runTest({Codec, List?>? gzip, bool isWeb = false}) { await testCaptureException(false, gzip, isWeb); }); - test('reads error message from the x-sentry-error header', () async { - final fakeClockProvider = () => DateTime.utc(2017, 1, 2); - - final httpMock = MockClient((http.Request request) async { - if (request.method == 'POST') { - return http.Response('', 401, headers: { - 'x-sentry-error': 'Invalid api key', - }); - } - fail( - 'Unexpected request on ${request.method} ${request.url} in HttpMock', - ); - }); - - final client = SentryClient( - SentryOptions( - dsn: testDsn, - ) - ..httpClient = httpMock - ..clock = fakeClockProvider - ..compressPayload = false - ..serverName = 'test.server.com' - ..release = '1.2.3' - ..environment = 'staging', - ); - - try { - throw ArgumentError('Test error'); - } catch (error, stackTrace) { - final sentryId = - await client.captureException(error, stackTrace: stackTrace); - expect('$sentryId', '00000000000000000000000000000000'); - } - - client.close(); - }); - - test('$SentryEvent user overrides client', () async { - final fakeClockProvider = () => DateTime.utc(2017, 1, 2); - - String? loggedUserId; // used to find out what user context was sent - final httpMock = MockClient((http.Request request) async { - if (request.method == 'POST') { - final bodyData = request.bodyBytes; - final decoded = const Utf8Codec().decode(bodyData); - final dynamic decodedJson = jsonDecode(decoded); - loggedUserId = decodedJson['user']['id'] as String?; - return http.Response( - '', - 401, - headers: { - 'x-sentry-error': 'Invalid api key', - }, - ); - } - fail( - 'Unexpected request on ${request.method} ${request.url} in HttpMock', - ); - }); - - final clientUser = SentryUser( - id: 'client_user', - username: 'username', - email: 'email@email.com', - ipAddress: '127.0.0.1', - ); - final eventUser = SentryUser( - id: 'event_user', - username: 'username', - email: 'email@email.com', - ipAddress: '127.0.0.1', - extras: {'foo': 'bar'}, - ); - - final options = SentryOptions( - dsn: testDsn, - ) - ..httpClient = httpMock - ..clock = fakeClockProvider - ..compressPayload = false - ..serverName = 'test.server.com' - ..release = '1.2.3' - ..environment = 'staging'; - - final client = SentryClient(options); - - try { - throw ArgumentError('Test error'); - } catch (error) { - final eventWithoutContext = SentryEvent( - eventId: SentryId.empty(), - throwable: error, - ); - final eventWithContext = SentryEvent( - eventId: SentryId.empty(), - throwable: error, - user: eventUser, - ); - await client.captureEvent( - eventWithoutContext, - scope: Scope(options)..user = clientUser, - ); - expect(loggedUserId, clientUser.id); - - await client.captureEvent( - eventWithContext, - scope: Scope(options)..user = clientUser, - ); - expect(loggedUserId, eventUser.id); - } - - client.close(); - }); + // test('reads error message from the x-sentry-error header', () async { + // final fakeClockProvider = () => DateTime.utc(2017, 1, 2); + + // final httpMock = MockClient((http.Request request) async { + // if (request.method == 'POST') { + // return http.Response('', 401, headers: { + // 'x-sentry-error': 'Invalid api key', + // }); + // } + // fail( + // 'Unexpected request on ${request.method} ${request.url} in HttpMock', + // ); + // }); + + // final client = SentryClient( + // SentryOptions( + // dsn: testDsn, + // ) + // ..httpClient = httpMock + // ..clock = fakeClockProvider + // ..compressPayload = false + // ..serverName = 'test.server.com' + // ..release = '1.2.3' + // ..environment = 'staging', + // ); + + // try { + // throw ArgumentError('Test error'); + // } catch (error, stackTrace) { + // final sentryId = + // await client.captureException(error, stackTrace: stackTrace); + // expect('$sentryId', '00000000000000000000000000000000'); + // } + + // client.close(); + // }); + + // test('$SentryEvent user overrides client', () async { + // final fakeClockProvider = () => DateTime.utc(2017, 1, 2); + + // String? loggedUserId; // used to find out what user context was sent + // final httpMock = MockClient((http.Request request) async { + // if (request.method == 'POST') { + // final bodyData = request.bodyBytes; + // final decoded = const Utf8Codec().decode(bodyData); + // final dynamic decodedJson = jsonDecode(decoded); + // loggedUserId = decodedJson['user']['id'] as String?; + // return http.Response( + // '', + // 401, + // headers: { + // 'x-sentry-error': 'Invalid api key', + // }, + // ); + // } + // fail( + // 'Unexpected request on ${request.method} ${request.url} in HttpMock', + // ); + // }); + + // final clientUser = SentryUser( + // id: 'client_user', + // username: 'username', + // email: 'email@email.com', + // ipAddress: '127.0.0.1', + // ); + // final eventUser = SentryUser( + // id: 'event_user', + // username: 'username', + // email: 'email@email.com', + // ipAddress: '127.0.0.1', + // extras: {'foo': 'bar'}, + // ); + + // final options = SentryOptions( + // dsn: testDsn, + // ) + // ..httpClient = httpMock + // ..clock = fakeClockProvider + // ..compressPayload = false + // ..serverName = 'test.server.com' + // ..release = '1.2.3' + // ..environment = 'staging'; + + // final client = SentryClient(options); + + // try { + // throw ArgumentError('Test error'); + // } catch (error) { + // final eventWithoutContext = SentryEvent( + // eventId: SentryId.empty(), + // throwable: error, + // ); + // final eventWithContext = SentryEvent( + // eventId: SentryId.empty(), + // throwable: error, + // user: eventUser, + // ); + // await client.captureEvent( + // eventWithoutContext, + // scope: Scope(options)..user = clientUser, + // ); + // expect(loggedUserId, clientUser.id); + + // await client.captureEvent( + // eventWithContext, + // scope: Scope(options)..user = clientUser, + // ); + // expect(loggedUserId, eventUser.id); + // } + + // client.close(); + // }); } diff --git a/flutter/lib/src/file_system_transport.dart b/flutter/lib/src/file_system_transport.dart index 188e896452..0b26c94ae8 100644 --- a/flutter/lib/src/file_system_transport.dart +++ b/flutter/lib/src/file_system_transport.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:flutter/services.dart'; import 'package:sentry/sentry.dart'; @@ -10,26 +8,14 @@ class FileSystemTransport implements Transport { final SentryOptions _options; @override - Future send(SentryEvent event) async { - final headerMap = { - 'event_id': event.eventId.toString(), - 'sdk': _options.sdk.toJson() - }; - - final eventMap = event.toJson(); - - final eventString = jsonEncode(eventMap); - final eventUtf8 = utf8.encode(eventString); - - final itemHeaderMap = { - 'content_type': 'application/json', - 'type': 'event', - 'length': eventUtf8.length, - }; + Future sendSentryEvent(SentryEvent event) async { + final envelope = SentryEnvelope.fromEvent(event, _options.sdk); + return await sendSentryEnvelope(envelope); + } - final headerString = jsonEncode(headerMap); - final itemHeaderString = jsonEncode(itemHeaderMap); - final envelopeString = '$headerString\n$itemHeaderString\n$eventString'; + @override + Future sendSentryEnvelope(SentryEnvelope envelope) async { + final envelopeString = envelope.serialize(); final args = [envelopeString]; try { @@ -42,6 +28,6 @@ class FileSystemTransport implements Transport { return SentryId.empty(); } - return event.eventId; + return envelope.header.eventId; } } diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 41c71eef38..babee787a7 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -7,7 +7,7 @@ import 'package:sentry/sentry.dart'; import 'sentry_flutter_options.dart'; import 'default_integrations.dart'; -import 'file_system_transport.dart'; +//import 'file_system_transport.dart'; import 'version.dart'; // conditional import for the iOSPlatformChecker // in browser, the iOSPlatformChecker will always return false @@ -62,7 +62,7 @@ mixin SentryFlutter { ) async { // web still uses a http transport for Web which is set by default if (!kIsWeb) { - options.transport = FileSystemTransport(channel, options); + //options.transport = FileSystemTransport(channel, options); } _setSdk(options); diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index b01ee3dff3..6bec3e66e4 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - mockito: ^5.0.0 + mockito: ^5.0.3 yaml: ^3.0.0 # needed for version match (code and pubspec) pedantic: ^1.10.0 build_runner: ^1.11.5 diff --git a/flutter/test/file_system_transport_test.dart b/flutter/test/file_system_transport_test.dart index 41b88a5cab..6a0f7fe511 100644 --- a/flutter/test/file_system_transport_test.dart +++ b/flutter/test/file_system_transport_test.dart @@ -26,7 +26,7 @@ void main() { final transport = fixture.getSut(_channel); final event = SentryEvent(); - final sentryId = await transport.send(event); + final sentryId = await transport.sendSentryEvent(event); expect(sentryId, sentryId); }); @@ -38,7 +38,7 @@ void main() { final transport = fixture.getSut(_channel); - final sentryId = await transport.send(SentryEvent()); + final sentryId = await transport.sendSentryEvent(SentryEvent()); expect(SentryId.empty(), sentryId); }); @@ -53,7 +53,7 @@ void main() { final event = SentryEvent(message: SentryMessage('hi I am a special char ◤')); - await transport.send(event); + await transport.sendSentryEvent(event); final envelopeList = arguments as List; final envelopeString = envelopeList.first as String; diff --git a/flutter/test/mocks.mocks.dart b/flutter/test/mocks.mocks.dart index d81a53242f..b059ac13ab 100644 --- a/flutter/test/mocks.mocks.dart +++ b/flutter/test/mocks.mocks.dart @@ -1,5 +1,5 @@ -// Mocks generated by Mockito 5.0.0-nullsafety.7 from annotations -// in sentry_flutter/test/mocks.dart. +// Mocks generated by Mockito 5.0.3 from annotations +// in sentry_flutter/example/ios/.symlinks/plugins/sentry_flutter/test/mocks.dart. // Do not manually edit this file. import 'dart:async' as _i4; @@ -11,6 +11,7 @@ import 'package:sentry/src/protocol/sentry_event.dart' as _i5; import 'package:sentry/src/protocol/sentry_id.dart' as _i2; import 'package:sentry/src/protocol/sentry_level.dart' as _i6; import 'package:sentry/src/sentry_client.dart' as _i8; +import 'package:sentry/src/sentry_envelope.dart' as _i10; import 'package:sentry/src/transport/transport.dart' as _i9; // ignore_for_file: comment_references @@ -95,7 +96,13 @@ class MockTransport extends _i1.Mock implements _i9.Transport { } @override - _i4.Future<_i2.SentryId> send(_i5.SentryEvent? event) => (super.noSuchMethod( - Invocation.method(#send, [event]), - returnValue: Future.value(_FakeSentryId())) as _i4.Future<_i2.SentryId>); + _i4.Future<_i2.SentryId?> sendSentryEvent(_i5.SentryEvent? event) => + (super.noSuchMethod(Invocation.method(#sendSentryEvent, [event]), + returnValue: Future.value(_FakeSentryId())) + as _i4.Future<_i2.SentryId?>); + @override + _i4.Future<_i2.SentryId?> sendSentryEnvelope(_i10.SentryEnvelope? envelope) => + (super.noSuchMethod(Invocation.method(#sendSentryEnvelope, [envelope]), + returnValue: Future.value(_FakeSentryId())) + as _i4.Future<_i2.SentryId?>); } diff --git a/flutter/test/sentry_flutter_test.dart b/flutter/test/sentry_flutter_test.dart index 94de1e984b..4686cbff73 100644 --- a/flutter/test/sentry_flutter_test.dart +++ b/flutter/test/sentry_flutter_test.dart @@ -52,7 +52,7 @@ void main() { _channel.setMockMethodCallHandler( (MethodCall methodCall) async => {}, ); - when(transport.send(any)) + when(transport.sendSentryEvent(any)) .thenAnswer((realInvocation) => Future.value(SentryId.newId())); }); @@ -74,7 +74,7 @@ void main() { await Sentry.captureMessage('a message'); final event = - verify(transport.send(captureAny)).captured.first as SentryEvent; + verify(transport.sendSentryEvent(captureAny)).captured.first as SentryEvent; expect(event.sdk!.integrations.length, 7); expect(event.sdk!.integrations.contains('loadContextsIntegration'), true); @@ -93,7 +93,7 @@ void main() { await Sentry.captureMessage('a message'); final event = - verify(transport.send(captureAny)).captured.first as SentryEvent; + verify(transport.sendSentryEvent(captureAny)).captured.first as SentryEvent; expect(event.sdk!.integrations.length, 6); expect( @@ -114,7 +114,7 @@ void main() { await Sentry.captureMessage('a message'); final event = - verify(transport.send(captureAny)).captured.first as SentryEvent; + verify(transport.sendSentryEvent(captureAny)).captured.first as SentryEvent; expect(event.sdk!.integrations.length, 6); expect( diff --git a/flutter/test/sentry_flutter_util.dart b/flutter/test/sentry_flutter_util.dart index 533344269a..0456482e6f 100644 --- a/flutter/test/sentry_flutter_util.dart +++ b/flutter/test/sentry_flutter_util.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:sentry_flutter/src/file_system_transport.dart'; +//import 'package:sentry_flutter/src/file_system_transport.dart'; import 'package:sentry_flutter/src/sentry_flutter_options.dart'; import 'package:sentry_flutter/src/version.dart'; @@ -22,7 +22,7 @@ FutureOr Function(SentryOptions) getConfigurationTester({ expect(kDebugMode, options.debug); expect('debug', options.environment); - expect(!isWeb, options.transport is FileSystemTransport); + // expect(!isWeb, options.transport is FileSystemTransport); expect( options.integrations.whereType().length, From 1bf198b980c575364beac787b3bad645b2d7a9da Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 6 Apr 2021 15:13:07 +0200 Subject: [PATCH 22/83] Handle envelope data in test --- dart/test/test_utils.dart | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index b9c05a43e7..51359fe50e 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -101,16 +101,14 @@ Future testCaptureException( sdkName: sdkName, ); - Map? data; + String envelopeData; if (compressPayload) { - data = - json.decode(utf8.decode(gzip!.decode(body))) as Map?; + envelopeData = utf8.decode(gzip!.decode(body)); } else { - data = json.decode(utf8.decode(body!)) as Map?; + envelopeData = utf8.decode(body!); } - - // TODO(denis): Update the expectations, as we are transporting envelope data - // instead of event data. + final eventJson = envelopeData.split('\n').last; + final data = json.decode(eventJson) as Map?; // so we assert the generated and returned id data!['event_id'] = sentryId.toString(); From f4a97bee68d8269b0a93f6e7d47d6ec3a8540116 Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 6 Apr 2021 15:13:22 +0200 Subject: [PATCH 23/83] Fallback to empty --- dart/lib/src/sentry_client.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index de84c175a7..309e0f3391 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -106,7 +106,7 @@ class SentryClient { } final eventId = await _options.transport.sendSentryEvent(preparedEvent); - return eventId ?? preparedEvent.eventId; + return eventId ?? SentryId.empty(); } SentryEvent _prepareEvent(SentryEvent event, {dynamic stackTrace}) { From 7c2529e65860670d739a7929ed16e618aefd4f1a Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 6 Apr 2021 15:13:54 +0200 Subject: [PATCH 24/83] update retry after limits --- dart/lib/src/transport/http_transport.dart | 24 +++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index 7b10f973d8..224bb9ebf2 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -53,7 +53,12 @@ class HttpTransport implements Transport { @override Future sendSentryEnvelope(SentryEnvelope envelope) async { - final data = envelope.serialize(); + final filteredEnvelope = _rateLimiter.filter(envelope); + if (filteredEnvelope == null) { + return null; + } + + final data = filteredEnvelope.serialize(); final body = _bodyEncoder( data, @@ -67,6 +72,8 @@ class HttpTransport implements Transport { body: body, ); + _updateRetryAfterLimits(response); + if (response.statusCode != 200) { // body guard to not log the error as it has performance impact to allocate // the body String. @@ -102,6 +109,21 @@ class HttpTransport implements Transport { } return body; } + + void _updateRetryAfterLimits(Response response) { + // seconds + final retryAfterHeader = response.headers['Retry-After']; + + // X-Sentry-Rate-Limits looks like: seconds:categories:scope + // it could have more than one scope so it looks like: + // quota_limit, quota_limit, quota_limit + + // a real example: 50:transaction:key, 2700:default;error;security:organization + // 50::key is also a valid case, it means no categories and it should apply to all of them + final sentryRateLimitHeader = response.headers['X-Sentry-Rate-Limits']; + _rateLimiter.updateRetryAfterLimits( + sentryRateLimitHeader, retryAfterHeader, response.statusCode); + } } class _CredentialBuilder { From e35c4c8e0714fc3e8e52b15a50af498e788369f9 Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 6 Apr 2021 16:32:37 +0200 Subject: [PATCH 25/83] test ratelimiter integration into http_transport --- dart/lib/src/sentry_client.dart | 5 +- dart/lib/src/transport/http_transport.dart | 8 +- dart/lib/src/transport/rate_limiter.dart | 18 +-- dart/test/mocks.dart | 28 ++++ dart/test/transport/http_transport_test.dart | 144 +++++++++++++++++++ 5 files changed, 188 insertions(+), 15 deletions(-) create mode 100644 dart/test/transport/http_transport_test.dart diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 309e0f3391..0404825261 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -1,6 +1,9 @@ import 'dart:async'; import 'dart:math'; +import 'current_date_provider.dart'; +import 'transport/rate_limiter.dart'; + import 'protocol.dart'; import 'scope.dart'; import 'sentry_exception_factory.dart'; @@ -31,7 +34,7 @@ class SentryClient { /// Instantiates a client using [SentryOptions] factory SentryClient(SentryOptions options) { if (options.transport is NoOpTransport) { - options.transport = HttpTransport(options); + options.transport = HttpTransport(options, RateLimiter(CurrentDateTimeProvider())); } return SentryClient._(options); diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index 224bb9ebf2..ff162f3ffc 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -7,7 +7,6 @@ import '../noop_client.dart'; import '../protocol.dart'; import '../sentry_options.dart'; import '../utils.dart'; -import '../current_date_provider.dart'; import '../sentry_envelope.dart'; import 'noop_encode.dart' if (dart.library.io) 'encode.dart'; @@ -26,17 +25,16 @@ class HttpTransport implements Transport { final Map _headers; - factory HttpTransport(SentryOptions options) { + factory HttpTransport(SentryOptions options, RateLimiter rateLimiter) { if (options.httpClient is NoOpClient) { options.httpClient = Client(); } - return HttpTransport._(options); + return HttpTransport._(options, rateLimiter); } - HttpTransport._(this._options) + HttpTransport._(this._options, this._rateLimiter) : _dsn = Dsn.parse(_options.dsn!), - _rateLimiter = RateLimiter(CurrentDateTimeProvider()), _headers = _buildHeaders(_options.sdk.identifier) { _credentialBuilder = _CredentialBuilder( _dsn, diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index 1267bac37a..4f1f66abb5 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -9,10 +9,10 @@ import 'rate_limit_category.dart'; /// Controls retry limits on different category types sent to Sentry. class RateLimiter { - RateLimiter(this.currentDateTimeProvider); + RateLimiter(this._currentDateTimeProvider); - final CurrentDateTimeProvider currentDateTimeProvider; - final rateLimitedUntil = {}; + final CurrentDateTimeProvider _currentDateTimeProvider; + final _rateLimitedUntil = {}; SentryEnvelope? filter(SentryEnvelope envelope) { // Optimize for/No allocations if no items are under 429 @@ -52,7 +52,7 @@ class RateLimiter { void updateRetryAfterLimits( String? sentryRateLimitHeader, String? retryAfterHeader, int errorCode) { - final currentDateTime = currentDateTimeProvider.currentDateTime(); + final currentDateTime = _currentDateTimeProvider.currentDateTime(); var rateLimits = []; if (sentryRateLimitHeader != null) { @@ -76,10 +76,10 @@ class RateLimiter { bool _isRetryAfter(String itemType) { final dataCategory = _categoryFromItemType(itemType); final currentDate = DateTime.fromMillisecondsSinceEpoch( - currentDateTimeProvider.currentDateTime()); + _currentDateTimeProvider.currentDateTime()); // check all categories - final dateAllCategories = rateLimitedUntil[RateLimitCategory.all]; + final dateAllCategories = _rateLimitedUntil[RateLimitCategory.all]; if (dateAllCategories != null) { if (!(currentDate.millisecondsSinceEpoch > dateAllCategories.millisecondsSinceEpoch)) { @@ -93,7 +93,7 @@ class RateLimiter { } // check for specific dataCategory - final dateCategory = rateLimitedUntil[dataCategory]; + final dateCategory = _rateLimitedUntil[dataCategory]; if (dateCategory != null) { return !(currentDate.millisecondsSinceEpoch > dateCategory.millisecondsSinceEpoch); @@ -119,12 +119,12 @@ class RateLimiter { void _applyRetryAfterOnlyIfLonger( RateLimitCategory rateLimitCategory, DateTime date) { - final oldDate = rateLimitedUntil[rateLimitCategory]; + final oldDate = _rateLimitedUntil[rateLimitCategory]; // only overwrite its previous date if the limit is even longer if (oldDate == null || date.millisecondsSinceEpoch > oldDate.millisecondsSinceEpoch) { - rateLimitedUntil[rateLimitCategory] = date; + _rateLimitedUntil[rateLimitCategory] = date; } } } diff --git a/dart/test/mocks.dart b/dart/test/mocks.dart index c4472371f8..e1c7720dfd 100644 --- a/dart/test/mocks.dart +++ b/dart/test/mocks.dart @@ -1,5 +1,6 @@ import 'package:sentry/sentry.dart'; import 'package:sentry/src/protocol.dart'; +import 'package:sentry/src/transport/rate_limiter.dart'; void main() {} @@ -93,3 +94,30 @@ final fakeEvent = SentryEvent( ), ), ); + +class MockRateLimiter implements RateLimiter { + bool filterReturnsNull = false; + SentryEnvelope? filteredEnvelope; + SentryEnvelope? envelopeToFilter; + + String? sentryRateLimitHeader; + String? retryAfterHeader; + int? errorCode; + + @override + SentryEnvelope? filter(SentryEnvelope envelope) { + if (filterReturnsNull) { + return null; + } + envelopeToFilter = envelope; + return filteredEnvelope ?? envelope; + } + + @override + void updateRetryAfterLimits( + String? sentryRateLimitHeader, String? retryAfterHeader, int errorCode) { + this.sentryRateLimitHeader = sentryRateLimitHeader; + this.retryAfterHeader = retryAfterHeader; + this.errorCode = errorCode; + } +} diff --git a/dart/test/transport/http_transport_test.dart b/dart/test/transport/http_transport_test.dart new file mode 100644 index 0000000000..198fb5272e --- /dev/null +++ b/dart/test/transport/http_transport_test.dart @@ -0,0 +1,144 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:sentry/src/sentry_envelope_header.dart'; +import 'package:sentry/src/sentry_envelope_item.dart'; +import 'package:sentry/src/sentry_envelope_item_header.dart'; +import 'package:sentry/src/sentry_item_type.dart'; +import 'package:test/test.dart'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/transport/http_transport.dart'; + +import '../mocks.dart'; + +void main() { + + SentryEnvelope givenEnvelope() { + final filteredEnvelopeHeader = SentryEnvelopeHeader(SentryId.empty(), null); + final filteredItemHeader = SentryEnvelopeItemHeader(SentryItemType.event, 2, + contentType: 'application/json'); + final filteredItem = + SentryEnvelopeItem(filteredItemHeader, utf8.encode('{}')); + return SentryEnvelope(filteredEnvelopeHeader, [filteredItem]); + } + + group('filter', () { + test('filter called', () async { + final httpMock = MockClient((http.Request request) async { + return http.Response('{}', 200); + }); + + final options = + SentryOptions(dsn: 'https://public:secret@sentry.example.com/1') + ..compressPayload = false + ..httpClient = httpMock; + + final mockRateLimiter = MockRateLimiter(); + + final sut = HttpTransport(options, mockRateLimiter); + + final sentryEnvelope = givenEnvelope(); + await sut.sendSentryEnvelope(sentryEnvelope); + + expect(mockRateLimiter.envelopeToFilter, sentryEnvelope); + }); + + test('send filtered event', () async { + String? body; + + final httpMock = MockClient((http.Request request) async { + body = request.body; + return http.Response('{}', 200); + }); + + final filteredEnvelope = givenEnvelope(); + + final mockRateLimiter = MockRateLimiter(); + mockRateLimiter.filteredEnvelope = filteredEnvelope; + + final options = + SentryOptions(dsn: 'https://public:secret@sentry.example.com/1') + ..compressPayload = false + ..httpClient = httpMock; + + final sut = HttpTransport(options, mockRateLimiter); + final sentryEvent = SentryEvent(); + await sut.sendSentryEvent(sentryEvent); + + expect(body, filteredEnvelope.serialize()); + }); + + test('send nothing when filtered event null', () async { + var httpCalled = false; + final httpMock = MockClient((http.Request request) async { + httpCalled = true; + return http.Response('{}', 200); + }); + + final options = + SentryOptions(dsn: 'https://public:secret@sentry.example.com/1') + ..compressPayload = false + ..httpClient = httpMock; + + final mockRateLimiter = MockRateLimiter(); + mockRateLimiter.filterReturnsNull = true; + + final sut = HttpTransport(options, mockRateLimiter); + + final sentryEvent = SentryEvent(); + final eventId = await sut.sendSentryEvent(sentryEvent); + + expect(eventId, isNull); + expect(httpCalled, false); + }); + }); + + group('updateRetryAfterLimits', () { + test('retryAfterHeader', () async { + final httpMock = MockClient((http.Request request) async { + return http.Response('{}', 429, headers: {'Retry-After': '1'}); + }); + + final mockRateLimiter = MockRateLimiter(); + + final options = + SentryOptions(dsn: 'https://public:secret@sentry.example.com/1') + ..httpClient = httpMock; + + final sut = HttpTransport(options, mockRateLimiter); + final sentryEvent = SentryEvent(); + await sut.sendSentryEvent(sentryEvent); + + expect(mockRateLimiter.envelopeToFilter?.header.eventId, + sentryEvent.eventId); + + expect(mockRateLimiter.errorCode, 429); + expect(mockRateLimiter.retryAfterHeader, '1'); + expect(mockRateLimiter.sentryRateLimitHeader, isNull); + }); + + test('sentryRateLimitHeader', () async { + final httpMock = MockClient((http.Request request) async { + return http.Response('{}', 200, + headers: {'X-Sentry-Rate-Limits': 'fixture-sentryRateLimitHeader'}); + }); + + final mockRateLimiter = MockRateLimiter(); + + final options = + SentryOptions(dsn: 'https://public:secret@sentry.example.com/1') + ..httpClient = httpMock; + + final sut = HttpTransport(options, mockRateLimiter); + final sentryEvent = SentryEvent(); + await sut.sendSentryEvent(sentryEvent); + + expect(mockRateLimiter.errorCode, 200); + expect(mockRateLimiter.retryAfterHeader, isNull); + expect(mockRateLimiter.sentryRateLimitHeader, + 'fixture-sentryRateLimitHeader'); + }); + }); +} From d1547fae942dd11e30a51bb0f0b83091ba8111dc Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 6 Apr 2021 16:41:39 +0200 Subject: [PATCH 26/83] Use clock from options --- dart/lib/src/sentry_client.dart | 2 +- dart/lib/src/transport/rate_limiter.dart | 11 ++-- .../mock_current_data_time_provider.dart | 10 ---- dart/test/protocol/rate_limiter_test.dart | 60 +++++++++++-------- 4 files changed, 41 insertions(+), 42 deletions(-) delete mode 100644 dart/test/mocks/mock_current_data_time_provider.dart diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 0404825261..858946f241 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -34,7 +34,7 @@ class SentryClient { /// Instantiates a client using [SentryOptions] factory SentryClient(SentryOptions options) { if (options.transport is NoOpTransport) { - options.transport = HttpTransport(options, RateLimiter(CurrentDateTimeProvider())); + options.transport = HttpTransport(options, RateLimiter(options.clock)); } return SentryClient._(options); diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index 4f1f66abb5..c787cc2e41 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -1,6 +1,6 @@ import '../transport/rate_limit_parser.dart'; -import '../current_date_provider.dart'; +import '../sentry_options.dart'; import '../sentry_envelope.dart'; import '../sentry_envelope_item.dart'; import '../sentry_item_type.dart'; @@ -9,9 +9,9 @@ import 'rate_limit_category.dart'; /// Controls retry limits on different category types sent to Sentry. class RateLimiter { - RateLimiter(this._currentDateTimeProvider); + RateLimiter(this._clockProvider); - final CurrentDateTimeProvider _currentDateTimeProvider; + final ClockProvider _clockProvider; final _rateLimitedUntil = {}; SentryEnvelope? filter(SentryEnvelope envelope) { @@ -52,7 +52,7 @@ class RateLimiter { void updateRetryAfterLimits( String? sentryRateLimitHeader, String? retryAfterHeader, int errorCode) { - final currentDateTime = _currentDateTimeProvider.currentDateTime(); + final currentDateTime = _clockProvider().millisecondsSinceEpoch; var rateLimits = []; if (sentryRateLimitHeader != null) { @@ -76,7 +76,8 @@ class RateLimiter { bool _isRetryAfter(String itemType) { final dataCategory = _categoryFromItemType(itemType); final currentDate = DateTime.fromMillisecondsSinceEpoch( - _currentDateTimeProvider.currentDateTime()); + _clockProvider().millisecondsSinceEpoch + ); // check all categories final dateAllCategories = _rateLimitedUntil[RateLimitCategory.all]; diff --git a/dart/test/mocks/mock_current_data_time_provider.dart b/dart/test/mocks/mock_current_data_time_provider.dart deleted file mode 100644 index 5e89d82587..0000000000 --- a/dart/test/mocks/mock_current_data_time_provider.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:sentry/src/current_date_provider.dart'; - -class MockCurrentDateTimeProvider implements CurrentDateTimeProvider { - var dateTimeToReturn = 0; - - @override - int currentDateTime() { - return dateTimeToReturn; - } -} diff --git a/dart/test/protocol/rate_limiter_test.dart b/dart/test/protocol/rate_limiter_test.dart index 2a8e84bf29..ea1488be7d 100644 --- a/dart/test/protocol/rate_limiter_test.dart +++ b/dart/test/protocol/rate_limiter_test.dart @@ -6,8 +6,6 @@ import 'package:sentry/src/sentry_envelope.dart'; import 'package:sentry/src/sentry_envelope_header.dart'; import 'package:sentry/src/sentry_envelope_item.dart'; -import '../mocks/mock_current_data_time_provider.dart'; - void main() { var fixture = Fixture(); @@ -17,15 +15,16 @@ void main() { test('uses X-Sentry-Rate-Limit and allows sending if time has passed', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimeToReturn = 0; + fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + final envelope = + SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); rateLimiter.updateRetryAfterLimits( '50:transaction:key, 1:default;error;security:organization', null, 1); - fixture.currentDateProvider.dateTimeToReturn = 1001; + fixture.dateTimeToReturn = 1001; final result = rateLimiter.filter(envelope); expect(result, isNotNull); @@ -36,7 +35,7 @@ void main() { 'parse X-Sentry-Rate-Limit and set its values and retry after should be true', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimeToReturn = 0; + fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); // TODO Add another envelope item with different type and update rate limit header @@ -46,7 +45,7 @@ void main() { // final transactionItem = // SentryEnvelopeItem.fromEvent(fixture.serializer, transaction); final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), [eventItem/*, transactionItem*/]); + SentryEnvelopeHeader.newEventId(), [eventItem /*, transactionItem*/]); rateLimiter.updateRetryAfterLimits( '50:transaction:key, 2700:default;error;security:organization', @@ -61,7 +60,7 @@ void main() { 'parse X-Sentry-Rate-Limit and set its values and retry after should be false', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimeToReturn = 0; + fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); // TODO Add another envelope item with different type and update rate limit header @@ -70,12 +69,12 @@ void main() { // SentryTracer(TransactionContext('name', 'op'), mock())); // final transactionItem = SentryEnvelopeItem.fromEvent(transaction); final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), [eventItem/*, transactionItem*/]); + SentryEnvelopeHeader.newEventId(), [eventItem /*, transactionItem*/]); rateLimiter.updateRetryAfterLimits( '1:transaction:key, 1:default;error;security:organization', null, 1); - fixture.currentDateProvider.dateTimeToReturn = 1001; + fixture.dateTimeToReturn = 1001; final result = rateLimiter.filter(envelope); expect(result, isNotNull); @@ -87,9 +86,10 @@ void main() { 'When X-Sentry-Rate-Limit categories are empty, applies to all the categories', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimeToReturn = 0; + fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + final envelope = + SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); rateLimiter.updateRetryAfterLimits('50::key', null, 1); @@ -101,14 +101,15 @@ void main() { 'When all categories is set but expired, applies only for specific category', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimeToReturn = 0; + fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + final envelope = + SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); rateLimiter.updateRetryAfterLimits( '1::key, 60:default;error;security:organization', null, 1); - fixture.currentDateProvider.dateTimeToReturn = 1001; + fixture.dateTimeToReturn = 1001; final result = rateLimiter.filter(envelope); expect(result, isNull); @@ -117,14 +118,15 @@ void main() { test('When category has shorter rate limiting, do not apply new timestamp', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimeToReturn = 0; + fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + final envelope = + SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); rateLimiter.updateRetryAfterLimits( '60:error:key, 1:error:organization', null, 1); - fixture.currentDateProvider.dateTimeToReturn = 1001; + fixture.dateTimeToReturn = 1001; final result = rateLimiter.filter(envelope); expect(result, isNull); @@ -132,14 +134,15 @@ void main() { test('When category has longer rate limiting, apply new timestamp', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimeToReturn = 0; + fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + final envelope = + SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); rateLimiter.updateRetryAfterLimits( '1:error:key, 5:error:organization', null, 1); - fixture.currentDateProvider.dateTimeToReturn = 1001; + fixture.dateTimeToReturn = 1001; final result = rateLimiter.filter(envelope); expect(result, isNull); @@ -147,13 +150,14 @@ void main() { test('When both retry headers are not present, default delay is set', () { final rateLimiter = fixture.getSUT(); - fixture.currentDateProvider.dateTimeToReturn = 0; + fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + final envelope = + SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); rateLimiter.updateRetryAfterLimits(null, null, 429); - fixture.currentDateProvider.dateTimeToReturn = 1001; + fixture.dateTimeToReturn = 1001; final result = rateLimiter.filter(envelope); expect(result, isNull); @@ -161,9 +165,13 @@ void main() { } class Fixture { - final currentDateProvider = MockCurrentDateTimeProvider(); + var dateTimeToReturn = 0; RateLimiter getSUT() { - return RateLimiter(currentDateProvider); + return RateLimiter(_currentDateTime); + } + + DateTime _currentDateTime() { + return DateTime.fromMillisecondsSinceEpoch(dateTimeToReturn); } } From c1003766b8b837a149ce9ff36e45b03fef7d17f1 Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 6 Apr 2021 16:43:30 +0200 Subject: [PATCH 27/83] No need to test system code --- dart/lib/src/sentry_envelope_item.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index d1d39cb0ce..d126f1d106 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -9,8 +9,7 @@ class SentryEnvelopeItem { final SentryEnvelopeItemHeader header; final List data; - - // TODO(denis): Test formatting... + static SentryEnvelopeItem fromEvent(SentryEvent event) { final jsonEncoded = jsonEncode(event.toJson()); final data = utf8.encode(jsonEncoded); From 1c7c69c51d70b517491c5e3db61c6190c7310c91 Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 6 Apr 2021 17:01:25 +0200 Subject: [PATCH 28/83] Update comments, Add test for envelope capture --- dart/lib/src/transport/rate_limiter.dart | 5 +---- dart/lib/src/transport/transport.dart | 2 +- dart/test/mocks.dart | 3 +++ dart/test/sentry_client_test.dart | 19 +++++++++++++++++++ 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index c787cc2e41..1c0ed43ae4 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -26,8 +26,6 @@ class RateLimiter { } if (dropItems != null) { - //logger.log(SentryLevel.INFO, "%d items will be dropped due rate limiting.", dropItems.size()); - // Need a new envelope final toSend = []; for (final item in envelope.items) { @@ -38,8 +36,7 @@ class RateLimiter { // no reason to continue if (toSend.isEmpty) { - //logger.log(SentryLevel.INFO, "Envelope discarded due all items rate limited."); - + // TODO(denis): Should hints be implemented as well? //markHintWhenSendingFailed(hint, false); return null; } diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index c5d1987b8b..4bde8149cc 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -3,7 +3,7 @@ import 'dart:async'; import '../sentry_envelope.dart'; import '../protocol.dart'; -/// A transport is in charge of sending the events/envelope either via http +/// A transport is in charge of sending the event/envelope either via http /// or caching in the disk. abstract class Transport { Future sendSentryEvent(SentryEvent event); diff --git a/dart/test/mocks.dart b/dart/test/mocks.dart index e1c7720dfd..a0f26c2ef0 100644 --- a/dart/test/mocks.dart +++ b/dart/test/mocks.dart @@ -95,6 +95,9 @@ final fakeEvent = SentryEvent( ), ); +var fakeEnvelope = SentryEnvelope.fromEvent( + fakeEvent, SdkVersion(name: 'sdk1', version: '1.0.0')); + class MockRateLimiter implements RateLimiter { bool filterReturnsNull = false; SentryEnvelope? filteredEnvelope; diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart index a473d40779..0b702b8d26 100644 --- a/dart/test/sentry_client_test.dart +++ b/dart/test/sentry_client_test.dart @@ -592,6 +592,25 @@ void main() { expect((options.transport as MockTransport).called(0), true); }); }); + + group('SentryClient captures envelope', () { + var options = SentryOptions(dsn: fakeDsn); + + setUp(() { + options = SentryOptions(dsn: fakeDsn); + options.transport = MockTransport(); + }); + + test('should capture envelope', () async { + final client = SentryClient(options); + await client.captureEnvelope(fakeEnvelope); + + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + + expect(capturedEnvelope, fakeEnvelope); + }); + }); } SentryEvent? beforeSendCallbackDropEvent(SentryEvent event, {dynamic hint}) => From 20d1e82981a702bf59a2917bd40f4c03383cd7ce Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 6 Apr 2021 17:03:16 +0200 Subject: [PATCH 29/83] Delete unues file, comment in tests --- dart/lib/src/current_date_provider.dart | 5 - dart/test/test_utils.dart | 446 ++++++++++++------------ 2 files changed, 223 insertions(+), 228 deletions(-) delete mode 100644 dart/lib/src/current_date_provider.dart diff --git a/dart/lib/src/current_date_provider.dart b/dart/lib/src/current_date_provider.dart deleted file mode 100644 index 3572860546..0000000000 --- a/dart/lib/src/current_date_provider.dart +++ /dev/null @@ -1,5 +0,0 @@ -class CurrentDateTimeProvider { - int currentDateTime() { - return DateTime.now().millisecondsSinceEpoch; - } -} diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index 51359fe50e..827b3a391c 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -185,116 +185,116 @@ Future testCaptureException( } void runTest({Codec, List?>? gzip, bool isWeb = false}) { - // test('can parse DSN', () async { - // final options = SentryOptions(dsn: testDsn); - // final client = SentryClient(options); - - // final dsn = Dsn.parse(options.dsn!); - - // expect(dsn.uri, Uri.parse(testDsn)); - // expect( - // dsn.postUri, - // Uri.parse('https://sentry.example.com/api/1/envelope/'), - // ); - // expect(dsn.publicKey, 'public'); - // expect(dsn.secretKey, 'secret'); - // expect(dsn.projectId, '1'); - // client.close(); - // }); - - // test('can parse DSN without secret', () async { - // final options = SentryOptions(dsn: _testDsnWithoutSecret); - // final client = SentryClient(options); - - // final dsn = Dsn.parse(options.dsn!); - - // expect(dsn.uri, Uri.parse(_testDsnWithoutSecret)); - // expect( - // dsn.postUri, - // Uri.parse('https://sentry.example.com/api/1/envelope/'), - // ); - // expect(dsn.publicKey, 'public'); - // expect(dsn.secretKey, null); - // expect(dsn.projectId, '1'); - // client.close(); - // }); - - // test('can parse DSN with path', () async { - // final options = SentryOptions(dsn: _testDsnWithPath); - // final client = SentryClient(options); - - // final dsn = Dsn.parse(options.dsn!); - - // expect(dsn.uri, Uri.parse(_testDsnWithPath)); - // expect( - // dsn.postUri, - // Uri.parse('https://sentry.example.com/path/api/1/envelope/'), - // ); - // expect(dsn.publicKey, 'public'); - // expect(dsn.secretKey, 'secret'); - // expect(dsn.projectId, '1'); - // client.close(); - // }); - // test('can parse DSN with port', () async { - // final options = SentryOptions(dsn: _testDsnWithPort); - // final client = SentryClient(options); - - // final dsn = Dsn.parse(options.dsn!); - - // expect(dsn.uri, Uri.parse(_testDsnWithPort)); - // expect( - // dsn.postUri, - // Uri.parse('https://sentry.example.com:8888/api/1/envelope/'), - // ); - // expect(dsn.publicKey, 'public'); - // expect(dsn.secretKey, 'secret'); - // expect(dsn.projectId, '1'); - // client.close(); - // }); - // test('sends client auth header without secret', () async { - // final fakeClockProvider = () => DateTime.utc(2017, 1, 2); - - // Map? headers; - - // final httpMock = MockClient((http.Request request) async { - // if (request.method == 'POST') { - // headers = request.headers; - // return http.Response('{"id": "testeventid"}', 200); - // } - // fail( - // 'Unexpected request on ${request.method} ${request.url} in HttpMock', - // ); - // }); - - // final client = SentryClient( - // SentryOptions(dsn: _testDsnWithoutSecret) - // ..httpClient = httpMock - // ..clock = fakeClockProvider - // ..compressPayload = false - // ..serverName = 'test.server.com' - // ..release = '1.2.3' - // ..environment = 'staging', - // ); - - // try { - // throw ArgumentError('Test error'); - // } catch (error, stackTrace) { - // final sentryId = - // await client.captureException(error, stackTrace: stackTrace); - // expect('$sentryId', 'testeventid'); - // } - - // testHeaders( - // headers, - // fakeClockProvider, - // withUserAgent: !isWeb, - // compressPayload: false, - // withSecret: false, - // sdkName: sdkName, - // ); - - // client.close(); - // }); + test('can parse DSN', () async { + final options = SentryOptions(dsn: testDsn); + final client = SentryClient(options); + + final dsn = Dsn.parse(options.dsn!); + + expect(dsn.uri, Uri.parse(testDsn)); + expect( + dsn.postUri, + Uri.parse('https://sentry.example.com/api/1/envelope/'), + ); + expect(dsn.publicKey, 'public'); + expect(dsn.secretKey, 'secret'); + expect(dsn.projectId, '1'); + client.close(); + }); + + test('can parse DSN without secret', () async { + final options = SentryOptions(dsn: _testDsnWithoutSecret); + final client = SentryClient(options); + + final dsn = Dsn.parse(options.dsn!); + + expect(dsn.uri, Uri.parse(_testDsnWithoutSecret)); + expect( + dsn.postUri, + Uri.parse('https://sentry.example.com/api/1/envelope/'), + ); + expect(dsn.publicKey, 'public'); + expect(dsn.secretKey, null); + expect(dsn.projectId, '1'); + client.close(); + }); + + test('can parse DSN with path', () async { + final options = SentryOptions(dsn: _testDsnWithPath); + final client = SentryClient(options); + + final dsn = Dsn.parse(options.dsn!); + + expect(dsn.uri, Uri.parse(_testDsnWithPath)); + expect( + dsn.postUri, + Uri.parse('https://sentry.example.com/path/api/1/envelope/'), + ); + expect(dsn.publicKey, 'public'); + expect(dsn.secretKey, 'secret'); + expect(dsn.projectId, '1'); + client.close(); + }); + test('can parse DSN with port', () async { + final options = SentryOptions(dsn: _testDsnWithPort); + final client = SentryClient(options); + + final dsn = Dsn.parse(options.dsn!); + + expect(dsn.uri, Uri.parse(_testDsnWithPort)); + expect( + dsn.postUri, + Uri.parse('https://sentry.example.com:8888/api/1/envelope/'), + ); + expect(dsn.publicKey, 'public'); + expect(dsn.secretKey, 'secret'); + expect(dsn.projectId, '1'); + client.close(); + }); + test('sends client auth header without secret', () async { + final fakeClockProvider = () => DateTime.utc(2017, 1, 2); + + Map? headers; + + final httpMock = MockClient((http.Request request) async { + if (request.method == 'POST') { + headers = request.headers; + return http.Response('{"id": "testeventid"}', 200); + } + fail( + 'Unexpected request on ${request.method} ${request.url} in HttpMock', + ); + }); + + final client = SentryClient( + SentryOptions(dsn: _testDsnWithoutSecret) + ..httpClient = httpMock + ..clock = fakeClockProvider + ..compressPayload = false + ..serverName = 'test.server.com' + ..release = '1.2.3' + ..environment = 'staging', + ); + + try { + throw ArgumentError('Test error'); + } catch (error, stackTrace) { + final sentryId = + await client.captureException(error, stackTrace: stackTrace); + expect('$sentryId', 'testeventid'); + } + + testHeaders( + headers, + fakeClockProvider, + withUserAgent: !isWeb, + compressPayload: false, + withSecret: false, + sdkName: sdkName, + ); + + client.close(); + }); test('sends an exception report (compressed)', () async { await testCaptureException(true, gzip, isWeb); @@ -306,117 +306,117 @@ void runTest({Codec, List?>? gzip, bool isWeb = false}) { await testCaptureException(false, gzip, isWeb); }); - // test('reads error message from the x-sentry-error header', () async { - // final fakeClockProvider = () => DateTime.utc(2017, 1, 2); - - // final httpMock = MockClient((http.Request request) async { - // if (request.method == 'POST') { - // return http.Response('', 401, headers: { - // 'x-sentry-error': 'Invalid api key', - // }); - // } - // fail( - // 'Unexpected request on ${request.method} ${request.url} in HttpMock', - // ); - // }); - - // final client = SentryClient( - // SentryOptions( - // dsn: testDsn, - // ) - // ..httpClient = httpMock - // ..clock = fakeClockProvider - // ..compressPayload = false - // ..serverName = 'test.server.com' - // ..release = '1.2.3' - // ..environment = 'staging', - // ); - - // try { - // throw ArgumentError('Test error'); - // } catch (error, stackTrace) { - // final sentryId = - // await client.captureException(error, stackTrace: stackTrace); - // expect('$sentryId', '00000000000000000000000000000000'); - // } - - // client.close(); - // }); - - // test('$SentryEvent user overrides client', () async { - // final fakeClockProvider = () => DateTime.utc(2017, 1, 2); - - // String? loggedUserId; // used to find out what user context was sent - // final httpMock = MockClient((http.Request request) async { - // if (request.method == 'POST') { - // final bodyData = request.bodyBytes; - // final decoded = const Utf8Codec().decode(bodyData); - // final dynamic decodedJson = jsonDecode(decoded); - // loggedUserId = decodedJson['user']['id'] as String?; - // return http.Response( - // '', - // 401, - // headers: { - // 'x-sentry-error': 'Invalid api key', - // }, - // ); - // } - // fail( - // 'Unexpected request on ${request.method} ${request.url} in HttpMock', - // ); - // }); - - // final clientUser = SentryUser( - // id: 'client_user', - // username: 'username', - // email: 'email@email.com', - // ipAddress: '127.0.0.1', - // ); - // final eventUser = SentryUser( - // id: 'event_user', - // username: 'username', - // email: 'email@email.com', - // ipAddress: '127.0.0.1', - // extras: {'foo': 'bar'}, - // ); - - // final options = SentryOptions( - // dsn: testDsn, - // ) - // ..httpClient = httpMock - // ..clock = fakeClockProvider - // ..compressPayload = false - // ..serverName = 'test.server.com' - // ..release = '1.2.3' - // ..environment = 'staging'; - - // final client = SentryClient(options); - - // try { - // throw ArgumentError('Test error'); - // } catch (error) { - // final eventWithoutContext = SentryEvent( - // eventId: SentryId.empty(), - // throwable: error, - // ); - // final eventWithContext = SentryEvent( - // eventId: SentryId.empty(), - // throwable: error, - // user: eventUser, - // ); - // await client.captureEvent( - // eventWithoutContext, - // scope: Scope(options)..user = clientUser, - // ); - // expect(loggedUserId, clientUser.id); - - // await client.captureEvent( - // eventWithContext, - // scope: Scope(options)..user = clientUser, - // ); - // expect(loggedUserId, eventUser.id); - // } - - // client.close(); - // }); + test('reads error message from the x-sentry-error header', () async { + final fakeClockProvider = () => DateTime.utc(2017, 1, 2); + + final httpMock = MockClient((http.Request request) async { + if (request.method == 'POST') { + return http.Response('', 401, headers: { + 'x-sentry-error': 'Invalid api key', + }); + } + fail( + 'Unexpected request on ${request.method} ${request.url} in HttpMock', + ); + }); + + final client = SentryClient( + SentryOptions( + dsn: testDsn, + ) + ..httpClient = httpMock + ..clock = fakeClockProvider + ..compressPayload = false + ..serverName = 'test.server.com' + ..release = '1.2.3' + ..environment = 'staging', + ); + + try { + throw ArgumentError('Test error'); + } catch (error, stackTrace) { + final sentryId = + await client.captureException(error, stackTrace: stackTrace); + expect('$sentryId', '00000000000000000000000000000000'); + } + + client.close(); + }); + + test('$SentryEvent user overrides client', () async { + final fakeClockProvider = () => DateTime.utc(2017, 1, 2); + + String? loggedUserId; // used to find out what user context was sent + final httpMock = MockClient((http.Request request) async { + if (request.method == 'POST') { + final bodyData = request.bodyBytes; + final decoded = const Utf8Codec().decode(bodyData); + final dynamic decodedJson = jsonDecode(decoded); + loggedUserId = decodedJson['user']['id'] as String?; + return http.Response( + '', + 401, + headers: { + 'x-sentry-error': 'Invalid api key', + }, + ); + } + fail( + 'Unexpected request on ${request.method} ${request.url} in HttpMock', + ); + }); + + final clientUser = SentryUser( + id: 'client_user', + username: 'username', + email: 'email@email.com', + ipAddress: '127.0.0.1', + ); + final eventUser = SentryUser( + id: 'event_user', + username: 'username', + email: 'email@email.com', + ipAddress: '127.0.0.1', + extras: {'foo': 'bar'}, + ); + + final options = SentryOptions( + dsn: testDsn, + ) + ..httpClient = httpMock + ..clock = fakeClockProvider + ..compressPayload = false + ..serverName = 'test.server.com' + ..release = '1.2.3' + ..environment = 'staging'; + + final client = SentryClient(options); + + try { + throw ArgumentError('Test error'); + } catch (error) { + final eventWithoutContext = SentryEvent( + eventId: SentryId.empty(), + throwable: error, + ); + final eventWithContext = SentryEvent( + eventId: SentryId.empty(), + throwable: error, + user: eventUser, + ); + await client.captureEvent( + eventWithoutContext, + scope: Scope(options)..user = clientUser, + ); + expect(loggedUserId, clientUser.id); + + await client.captureEvent( + eventWithContext, + scope: Scope(options)..user = clientUser, + ); + expect(loggedUserId, eventUser.id); + } + + client.close(); + }); } From 650b90776e9e72335e83c7d7c49b3a877221f80b Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 6 Apr 2021 17:15:23 +0200 Subject: [PATCH 30/83] format code --- dart/lib/src/sentry_client.dart | 1 - dart/lib/src/sentry_envelope_header.dart | 4 ++-- dart/lib/src/sentry_envelope_item.dart | 10 +++++----- dart/lib/src/sentry_item_type.dart | 1 - dart/lib/src/transport/noop_transport.dart | 7 ++++--- .../lib/src/transport/rate_limit_category.dart | 1 - dart/lib/src/transport/rate_limit_parser.dart | 4 +++- dart/lib/src/transport/rate_limiter.dart | 3 +-- dart/test/mocks/mock_sentry_client.dart | 11 +++-------- dart/test/protocol/rate_limit_parser_test.dart | 18 ++++++++++++------ .../test/sentry_envelope_item_header_test.dart | 6 ++++-- dart/test/sentry_envelope_test.dart | 9 ++++----- dart/test/test_utils.dart | 4 +++- dart/test/transport/http_transport_test.dart | 1 - flutter/test/sentry_flutter_test.dart | 12 ++++++------ 15 files changed, 47 insertions(+), 45 deletions(-) diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 858946f241..7c855a2b86 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:math'; -import 'current_date_provider.dart'; import 'transport/rate_limiter.dart'; import 'protocol.dart'; diff --git a/dart/lib/src/sentry_envelope_header.dart b/dart/lib/src/sentry_envelope_header.dart index 897b15b976..4fb8e23bb4 100644 --- a/dart/lib/src/sentry_envelope_header.dart +++ b/dart/lib/src/sentry_envelope_header.dart @@ -6,8 +6,8 @@ import 'protocol/sdk_version.dart'; class SentryEnvelopeHeader { SentryEnvelopeHeader(this.eventId, this.sdkVersion); SentryEnvelopeHeader.newEventId() - : eventId = SentryId.newId(), - sdkVersion = null; + : eventId = SentryId.newId(), + sdkVersion = null; final SentryId? eventId; final SdkVersion? sdkVersion; diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index d126f1d106..91420f329f 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -9,14 +9,14 @@ class SentryEnvelopeItem { final SentryEnvelopeItemHeader header; final List data; - + static SentryEnvelopeItem fromEvent(SentryEvent event) { final jsonEncoded = jsonEncode(event.toJson()); - final data = utf8.encode(jsonEncoded); + final data = utf8.encode(jsonEncoded); return SentryEnvelopeItem( - SentryEnvelopeItemHeader(SentryItemType.event, data.length, contentType: 'application/json'), - data - ); + SentryEnvelopeItemHeader(SentryItemType.event, data.length, + contentType: 'application/json'), + data); } String serialize() { diff --git a/dart/lib/src/sentry_item_type.dart b/dart/lib/src/sentry_item_type.dart index 2606faffc5..1cda04d06f 100644 --- a/dart/lib/src/sentry_item_type.dart +++ b/dart/lib/src/sentry_item_type.dart @@ -1,7 +1,6 @@ enum SentryItemType { event, unknown } extension SentryItemTypeExtension on SentryItemType { - static SentryItemType fromStringValue(String stringValue) { switch (stringValue) { case 'event': diff --git a/dart/lib/src/transport/noop_transport.dart b/dart/lib/src/transport/noop_transport.dart index 525bfdcadb..a574d43c14 100644 --- a/dart/lib/src/transport/noop_transport.dart +++ b/dart/lib/src/transport/noop_transport.dart @@ -6,10 +6,11 @@ import '../protocol.dart'; import 'transport.dart'; class NoOpTransport implements Transport { - @override - Future sendSentryEvent(SentryEvent event) => Future.value(SentryId.empty()); + Future sendSentryEvent(SentryEvent event) => + Future.value(SentryId.empty()); @override - Future sendSentryEnvelope(SentryEnvelope envelope) => Future.value(SentryId.empty()); + Future sendSentryEnvelope(SentryEnvelope envelope) => + Future.value(SentryId.empty()); } diff --git a/dart/lib/src/transport/rate_limit_category.dart b/dart/lib/src/transport/rate_limit_category.dart index 027aad8b76..01f465dbb3 100644 --- a/dart/lib/src/transport/rate_limit_category.dart +++ b/dart/lib/src/transport/rate_limit_category.dart @@ -10,7 +10,6 @@ enum RateLimitCategory { } extension RateLimitCategoryExtension on RateLimitCategory { - static RateLimitCategory fromStringValue(String stringValue) { switch (stringValue) { case '__all__': diff --git a/dart/lib/src/transport/rate_limit_parser.dart b/dart/lib/src/transport/rate_limit_parser.dart index 416e13a336..d1d4d62436 100644 --- a/dart/lib/src/transport/rate_limit_parser.dart +++ b/dart/lib/src/transport/rate_limit_parser.dart @@ -45,7 +45,9 @@ class RateLimitParser { } List parseRetryAfterHeader() { - return [RateLimit(RateLimitCategory.all, _parseRetryAfterOrDefault(header))]; + return [ + RateLimit(RateLimitCategory.all, _parseRetryAfterOrDefault(header)) + ]; } // Helper diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index 1c0ed43ae4..ce2181211e 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -73,8 +73,7 @@ class RateLimiter { bool _isRetryAfter(String itemType) { final dataCategory = _categoryFromItemType(itemType); final currentDate = DateTime.fromMillisecondsSinceEpoch( - _clockProvider().millisecondsSinceEpoch - ); + _clockProvider().millisecondsSinceEpoch); // check all categories final dateAllCategories = _rateLimitedUntil[RateLimitCategory.all]; diff --git a/dart/test/mocks/mock_sentry_client.dart b/dart/test/mocks/mock_sentry_client.dart index 9328f30578..9b6f4175fe 100644 --- a/dart/test/mocks/mock_sentry_client.dart +++ b/dart/test/mocks/mock_sentry_client.dart @@ -61,11 +61,8 @@ class MockSentryClient implements SentryClient { } @override - Future captureEnvelope( - SentryEnvelope envelope) async { - captureEnvelopeCalls.add(CaptureEnvelopeCall( - envelope - )); + Future captureEnvelope(SentryEnvelope envelope) async { + captureEnvelopeCalls.add(CaptureEnvelopeCall(envelope)); return SentryId.newId(); } @@ -124,7 +121,5 @@ class CaptureMessageCall { class CaptureEnvelopeCall { final SentryEnvelope envelope; - CaptureEnvelopeCall( - this.envelope - ); + CaptureEnvelopeCall(this.envelope); } diff --git a/dart/test/protocol/rate_limit_parser_test.dart b/dart/test/protocol/rate_limit_parser_test.dart index 548985ade4..0e120b3fbd 100644 --- a/dart/test/protocol/rate_limit_parser_test.dart +++ b/dart/test/protocol/rate_limit_parser_test.dart @@ -13,7 +13,8 @@ void main() { }); test('single rate limit with multiple categories', () { - final sut = RateLimitParser('50:transaction;session').parseRateLimitHeader(); + final sut = + RateLimitParser('50:transaction;session').parseRateLimitHeader(); expect(sut.length, 2); expect(sut[0].category, RateLimitCategory.transaction); @@ -37,7 +38,8 @@ void main() { }); test('multiple rate limits', () { - final sut = RateLimitParser('50:transaction, 70:session').parseRateLimitHeader(); + final sut = + RateLimitParser('50:transaction, 70:session').parseRateLimitHeader(); expect(sut.length, 2); expect(sut[0].category, RateLimitCategory.transaction); @@ -47,7 +49,8 @@ void main() { }); test('multiple rate limits with same category', () { - final sut = RateLimitParser('50:transaction, 70:transaction').parseRateLimitHeader(); + final sut = RateLimitParser('50:transaction, 70:transaction') + .parseRateLimitHeader(); expect(sut.length, 2); expect(sut[0].category, RateLimitCategory.transaction); @@ -69,7 +72,8 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + expect(sut[0].durationInMillis, + RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); }); }); @@ -79,7 +83,8 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.all); - expect(sut[0].durationInMillis, RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + expect(sut[0].durationInMillis, + RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); }); test('parseable returns default category with duration in millis', () { @@ -95,7 +100,8 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.all); - expect(sut[0].durationInMillis, RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + expect(sut[0].durationInMillis, + RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); }); }); } diff --git a/dart/test/sentry_envelope_item_header_test.dart b/dart/test/sentry_envelope_item_header_test.dart index 6105ee6b84..9bd007321f 100644 --- a/dart/test/sentry_envelope_item_header_test.dart +++ b/dart/test/sentry_envelope_item_header_test.dart @@ -5,8 +5,10 @@ import 'package:test/test.dart'; void main() { group('SentryEnvelopeItemHeader', () { test('serialize', () { - final sut = SentryEnvelopeItemHeader(SentryItemType.event, 3, contentType: 'application/json'); - final expected = '{\"content_type\":\"application/json\",\"type\":\"event\",\"length\":3}'; + final sut = SentryEnvelopeItemHeader(SentryItemType.event, 3, + contentType: 'application/json'); + final expected = + '{\"content_type\":\"application/json\",\"type\":\"event\",\"length\":3}'; expect(sut.serialize(), expected); }); }); diff --git a/dart/test/sentry_envelope_test.dart b/dart/test/sentry_envelope_test.dart index 037307c19d..8fcb93449f 100644 --- a/dart/test/sentry_envelope_test.dart +++ b/dart/test/sentry_envelope_test.dart @@ -30,17 +30,16 @@ void main() { test('fromEvent', () { final eventId = SentryId.newId(); final sentryEvent = SentryEvent(eventId: eventId); - final sdkVersion = SdkVersion( - name: 'fixture-name', - version: 'fixture-version' - ); + final sdkVersion = + SdkVersion(name: 'fixture-name', version: 'fixture-version'); final sut = SentryEnvelope.fromEvent(sentryEvent, sdkVersion); final expectedEnvelopeItem = SentryEnvelopeItem.fromEvent(sentryEvent); expect(sut.header.eventId, eventId); expect(sut.header.sdkVersion, sdkVersion); - expect(sut.items[0].header.contentType, expectedEnvelopeItem.header.contentType); + expect(sut.items[0].header.contentType, + expectedEnvelopeItem.header.contentType); expect(sut.items[0].header.type, expectedEnvelopeItem.header.type); expect(sut.items[0].header.length, expectedEnvelopeItem.header.length); expect(sut.items[0].data, expectedEnvelopeItem.data); diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index 827b3a391c..49a1791795 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -351,7 +351,9 @@ void runTest({Codec, List?>? gzip, bool isWeb = false}) { if (request.method == 'POST') { final bodyData = request.bodyBytes; final decoded = const Utf8Codec().decode(bodyData); - final dynamic decodedJson = jsonDecode(decoded); + final eventJson = decoded.split('\n').last; + final dynamic decodedJson = json.decode(eventJson); + loggedUserId = decodedJson['user']['id'] as String?; return http.Response( '', diff --git a/dart/test/transport/http_transport_test.dart b/dart/test/transport/http_transport_test.dart index 198fb5272e..55ffea1449 100644 --- a/dart/test/transport/http_transport_test.dart +++ b/dart/test/transport/http_transport_test.dart @@ -14,7 +14,6 @@ import 'package:sentry/src/transport/http_transport.dart'; import '../mocks.dart'; void main() { - SentryEnvelope givenEnvelope() { final filteredEnvelopeHeader = SentryEnvelopeHeader(SentryId.empty(), null); final filteredItemHeader = SentryEnvelopeItemHeader(SentryItemType.event, 2, diff --git a/flutter/test/sentry_flutter_test.dart b/flutter/test/sentry_flutter_test.dart index 5072f45c03..cf48690dfd 100644 --- a/flutter/test/sentry_flutter_test.dart +++ b/flutter/test/sentry_flutter_test.dart @@ -73,8 +73,8 @@ void main() { await Sentry.captureMessage('a message'); - final event = - verify(transport.sendSentryEvent(captureAny)).captured.first as SentryEvent; + final event = verify(transport.sendSentryEvent(captureAny)).captured.first + as SentryEvent; expect(event.sdk!.integrations.length, 7); expect(event.sdk!.integrations.contains('loadContextsIntegration'), true); @@ -92,8 +92,8 @@ void main() { await Sentry.captureMessage('a message'); - final event = - verify(transport.sendSentryEvent(captureAny)).captured.first as SentryEvent; + final event = verify(transport.sendSentryEvent(captureAny)).captured.first + as SentryEvent; expect(event.sdk!.integrations.length, 6); expect( @@ -113,8 +113,8 @@ void main() { await Sentry.captureMessage('a message'); - final event = - verify(transport.sendSentryEvent(captureAny)).captured.first as SentryEvent; + final event = verify(transport.sendSentryEvent(captureAny)).captured.first + as SentryEvent; expect(event.sdk!.integrations.length, 6); expect( From 9acd08ec416d173972add509bd056a7414486eea Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 6 Apr 2021 17:21:25 +0200 Subject: [PATCH 31/83] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b4f11c813..1fa854a804 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * Fix: `Sentry.close()` closes native SDK integrations (#388) * Fix: Mark `Sentry.currentHub` as deprecated (#406) +* Feat: Support envelope based transport for events (#391) # 5.0.0 From 1f177a49b8b4545d8bc21d28469f82754d1b208d Mon Sep 17 00:00:00 2001 From: denrase Date: Wed, 7 Apr 2021 15:43:09 +0200 Subject: [PATCH 32/83] Defer serialization, Serialize to bytes --- dart/lib/src/sentry_envelope.dart | 14 ++++--- dart/lib/src/sentry_envelope_header.dart | 4 +- dart/lib/src/sentry_envelope_item.dart | 39 +++++++++++++++---- dart/lib/src/sentry_envelope_item_header.dart | 8 ++-- dart/lib/src/transport/http_transport.dart | 8 ++-- dart/test/sentry_envelope_header_test.dart | 12 +++--- .../sentry_envelope_item_header_test.dart | 12 +++--- dart/test/sentry_envelope_item_test.dart | 32 +++++++++------ dart/test/sentry_envelope_test.dart | 30 ++++++++------ dart/test/transport/http_transport_test.dart | 18 +++++---- flutter/lib/src/file_system_transport.dart | 7 +++- 11 files changed, 119 insertions(+), 65 deletions(-) diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index 8d82bc3344..70d3c8de74 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'sentry_envelope_header.dart'; import 'sentry_envelope_item.dart'; import 'protocol/sentry_event.dart'; @@ -14,12 +16,14 @@ class SentryEnvelope { [SentryEnvelopeItem.fromEvent(event)]); } - String serialize() { - final lines = []; - lines.add(header.serialize()); + Future> serialize() async { + var data = []; + data.addAll(await header.serialize()); + final newLineData = utf8.encode('\n'); for (final item in items) { - lines.add(item.serialize()); + data.addAll(newLineData); + data.addAll(await item.serialize()); } - return lines.join('\n'); + return data; } } diff --git a/dart/lib/src/sentry_envelope_header.dart b/dart/lib/src/sentry_envelope_header.dart index 4fb8e23bb4..81e18dfadf 100644 --- a/dart/lib/src/sentry_envelope_header.dart +++ b/dart/lib/src/sentry_envelope_header.dart @@ -12,7 +12,7 @@ class SentryEnvelopeHeader { final SentryId? eventId; final SdkVersion? sdkVersion; - String serialize() { + Future> serialize() async { final serializedMap = {}; if (eventId != null) { serializedMap['event_id'] = eventId!.toString(); @@ -20,6 +20,6 @@ class SentryEnvelopeHeader { if (sdkVersion != null) { serializedMap['sdk'] = sdkVersion!.toJson(); } - return jsonEncode(serializedMap); + return utf8.encode(jsonEncode(serializedMap)); } } diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index 91420f329f..6c9f8d5623 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -5,21 +5,44 @@ import 'protocol/sentry_event.dart'; import 'sentry_envelope_item_header.dart'; class SentryEnvelopeItem { - SentryEnvelopeItem(this.header, this.data); + SentryEnvelopeItem(this.header, this.dataFactory); final SentryEnvelopeItemHeader header; - final List data; + final Future> Function() dataFactory; static SentryEnvelopeItem fromEvent(SentryEvent event) { - final jsonEncoded = jsonEncode(event.toJson()); - final data = utf8.encode(jsonEncoded); + final cachedItem = CachedItem(() async { + final jsonEncoded = jsonEncode(event.toJson()); + return utf8.encode(jsonEncoded); + }); + + final getLength = () async { + return (await cachedItem.getData()).length; + }; + return SentryEnvelopeItem( - SentryEnvelopeItemHeader(SentryItemType.event, data.length, + SentryEnvelopeItemHeader(SentryItemType.event, getLength, contentType: 'application/json'), - data); + cachedItem.getData); + } + + Future> serialize() async { + var data = []; + data.addAll(await header.serialize()); + data.addAll(utf8.encode('\n')); + data.addAll(await dataFactory()); + return data; } +} + +class CachedItem { + CachedItem(this.dataFactory); + + List? data; + Future> Function() dataFactory; - String serialize() { - return '${header.serialize()}\n${utf8.decode(data)}'; + Future> getData() async { + data ??= await dataFactory(); + return data!; } } diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart index 6f2e8c4a85..0ddc9ec95a 100644 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -7,18 +7,18 @@ class SentryEnvelopeItemHeader { {this.contentType, this.fileName}); final SentryItemType type; - final int length; + final Future Function() length; final String? contentType; final String? fileName; - String serialize() { + Future> serialize() async { final serializedMap = {}; if (contentType != null) { serializedMap['content_type'] = contentType!; } serializedMap['type'] = type.toStringValue(); - serializedMap['length'] = length; - return jsonEncode(serializedMap); + serializedMap['length'] = await length(); + return utf8.encode(jsonEncode(serializedMap)); } } diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index ff162f3ffc..a28f7ef4be 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -56,7 +56,7 @@ class HttpTransport implements Transport { return null; } - final data = filteredEnvelope.serialize(); + final data = await filteredEnvelope.serialize(); final body = _bodyEncoder( data, @@ -95,15 +95,15 @@ class HttpTransport implements Transport { } List _bodyEncoder( - String data, + List data, Map headers, { required bool compressPayload, }) { // [SentryIOClient] implement gzip compression // gzip compression is not available on browser - var body = utf8.encode(data); + var body = data; if (compressPayload) { - body = compressBody(body, headers); + body = compressBody(data, headers); } return body; } diff --git a/dart/test/sentry_envelope_header_test.dart b/dart/test/sentry_envelope_header_test.dart index 922b359a93..e235eb79ec 100644 --- a/dart/test/sentry_envelope_header_test.dart +++ b/dart/test/sentry_envelope_header_test.dart @@ -6,13 +6,13 @@ import 'package:test/test.dart'; void main() { group('SentryEnvelopeItemHeader', () { - test('serialize empty', () { + test('serialize empty', () async { final sut = SentryEnvelopeHeader(null, null); - final expected = '{}'; - expect(sut.serialize(), expected); + final expected = utf8.encode('{}'); + expect(await sut.serialize(), expected); }); - test('serialize', () { + test('serialize', () async { final eventId = SentryId.newId(); final sdkVersion = SdkVersion( name: 'fixture-sdkName', @@ -20,8 +20,8 @@ void main() { ); final sut = SentryEnvelopeHeader(eventId, sdkVersion); final expextedSkd = jsonEncode(sdkVersion.toJson()); - final expected = '{\"event_id\":\"$eventId\",\"sdk\":$expextedSkd}'; - expect(sut.serialize(), expected); + final expected = utf8.encode('{\"event_id\":\"$eventId\",\"sdk\":$expextedSkd}'); + expect(await sut.serialize(), expected); }); }); } diff --git a/dart/test/sentry_envelope_item_header_test.dart b/dart/test/sentry_envelope_item_header_test.dart index 9bd007321f..cf66ee4011 100644 --- a/dart/test/sentry_envelope_item_header_test.dart +++ b/dart/test/sentry_envelope_item_header_test.dart @@ -1,15 +1,17 @@ +import 'dart:convert'; + import 'package:sentry/src/sentry_envelope_item_header.dart'; import 'package:sentry/src/sentry_item_type.dart'; import 'package:test/test.dart'; void main() { group('SentryEnvelopeItemHeader', () { - test('serialize', () { - final sut = SentryEnvelopeItemHeader(SentryItemType.event, 3, + test('serialize', () async { + final sut = SentryEnvelopeItemHeader(SentryItemType.event, () async { return 3; }, contentType: 'application/json'); - final expected = - '{\"content_type\":\"application/json\",\"type\":\"event\",\"length\":3}'; - expect(sut.serialize(), expected); + final expected = utf8.encode( + '{\"content_type\":\"application/json\",\"type\":\"event\",\"length\":3}'); + expect(await sut.serialize(), expected); }); }); } diff --git a/dart/test/sentry_envelope_item_test.dart b/dart/test/sentry_envelope_item_test.dart index 5e026d090e..af349aaec5 100644 --- a/dart/test/sentry_envelope_item_test.dart +++ b/dart/test/sentry_envelope_item_test.dart @@ -9,27 +9,37 @@ import 'package:test/test.dart'; void main() { group('SentryEnvelopeItem', () { - test('serialize', () { - final header = SentryEnvelopeItemHeader(SentryItemType.event, 9, - contentType: 'application/json'); - final sut = SentryEnvelopeItem( - header, [123, 102, 105, 120, 116, 117, 114, 101, 125]); // {fixture} - - final expected = '${header.serialize()}\n{fixture}'; - expect(sut.serialize(), expected); + test('serialize', () async { + final header = SentryEnvelopeItemHeader(SentryItemType.event, () async { + return 9; + }, contentType: 'application/json'); + + final dataFactory = () async { + return utf8.encode('{fixture}'); + }; + + final sut = SentryEnvelopeItem(header, dataFactory); + + final expected = + utf8.encode('${utf8.decode(await header.serialize())}\n{fixture}'); + expect(await sut.serialize(), expected); }); - test('fromEvent', () { + test('fromEvent', () async { final eventId = SentryId.newId(); final sentryEvent = SentryEvent(eventId: eventId); final sut = SentryEnvelopeItem.fromEvent(sentryEvent); final expectedData = utf8.encode(jsonEncode(sentryEvent.toJson())); + final actualData = await sut.dataFactory(); + + final expectedLength = expectedData.length; + final actualLength = await sut.header.length(); expect(sut.header.contentType, 'application/json'); expect(sut.header.type, SentryItemType.event); - expect(sut.header.length, expectedData.length); - expect(sut.data, expectedData); + expect(actualLength, expectedLength); + expect(actualData, expectedData); }); }); } diff --git a/dart/test/sentry_envelope_test.dart b/dart/test/sentry_envelope_test.dart index 8fcb93449f..ceb1911094 100644 --- a/dart/test/sentry_envelope_test.dart +++ b/dart/test/sentry_envelope_test.dart @@ -11,23 +11,30 @@ import 'package:test/test.dart'; void main() { group('SentryEnvelopeItem', () { - test('serialize', () { + test('serialize', () async { final eventId = SentryId.newId(); - final itemHeader = SentryEnvelopeItemHeader(SentryItemType.event, 9, - contentType: 'application/json'); - final item = - SentryEnvelopeItem(itemHeader, utf8.encode('{fixture}')); // {fixture} + final itemHeader = + SentryEnvelopeItemHeader(SentryItemType.event, () async { + return 9; + }, contentType: 'application/json'); + + final dataFactory = () async { + return utf8.encode('{fixture}'); + }; + + final item = SentryEnvelopeItem(itemHeader, dataFactory); final header = SentryEnvelopeHeader(eventId, null); final sut = SentryEnvelope(header, [item, item]); - final expected = - '${header.serialize()}\n${itemHeader.serialize()}\n{fixture}\n${itemHeader.serialize()}\n{fixture}'; - expect(sut.serialize(), expected); + final expected = utf8.encode( + '${utf8.decode(await header.serialize())}\n${utf8.decode(await itemHeader.serialize())}\n{fixture}\n${utf8.decode(await itemHeader.serialize())}\n{fixture}'); + final actual = await sut.serialize(); + expect(actual, expected); }); - test('fromEvent', () { + test('fromEvent', () async { final eventId = SentryId.newId(); final sentryEvent = SentryEvent(eventId: eventId); final sdkVersion = @@ -41,8 +48,9 @@ void main() { expect(sut.items[0].header.contentType, expectedEnvelopeItem.header.contentType); expect(sut.items[0].header.type, expectedEnvelopeItem.header.type); - expect(sut.items[0].header.length, expectedEnvelopeItem.header.length); - expect(sut.items[0].data, expectedEnvelopeItem.data); + expect(await sut.items[0].header.length(), + await expectedEnvelopeItem.header.length()); + expect(await sut.items[0].serialize(), await expectedEnvelopeItem.serialize()); }); }); } diff --git a/dart/test/transport/http_transport_test.dart b/dart/test/transport/http_transport_test.dart index 55ffea1449..af413002f0 100644 --- a/dart/test/transport/http_transport_test.dart +++ b/dart/test/transport/http_transport_test.dart @@ -16,10 +16,14 @@ import '../mocks.dart'; void main() { SentryEnvelope givenEnvelope() { final filteredEnvelopeHeader = SentryEnvelopeHeader(SentryId.empty(), null); - final filteredItemHeader = SentryEnvelopeItemHeader(SentryItemType.event, 2, - contentType: 'application/json'); - final filteredItem = - SentryEnvelopeItem(filteredItemHeader, utf8.encode('{}')); + final filteredItemHeader = + SentryEnvelopeItemHeader(SentryItemType.event, () async { + return 2; + }, contentType: 'application/json'); + final dataFactory = () async { + return utf8.encode('{}'); + }; + final filteredItem = SentryEnvelopeItem(filteredItemHeader, dataFactory); return SentryEnvelope(filteredEnvelopeHeader, [filteredItem]); } @@ -45,10 +49,10 @@ void main() { }); test('send filtered event', () async { - String? body; + List? body; final httpMock = MockClient((http.Request request) async { - body = request.body; + body = request.bodyBytes; return http.Response('{}', 200); }); @@ -66,7 +70,7 @@ void main() { final sentryEvent = SentryEvent(); await sut.sendSentryEvent(sentryEvent); - expect(body, filteredEnvelope.serialize()); + expect(body, await filteredEnvelope.serialize()); }); test('send nothing when filtered event null', () async { diff --git a/flutter/lib/src/file_system_transport.dart b/flutter/lib/src/file_system_transport.dart index 0b26c94ae8..af6a18d0ef 100644 --- a/flutter/lib/src/file_system_transport.dart +++ b/flutter/lib/src/file_system_transport.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/services.dart'; import 'package:sentry/sentry.dart'; @@ -15,8 +17,9 @@ class FileSystemTransport implements Transport { @override Future sendSentryEnvelope(SentryEnvelope envelope) async { - final envelopeString = envelope.serialize(); - + final envelopeData = await envelope.serialize(); + final envelopeString = utf8.decode(envelopeData); + final args = [envelopeString]; try { await _channel.invokeMethod('captureEnvelope', args); From e4dc045b6d46b450498900f0cf31e6df599901d2 Mon Sep 17 00:00:00 2001 From: denrase Date: Wed, 7 Apr 2021 15:58:06 +0200 Subject: [PATCH 33/83] Use factory instead of static --- dart/lib/src/sentry_envelope.dart | 2 +- dart/lib/src/sentry_envelope_item.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index 70d3c8de74..b8372b782e 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -11,7 +11,7 @@ class SentryEnvelope { final SentryEnvelopeHeader header; final List items; - static SentryEnvelope fromEvent(SentryEvent event, SdkVersion sdkVersion) { + factory SentryEnvelope.fromEvent(SentryEvent event, SdkVersion sdkVersion) { return SentryEnvelope(SentryEnvelopeHeader(event.eventId, sdkVersion), [SentryEnvelopeItem.fromEvent(event)]); } diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index 6c9f8d5623..f9d6c4e36b 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -10,7 +10,7 @@ class SentryEnvelopeItem { final SentryEnvelopeItemHeader header; final Future> Function() dataFactory; - static SentryEnvelopeItem fromEvent(SentryEvent event) { + factory SentryEnvelopeItem.fromEvent(SentryEvent event) { final cachedItem = CachedItem(() async { final jsonEncoded = jsonEncode(event.toJson()); return utf8.encode(jsonEncoded); From 103bf08c0b2694689a673a5c882a931b6d6ba2e5 Mon Sep 17 00:00:00 2001 From: denrase Date: Wed, 7 Apr 2021 16:04:09 +0200 Subject: [PATCH 34/83] Remove todo --- dart/lib/src/transport/rate_limiter.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index ce2181211e..8ce61bb087 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -36,8 +36,6 @@ class RateLimiter { // no reason to continue if (toSend.isEmpty) { - // TODO(denis): Should hints be implemented as well? - //markHintWhenSendingFailed(hint, false); return null; } From 9e13272f6bc9b0745829a9da03b4028eb68e14cb Mon Sep 17 00:00:00 2001 From: denrase Date: Wed, 7 Apr 2021 16:05:32 +0200 Subject: [PATCH 35/83] Remove unused main function --- dart/test/mocks.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/dart/test/mocks.dart b/dart/test/mocks.dart index a0f26c2ef0..f57470436f 100644 --- a/dart/test/mocks.dart +++ b/dart/test/mocks.dart @@ -2,8 +2,6 @@ import 'package:sentry/sentry.dart'; import 'package:sentry/src/protocol.dart'; import 'package:sentry/src/transport/rate_limiter.dart'; -void main() {} - final fakeDsn = 'https://abc@def.ingest.sentry.io/1234567'; final fakeException = Exception('Error'); From 07642d39cf80e5ce4b6cb7683b05fb00429cf1bb Mon Sep 17 00:00:00 2001 From: denrase Date: Wed, 7 Apr 2021 16:07:22 +0200 Subject: [PATCH 36/83] Use local variable for dart type inference to kick in --- dart/lib/src/sentry_envelope_header.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dart/lib/src/sentry_envelope_header.dart b/dart/lib/src/sentry_envelope_header.dart index 81e18dfadf..3df36c3a5a 100644 --- a/dart/lib/src/sentry_envelope_header.dart +++ b/dart/lib/src/sentry_envelope_header.dart @@ -14,11 +14,13 @@ class SentryEnvelopeHeader { Future> serialize() async { final serializedMap = {}; - if (eventId != null) { - serializedMap['event_id'] = eventId!.toString(); + final tempEventId = eventId; + if (tempEventId != null) { + serializedMap['event_id'] = tempEventId.toString(); } - if (sdkVersion != null) { - serializedMap['sdk'] = sdkVersion!.toJson(); + final tempSdkVersion = sdkVersion; + if (tempSdkVersion != null) { + serializedMap['sdk'] = tempSdkVersion.toJson(); } return utf8.encode(jsonEncode(serializedMap)); } From 5f7d42ce5f39a6709adc6810fcc4a8eb7fe681b2 Mon Sep 17 00:00:00 2001 From: denrase Date: Wed, 7 Apr 2021 16:16:22 +0200 Subject: [PATCH 37/83] send correct header for envelope data --- dart/lib/src/transport/http_transport.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index a28f7ef4be..9840816e10 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -172,7 +172,7 @@ class _CredentialBuilder { } Map _buildHeaders(String sdkIdentifier) { - final headers = {'Content-Type': 'application/json'}; + final headers = {'Content-Type': 'application/x-sentry-envelope'}; // NOTE(lejard_h) overriding user agent on VM and Flutter not sure why // for web it use browser user agent if (!isWeb) { From c8c499a686010248a443f7a25071aeb270857a03 Mon Sep 17 00:00:00 2001 From: denrase Date: Wed, 7 Apr 2021 16:22:22 +0200 Subject: [PATCH 38/83] Change const name to dart style --- dart/test/protocol/rate_limit_parser_test.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dart/test/protocol/rate_limit_parser_test.dart b/dart/test/protocol/rate_limit_parser_test.dart index 0e120b3fbd..d39e9e2478 100644 --- a/dart/test/protocol/rate_limit_parser_test.dart +++ b/dart/test/protocol/rate_limit_parser_test.dart @@ -73,7 +73,7 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.transaction); expect(sut[0].durationInMillis, - RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + RateLimitParser.httpRetryAfterDefaultDelayMillis); }); }); @@ -84,7 +84,7 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.all); expect(sut[0].durationInMillis, - RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + RateLimitParser.httpRetryAfterDefaultDelayMillis); }); test('parseable returns default category with duration in millis', () { @@ -101,7 +101,7 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.all); expect(sut[0].durationInMillis, - RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS); + RateLimitParser.httpRetryAfterDefaultDelayMillis); }); }); } From c18c442287ce36b76724171297457b291d29a1a1 Mon Sep 17 00:00:00 2001 From: denrase Date: Wed, 7 Apr 2021 16:25:08 +0200 Subject: [PATCH 39/83] Leave in file system transport --- dart/lib/sentry.dart | 2 +- dart/lib/src/transport/rate_limit_parser.dart | 5 +++-- flutter/lib/src/sentry_flutter.dart | 3 ++- flutter/test/sentry_flutter_util.dart | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index 649ec6d31e..db3a97055f 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -12,7 +12,7 @@ export 'src/noop_isolate_error_integration.dart' export 'src/protocol.dart'; export 'src/scope.dart'; export 'src/sentry.dart'; -export 'src/sentry_envelope.dart'; //TODO(denis) Remove this again if we will remove file system transport. +export 'src/sentry_envelope.dart'; export 'src/sentry_client.dart'; export 'src/sentry_options.dart'; // useful for integrations diff --git a/dart/lib/src/transport/rate_limit_parser.dart b/dart/lib/src/transport/rate_limit_parser.dart index d1d4d62436..8c95f4ca30 100644 --- a/dart/lib/src/transport/rate_limit_parser.dart +++ b/dart/lib/src/transport/rate_limit_parser.dart @@ -4,7 +4,7 @@ import 'rate_limit.dart'; class RateLimitParser { RateLimitParser(this.header); - static const HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS = 60000; + static const httpRetryAfterDefaultDelayMillis = 60000; String? header; @@ -53,11 +53,12 @@ class RateLimitParser { // Helper static int _parseRetryAfterOrDefault(String? value) { + int.parse(source) final durationInSeconds = int.tryParse(value ?? ''); if (durationInSeconds != null) { return durationInSeconds * 1000; } else { - return RateLimitParser.HTTP_RETRY_AFTER_DEFAULT_DELAY_MILLIS; + return RateLimitParser.httpRetryAfterDefaultDelayMillis; } } } diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index babee787a7..f80263aeb2 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -6,6 +6,7 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:sentry/sentry.dart'; import 'sentry_flutter_options.dart'; +import 'file_system_transport.dart'; import 'default_integrations.dart'; //import 'file_system_transport.dart'; import 'version.dart'; @@ -62,7 +63,7 @@ mixin SentryFlutter { ) async { // web still uses a http transport for Web which is set by default if (!kIsWeb) { - //options.transport = FileSystemTransport(channel, options); + options.transport = FileSystemTransport(channel, options); } _setSdk(options); diff --git a/flutter/test/sentry_flutter_util.dart b/flutter/test/sentry_flutter_util.dart index 0456482e6f..533344269a 100644 --- a/flutter/test/sentry_flutter_util.dart +++ b/flutter/test/sentry_flutter_util.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -//import 'package:sentry_flutter/src/file_system_transport.dart'; +import 'package:sentry_flutter/src/file_system_transport.dart'; import 'package:sentry_flutter/src/sentry_flutter_options.dart'; import 'package:sentry_flutter/src/version.dart'; @@ -22,7 +22,7 @@ FutureOr Function(SentryOptions) getConfigurationTester({ expect(kDebugMode, options.debug); expect('debug', options.environment); - // expect(!isWeb, options.transport is FileSystemTransport); + expect(!isWeb, options.transport is FileSystemTransport); expect( options.integrations.whereType().length, From 30754b8d2a78aa062d834726575d0dd18100c578 Mon Sep 17 00:00:00 2001 From: denrase Date: Wed, 7 Apr 2021 16:25:58 +0200 Subject: [PATCH 40/83] Remove debug code --- dart/lib/src/transport/rate_limit_parser.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/dart/lib/src/transport/rate_limit_parser.dart b/dart/lib/src/transport/rate_limit_parser.dart index 8c95f4ca30..3966d1bc95 100644 --- a/dart/lib/src/transport/rate_limit_parser.dart +++ b/dart/lib/src/transport/rate_limit_parser.dart @@ -53,7 +53,6 @@ class RateLimitParser { // Helper static int _parseRetryAfterOrDefault(String? value) { - int.parse(source) final durationInSeconds = int.tryParse(value ?? ''); if (durationInSeconds != null) { return durationInSeconds * 1000; From 424b5de135ff3c337209e2534fbe92b880f3f9c8 Mon Sep 17 00:00:00 2001 From: denrase Date: Wed, 7 Apr 2021 16:32:10 +0200 Subject: [PATCH 41/83] Use forEach instead of for loops --- dart/lib/src/transport/rate_limit_parser.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dart/lib/src/transport/rate_limit_parser.dart b/dart/lib/src/transport/rate_limit_parser.dart index 3966d1bc95..24627f9c6e 100644 --- a/dart/lib/src/transport/rate_limit_parser.dart +++ b/dart/lib/src/transport/rate_limit_parser.dart @@ -17,7 +17,7 @@ class RateLimitParser { final rateLimits = []; final rateLimitValues = rateLimitHeader.toLowerCase().split(','); - for (final rateLimitValue in rateLimitValues) { + rateLimitValues.forEach((rateLimitValue) { final durationAndCategories = rateLimitValue.trim().split(':'); if (durationAndCategories.isNotEmpty) { @@ -28,19 +28,19 @@ class RateLimitParser { final allCategories = durationAndCategories[1]; if (allCategories.isNotEmpty) { final categoryValues = durationAndCategories[1].split(';'); - for (final categoryValue in categoryValues) { + categoryValues.forEach((categoryValue) { final category = RateLimitCategoryExtension.fromStringValue(categoryValue); if (category != RateLimitCategory.unknown) { rateLimits.add(RateLimit(category, durationInMillis)); } - } + }); } else { rateLimits.add(RateLimit(RateLimitCategory.all, durationInMillis)); } } } - } + }); return rateLimits; } From c92173465091dd166ffc0e2322fef879107b47ad Mon Sep 17 00:00:00 2001 From: denrase Date: Wed, 7 Apr 2021 16:32:41 +0200 Subject: [PATCH 42/83] Use variable instead of list access again --- dart/lib/src/transport/rate_limit_parser.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dart/lib/src/transport/rate_limit_parser.dart b/dart/lib/src/transport/rate_limit_parser.dart index 24627f9c6e..db300e168a 100644 --- a/dart/lib/src/transport/rate_limit_parser.dart +++ b/dart/lib/src/transport/rate_limit_parser.dart @@ -27,7 +27,7 @@ class RateLimitParser { if (durationAndCategories.length > 1) { final allCategories = durationAndCategories[1]; if (allCategories.isNotEmpty) { - final categoryValues = durationAndCategories[1].split(';'); + final categoryValues = allCategories.split(';'); categoryValues.forEach((categoryValue) { final category = RateLimitCategoryExtension.fromStringValue(categoryValue); From f753638ea36ceb7b0bc6f9966d37de49138a3664 Mon Sep 17 00:00:00 2001 From: denrase Date: Wed, 7 Apr 2021 16:33:23 +0200 Subject: [PATCH 43/83] Run format --- flutter/lib/src/file_system_transport.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/src/file_system_transport.dart b/flutter/lib/src/file_system_transport.dart index af6a18d0ef..c788b0cb79 100644 --- a/flutter/lib/src/file_system_transport.dart +++ b/flutter/lib/src/file_system_transport.dart @@ -19,7 +19,7 @@ class FileSystemTransport implements Transport { Future sendSentryEnvelope(SentryEnvelope envelope) async { final envelopeData = await envelope.serialize(); final envelopeString = utf8.decode(envelopeData); - + final args = [envelopeString]; try { await _channel.invokeMethod('captureEnvelope', args); From 4d5dcc58c3bfc5fed8cab6c6df6ad719dbbd5a66 Mon Sep 17 00:00:00 2001 From: denrase Date: Wed, 7 Apr 2021 18:39:17 +0200 Subject: [PATCH 44/83] Update envelope class serielaization method naming and return types --- dart/lib/src/sentry_envelope.dart | 6 +++--- dart/lib/src/sentry_envelope_header.dart | 12 +++++------- dart/lib/src/sentry_envelope_item.dart | 4 ++-- dart/lib/src/sentry_envelope_item_header.dart | 14 ++++++-------- dart/lib/src/transport/http_transport.dart | 2 +- dart/test/sentry_envelope_header_test.dart | 14 +++++++------- dart/test/sentry_envelope_item_header_test.dart | 7 ++----- dart/test/sentry_envelope_item_test.dart | 6 ++++-- dart/test/sentry_envelope_test.dart | 13 ++++++++++--- dart/test/test_utils.dart | 2 +- dart/test/transport/http_transport_test.dart | 2 +- flutter/lib/src/file_system_transport.dart | 2 +- 12 files changed, 43 insertions(+), 41 deletions(-) diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index b8372b782e..93496ae079 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -16,13 +16,13 @@ class SentryEnvelope { [SentryEnvelopeItem.fromEvent(event)]); } - Future> serialize() async { + Future> toEnvelope() async { var data = []; - data.addAll(await header.serialize()); + data.addAll(utf8.encode(jsonEncode(header.toJson()))); final newLineData = utf8.encode('\n'); for (final item in items) { data.addAll(newLineData); - data.addAll(await item.serialize()); + data.addAll(await item.toEnvelopeItem()); } return data; } diff --git a/dart/lib/src/sentry_envelope_header.dart b/dart/lib/src/sentry_envelope_header.dart index 3df36c3a5a..dc5ea0de9e 100644 --- a/dart/lib/src/sentry_envelope_header.dart +++ b/dart/lib/src/sentry_envelope_header.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'protocol/sentry_id.dart'; import 'protocol/sdk_version.dart'; @@ -12,16 +10,16 @@ class SentryEnvelopeHeader { final SentryId? eventId; final SdkVersion? sdkVersion; - Future> serialize() async { - final serializedMap = {}; + Map toJson() { + final json = {}; final tempEventId = eventId; if (tempEventId != null) { - serializedMap['event_id'] = tempEventId.toString(); + json['event_id'] = tempEventId.toString(); } final tempSdkVersion = sdkVersion; if (tempSdkVersion != null) { - serializedMap['sdk'] = tempSdkVersion.toJson(); + json['sdk'] = tempSdkVersion.toJson(); } - return utf8.encode(jsonEncode(serializedMap)); + return json; } } diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index f9d6c4e36b..15d2eeceac 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -26,9 +26,9 @@ class SentryEnvelopeItem { cachedItem.getData); } - Future> serialize() async { + Future> toEnvelopeItem() async { var data = []; - data.addAll(await header.serialize()); + data.addAll(utf8.encode(jsonEncode(await header.toJson()))); data.addAll(utf8.encode('\n')); data.addAll(await dataFactory()); return data; diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart index 0ddc9ec95a..86a99611e7 100644 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'sentry_item_type.dart'; class SentryEnvelopeItemHeader { @@ -12,13 +10,13 @@ class SentryEnvelopeItemHeader { final String? contentType; final String? fileName; - Future> serialize() async { - final serializedMap = {}; + Future> toJson() async { + final json = {}; if (contentType != null) { - serializedMap['content_type'] = contentType!; + json['content_type'] = contentType!; } - serializedMap['type'] = type.toStringValue(); - serializedMap['length'] = await length(); - return utf8.encode(jsonEncode(serializedMap)); + json['type'] = type.toStringValue(); + json['length'] = await length(); + return json; } } diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index 9840816e10..7a8a887449 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -56,7 +56,7 @@ class HttpTransport implements Transport { return null; } - final data = await filteredEnvelope.serialize(); + final data = await filteredEnvelope.toEnvelope(); final body = _bodyEncoder( data, diff --git a/dart/test/sentry_envelope_header_test.dart b/dart/test/sentry_envelope_header_test.dart index e235eb79ec..8863f9c47d 100644 --- a/dart/test/sentry_envelope_header_test.dart +++ b/dart/test/sentry_envelope_header_test.dart @@ -6,22 +6,22 @@ import 'package:test/test.dart'; void main() { group('SentryEnvelopeItemHeader', () { - test('serialize empty', () async { + test('toJson empty', () { final sut = SentryEnvelopeHeader(null, null); - final expected = utf8.encode('{}'); - expect(await sut.serialize(), expected); + final expected = {}; + expect(sut.toJson(), expected); }); - test('serialize', () async { + test('toJson', () async { final eventId = SentryId.newId(); final sdkVersion = SdkVersion( name: 'fixture-sdkName', version: 'fixture-version', ); final sut = SentryEnvelopeHeader(eventId, sdkVersion); - final expextedSkd = jsonEncode(sdkVersion.toJson()); - final expected = utf8.encode('{\"event_id\":\"$eventId\",\"sdk\":$expextedSkd}'); - expect(await sut.serialize(), expected); + final expextedSkd = sdkVersion.toJson(); + final expected = {'event_id': eventId.toString(), 'sdk': expextedSkd}; + expect(sut.toJson(), expected); }); }); } diff --git a/dart/test/sentry_envelope_item_header_test.dart b/dart/test/sentry_envelope_item_header_test.dart index cf66ee4011..e312bc96d4 100644 --- a/dart/test/sentry_envelope_item_header_test.dart +++ b/dart/test/sentry_envelope_item_header_test.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:sentry/src/sentry_envelope_item_header.dart'; import 'package:sentry/src/sentry_item_type.dart'; import 'package:test/test.dart'; @@ -9,9 +7,8 @@ void main() { test('serialize', () async { final sut = SentryEnvelopeItemHeader(SentryItemType.event, () async { return 3; }, contentType: 'application/json'); - final expected = utf8.encode( - '{\"content_type\":\"application/json\",\"type\":\"event\",\"length\":3}'); - expect(await sut.serialize(), expected); + final expected = {'content_type': 'application/json','type': 'event','length': 3}; + expect(await sut.toJson(), expected); }); }); } diff --git a/dart/test/sentry_envelope_item_test.dart b/dart/test/sentry_envelope_item_test.dart index af349aaec5..9fd2215c0f 100644 --- a/dart/test/sentry_envelope_item_test.dart +++ b/dart/test/sentry_envelope_item_test.dart @@ -20,9 +20,11 @@ void main() { final sut = SentryEnvelopeItem(header, dataFactory); + final headerJson = await header.toJson(); + final headerJsonEncoded = jsonEncode(headerJson); final expected = - utf8.encode('${utf8.decode(await header.serialize())}\n{fixture}'); - expect(await sut.serialize(), expected); + utf8.encode('$headerJsonEncoded\n{fixture}'); + expect(await sut.toEnvelopeItem(), expected); }); test('fromEvent', () async { diff --git a/dart/test/sentry_envelope_test.dart b/dart/test/sentry_envelope_test.dart index ceb1911094..ebba1093f9 100644 --- a/dart/test/sentry_envelope_test.dart +++ b/dart/test/sentry_envelope_test.dart @@ -28,9 +28,15 @@ void main() { final header = SentryEnvelopeHeader(eventId, null); final sut = SentryEnvelope(header, [item, item]); + final expectesHeaderJson = header.toJson(); + final expectesHeaderJsonSerialized = jsonEncode(expectesHeaderJson); + + final expectedItem = await item.toEnvelopeItem(); + final expectedItemSerialized = utf8.decode(expectedItem); + final expected = utf8.encode( - '${utf8.decode(await header.serialize())}\n${utf8.decode(await itemHeader.serialize())}\n{fixture}\n${utf8.decode(await itemHeader.serialize())}\n{fixture}'); - final actual = await sut.serialize(); + '$expectesHeaderJsonSerialized\n$expectedItemSerialized\n$expectedItemSerialized'); + final actual = await sut.toEnvelope(); expect(actual, expected); }); @@ -50,7 +56,8 @@ void main() { expect(sut.items[0].header.type, expectedEnvelopeItem.header.type); expect(await sut.items[0].header.length(), await expectedEnvelopeItem.header.length()); - expect(await sut.items[0].serialize(), await expectedEnvelopeItem.serialize()); + expect(await sut.items[0].toEnvelopeItem(), + await expectedEnvelopeItem.toEnvelopeItem()); }); }); } diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index 49a1791795..3d9baad5ed 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -27,7 +27,7 @@ void testHeaders( bool withSecret = true, }) { final expectedHeaders = { - 'Content-Type': 'application/json', + 'Content-Type': 'application/x-sentry-envelope', 'X-Sentry-Auth': 'Sentry sentry_version=7, ' 'sentry_client=$sdkName/$sdkVersion, ' 'sentry_key=public, ' diff --git a/dart/test/transport/http_transport_test.dart b/dart/test/transport/http_transport_test.dart index af413002f0..73a63e3534 100644 --- a/dart/test/transport/http_transport_test.dart +++ b/dart/test/transport/http_transport_test.dart @@ -70,7 +70,7 @@ void main() { final sentryEvent = SentryEvent(); await sut.sendSentryEvent(sentryEvent); - expect(body, await filteredEnvelope.serialize()); + expect(body, await filteredEnvelope.toEnvelope()); }); test('send nothing when filtered event null', () async { diff --git a/flutter/lib/src/file_system_transport.dart b/flutter/lib/src/file_system_transport.dart index c788b0cb79..df45855fd6 100644 --- a/flutter/lib/src/file_system_transport.dart +++ b/flutter/lib/src/file_system_transport.dart @@ -17,7 +17,7 @@ class FileSystemTransport implements Transport { @override Future sendSentryEnvelope(SentryEnvelope envelope) async { - final envelopeData = await envelope.serialize(); + final envelopeData = await envelope.toEnvelope(); final envelopeString = utf8.decode(envelopeData); final args = [envelopeString]; From d24dc78be650f56cbd3818efe782d24f05de0baa Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 10:20:26 +0200 Subject: [PATCH 45/83] Introduce breaking changes section --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fa854a804..52b27ed5b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ * Fix: `Sentry.close()` closes native SDK integrations (#388) * Fix: Mark `Sentry.currentHub` as deprecated (#406) + +## Breaking Changes: + * Feat: Support envelope based transport for events (#391) # 5.0.0 From d93eddc6343a2cb3addab1519ab66f2237d49bcf Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 10:25:30 +0200 Subject: [PATCH 46/83] Add what has changed --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52b27ed5b6..4f8e50b478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ## Breaking Changes: * Feat: Support envelope based transport for events (#391) + * The method signature of `Transport` changed from `Future send(SentryEvent event)` to `Future sendSentryEvent(SentryEvent event)` # 5.0.0 From 0bb9a599e9a98034bc67312aaaf6a6891f038fe2 Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 10:37:27 +0200 Subject: [PATCH 47/83] Return non-null sentryid from transport calls --- dart/lib/src/sentry_client.dart | 4 +--- dart/lib/src/transport/http_transport.dart | 6 +++--- dart/lib/src/transport/noop_transport.dart | 4 ++-- dart/lib/src/transport/transport.dart | 4 ++-- dart/test/mocks/mock_transport.dart | 6 +++--- dart/test/transport/http_transport_test.dart | 2 +- flutter/lib/src/file_system_transport.dart | 6 +++--- flutter/test/mocks.mocks.dart | 12 ++++++++---- 8 files changed, 23 insertions(+), 21 deletions(-) diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 7c855a2b86..552e11ea81 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -106,9 +106,7 @@ class SentryClient { return _sentryId; } } - - final eventId = await _options.transport.sendSentryEvent(preparedEvent); - return eventId ?? SentryId.empty(); + return await _options.transport.sendSentryEvent(preparedEvent); } SentryEvent _prepareEvent(SentryEvent event, {dynamic stackTrace}) { diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index 7a8a887449..c860dd4c07 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -44,16 +44,16 @@ class HttpTransport implements Transport { } @override - Future sendSentryEvent(SentryEvent event) async { + Future sendSentryEvent(SentryEvent event) async { final envelope = SentryEnvelope.fromEvent(event, _options.sdk); return await sendSentryEnvelope(envelope); } @override - Future sendSentryEnvelope(SentryEnvelope envelope) async { + Future sendSentryEnvelope(SentryEnvelope envelope) async { final filteredEnvelope = _rateLimiter.filter(envelope); if (filteredEnvelope == null) { - return null; + return SentryId.empty(); } final data = await filteredEnvelope.toEnvelope(); diff --git a/dart/lib/src/transport/noop_transport.dart b/dart/lib/src/transport/noop_transport.dart index a574d43c14..0e36b14d0d 100644 --- a/dart/lib/src/transport/noop_transport.dart +++ b/dart/lib/src/transport/noop_transport.dart @@ -7,10 +7,10 @@ import 'transport.dart'; class NoOpTransport implements Transport { @override - Future sendSentryEvent(SentryEvent event) => + Future sendSentryEvent(SentryEvent event) => Future.value(SentryId.empty()); @override - Future sendSentryEnvelope(SentryEnvelope envelope) => + Future sendSentryEnvelope(SentryEnvelope envelope) => Future.value(SentryId.empty()); } diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 4bde8149cc..ca1e05cfd9 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -6,6 +6,6 @@ import '../protocol.dart'; /// A transport is in charge of sending the event/envelope either via http /// or caching in the disk. abstract class Transport { - Future sendSentryEvent(SentryEvent event); - Future sendSentryEnvelope(SentryEnvelope envelope); + Future sendSentryEvent(SentryEvent event); + Future sendSentryEnvelope(SentryEnvelope envelope); } diff --git a/dart/test/mocks/mock_transport.dart b/dart/test/mocks/mock_transport.dart index 615947d7c1..cb5d4f43f7 100644 --- a/dart/test/mocks/mock_transport.dart +++ b/dart/test/mocks/mock_transport.dart @@ -9,14 +9,14 @@ class MockTransport implements Transport { } @override - Future sendSentryEvent(SentryEvent event) async { + Future sendSentryEvent(SentryEvent event) async { events.add(event); return event.eventId; } @override - Future sendSentryEnvelope(SentryEnvelope envelope) async { + Future sendSentryEnvelope(SentryEnvelope envelope) async { envelopes.add(envelope); - return envelope.header.eventId; + return envelope.header.eventId ?? SentryId.empty(); } } diff --git a/dart/test/transport/http_transport_test.dart b/dart/test/transport/http_transport_test.dart index 73a63e3534..c9c8e6bea3 100644 --- a/dart/test/transport/http_transport_test.dart +++ b/dart/test/transport/http_transport_test.dart @@ -93,7 +93,7 @@ void main() { final sentryEvent = SentryEvent(); final eventId = await sut.sendSentryEvent(sentryEvent); - expect(eventId, isNull); + expect(eventId, SentryId.empty()); expect(httpCalled, false); }); }); diff --git a/flutter/lib/src/file_system_transport.dart b/flutter/lib/src/file_system_transport.dart index df45855fd6..e1ddac7a7d 100644 --- a/flutter/lib/src/file_system_transport.dart +++ b/flutter/lib/src/file_system_transport.dart @@ -10,13 +10,13 @@ class FileSystemTransport implements Transport { final SentryOptions _options; @override - Future sendSentryEvent(SentryEvent event) async { + Future sendSentryEvent(SentryEvent event) async { final envelope = SentryEnvelope.fromEvent(event, _options.sdk); return await sendSentryEnvelope(envelope); } @override - Future sendSentryEnvelope(SentryEnvelope envelope) async { + Future sendSentryEnvelope(SentryEnvelope envelope) async { final envelopeData = await envelope.toEnvelope(); final envelopeString = utf8.decode(envelopeData); @@ -31,6 +31,6 @@ class FileSystemTransport implements Transport { return SentryId.empty(); } - return envelope.header.eventId; + return envelope.header.eventId ?? SentryId.empty(); } } diff --git a/flutter/test/mocks.mocks.dart b/flutter/test/mocks.mocks.dart index b059ac13ab..445b4056ca 100644 --- a/flutter/test/mocks.mocks.dart +++ b/flutter/test/mocks.mocks.dart @@ -82,6 +82,10 @@ class MockHub extends _i1.Mock implements _i3.Hub { _i3.Hub clone() => (super.noSuchMethod(Invocation.method(#clone, []), returnValue: _FakeHub()) as _i3.Hub); @override + _i4.Future close() => (super.noSuchMethod(Invocation.method(#close, []), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i4.Future); + @override void configureScope(_i3.ScopeCallback? callback) => super.noSuchMethod(Invocation.method(#configureScope, [callback]), returnValueForMissingStub: null); @@ -96,13 +100,13 @@ class MockTransport extends _i1.Mock implements _i9.Transport { } @override - _i4.Future<_i2.SentryId?> sendSentryEvent(_i5.SentryEvent? event) => + _i4.Future<_i2.SentryId> sendSentryEvent(_i5.SentryEvent? event) => (super.noSuchMethod(Invocation.method(#sendSentryEvent, [event]), returnValue: Future.value(_FakeSentryId())) - as _i4.Future<_i2.SentryId?>); + as _i4.Future<_i2.SentryId>); @override - _i4.Future<_i2.SentryId?> sendSentryEnvelope(_i10.SentryEnvelope? envelope) => + _i4.Future<_i2.SentryId> sendSentryEnvelope(_i10.SentryEnvelope? envelope) => (super.noSuchMethod(Invocation.method(#sendSentryEnvelope, [envelope]), returnValue: Future.value(_FakeSentryId())) - as _i4.Future<_i2.SentryId?>); + as _i4.Future<_i2.SentryId>); } From 7697bef03c6dd904c74cd89b015a6d38fac892c9 Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 10:41:08 +0200 Subject: [PATCH 48/83] Use trailing comma --- dart/lib/src/transport/rate_limiter.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index 8ce61bb087..0f86d13312 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -60,9 +60,10 @@ class RateLimiter { for (final rateLimit in rateLimits) { _applyRetryAfterOnlyIfLonger( - rateLimit.category, - DateTime.fromMillisecondsSinceEpoch( - currentDateTime + rateLimit.durationInMillis)); + rateLimit.category, + DateTime.fromMillisecondsSinceEpoch( + currentDateTime + rateLimit.durationInMillis), + ); } } From 0cd0455c5e521cb99bb75648b095c29bc910d9c9 Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 10:41:34 +0200 Subject: [PATCH 49/83] Run format --- dart/test/sentry_envelope_header_test.dart | 7 ++++--- dart/test/sentry_envelope_item_header_test.dart | 11 ++++++++--- dart/test/sentry_envelope_item_test.dart | 3 +-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/dart/test/sentry_envelope_header_test.dart b/dart/test/sentry_envelope_header_test.dart index 8863f9c47d..ae854489af 100644 --- a/dart/test/sentry_envelope_header_test.dart +++ b/dart/test/sentry_envelope_header_test.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:sentry/sentry.dart'; import 'package:sentry/src/sentry_envelope_header.dart'; import 'package:test/test.dart'; @@ -20,7 +18,10 @@ void main() { ); final sut = SentryEnvelopeHeader(eventId, sdkVersion); final expextedSkd = sdkVersion.toJson(); - final expected = {'event_id': eventId.toString(), 'sdk': expextedSkd}; + final expected = { + 'event_id': eventId.toString(), + 'sdk': expextedSkd + }; expect(sut.toJson(), expected); }); }); diff --git a/dart/test/sentry_envelope_item_header_test.dart b/dart/test/sentry_envelope_item_header_test.dart index e312bc96d4..8aca9316a3 100644 --- a/dart/test/sentry_envelope_item_header_test.dart +++ b/dart/test/sentry_envelope_item_header_test.dart @@ -5,9 +5,14 @@ import 'package:test/test.dart'; void main() { group('SentryEnvelopeItemHeader', () { test('serialize', () async { - final sut = SentryEnvelopeItemHeader(SentryItemType.event, () async { return 3; }, - contentType: 'application/json'); - final expected = {'content_type': 'application/json','type': 'event','length': 3}; + final sut = SentryEnvelopeItemHeader(SentryItemType.event, () async { + return 3; + }, contentType: 'application/json'); + final expected = { + 'content_type': 'application/json', + 'type': 'event', + 'length': 3 + }; expect(await sut.toJson(), expected); }); }); diff --git a/dart/test/sentry_envelope_item_test.dart b/dart/test/sentry_envelope_item_test.dart index 9fd2215c0f..628901a61b 100644 --- a/dart/test/sentry_envelope_item_test.dart +++ b/dart/test/sentry_envelope_item_test.dart @@ -22,8 +22,7 @@ void main() { final headerJson = await header.toJson(); final headerJsonEncoded = jsonEncode(headerJson); - final expected = - utf8.encode('$headerJsonEncoded\n{fixture}'); + final expected = utf8.encode('$headerJsonEncoded\n{fixture}'); expect(await sut.toEnvelopeItem(), expected); }); From ef3fb62df038a48f09aa143c1f670da3a5304b51 Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 11:15:30 +0200 Subject: [PATCH 50/83] Add documentation --- dart/lib/src/sentry_envelope.dart | 6 ++++++ dart/lib/src/sentry_envelope_header.dart | 5 +++++ dart/lib/src/sentry_envelope_item.dart | 20 ++++++++++++------- dart/lib/src/sentry_envelope_item_header.dart | 6 ++++++ dart/lib/src/transport/rate_limit.dart | 1 + .../src/transport/rate_limit_category.dart | 1 + dart/lib/src/transport/rate_limit_parser.dart | 9 +++++---- dart/lib/src/transport/rate_limiter.dart | 2 ++ 8 files changed, 39 insertions(+), 11 deletions(-) diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index 93496ae079..0504e2e3d6 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -5,17 +5,23 @@ import 'sentry_envelope_item.dart'; import 'protocol/sentry_event.dart'; import 'protocol/sdk_version.dart'; +/// Class representation of `Envelope` file. class SentryEnvelope { SentryEnvelope(this.header, this.items); + /// Header descriping envelope content. final SentryEnvelopeHeader header; + + /// All items contained in the envelope. final List items; + /// Create an `SentryEnvelope` with containing one `SentryEnvelopeItem` which holds the `SentyEvent` data. factory SentryEnvelope.fromEvent(SentryEvent event, SdkVersion sdkVersion) { return SentryEnvelope(SentryEnvelopeHeader(event.eventId, sdkVersion), [SentryEnvelopeItem.fromEvent(event)]); } + /// Create binary data representation of `Envelope` file encoded in utf8. Future> toEnvelope() async { var data = []; data.addAll(utf8.encode(jsonEncode(header.toJson()))); diff --git a/dart/lib/src/sentry_envelope_header.dart b/dart/lib/src/sentry_envelope_header.dart index dc5ea0de9e..7dde9d8f9d 100644 --- a/dart/lib/src/sentry_envelope_header.dart +++ b/dart/lib/src/sentry_envelope_header.dart @@ -1,15 +1,20 @@ import 'protocol/sentry_id.dart'; import 'protocol/sdk_version.dart'; +/// Header containing `SentryId` and `SdkVersion`. class SentryEnvelopeHeader { SentryEnvelopeHeader(this.eventId, this.sdkVersion); SentryEnvelopeHeader.newEventId() : eventId = SentryId.newId(), sdkVersion = null; + /// The identifier of encoded `SentryEvent`. final SentryId? eventId; + + /// The `SdkVersion` with which the envelope was send. final SdkVersion? sdkVersion; + /// Header encoded as JSON Map toJson() { final json = {}; final tempEventId = eventId; diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index 15d2eeceac..cf0efdb73b 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -4,14 +4,19 @@ import 'sentry_item_type.dart'; import 'protocol/sentry_event.dart'; import 'sentry_envelope_item_header.dart'; +/// Item holding header information and JSON encoded data. class SentryEnvelopeItem { SentryEnvelopeItem(this.header, this.dataFactory); + /// Header with info about type and length of data in bytes. final SentryEnvelopeItemHeader header; + + /// Create binary data representation of item JSON encoded in utf8. final Future> Function() dataFactory; + /// Create an `SentryEnvelopeItem` which holds the `SentyEvent` data. factory SentryEnvelopeItem.fromEvent(SentryEvent event) { - final cachedItem = CachedItem(() async { + final cachedItem = _CachedItem(() async { final jsonEncoded = jsonEncode(event.toJson()); return utf8.encode(jsonEncoded); }); @@ -26,6 +31,7 @@ class SentryEnvelopeItem { cachedItem.getData); } + /// Create binary data representation of `Envelope` item encoded in utf8. Future> toEnvelopeItem() async { var data = []; data.addAll(utf8.encode(jsonEncode(await header.toJson()))); @@ -35,14 +41,14 @@ class SentryEnvelopeItem { } } -class CachedItem { - CachedItem(this.dataFactory); +class _CachedItem { + _CachedItem(this._dataFactory); - List? data; - Future> Function() dataFactory; + List? _data; + Future> Function() _dataFactory; Future> getData() async { - data ??= await dataFactory(); - return data!; + _data ??= await _dataFactory(); + return _data!; } } diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart index 86a99611e7..889b0692a0 100644 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -1,15 +1,21 @@ import 'sentry_item_type.dart'; +/// Header with item info about type and length of data in bytes. class SentryEnvelopeItemHeader { SentryEnvelopeItemHeader(this.type, this.length, {this.contentType, this.fileName}); + /// Type of encoded data. final SentryItemType type; + + /// The number of bytes of the encoded item JSON. final Future Function() length; final String? contentType; + final String? fileName; + /// Item header encoded as JSON Future> toJson() async { final json = {}; if (contentType != null) { diff --git a/dart/lib/src/transport/rate_limit.dart b/dart/lib/src/transport/rate_limit.dart index 7db464147a..e63d4a74ef 100644 --- a/dart/lib/src/transport/rate_limit.dart +++ b/dart/lib/src/transport/rate_limit.dart @@ -1,5 +1,6 @@ import 'rate_limit_category.dart'; +/// `RateLimit` containing limited `RateLimitCategory` and duration in milliseconds. class RateLimit { RateLimit(this.category, this.durationInMillis); diff --git a/dart/lib/src/transport/rate_limit_category.dart b/dart/lib/src/transport/rate_limit_category.dart index 01f465dbb3..cb0040a0e6 100644 --- a/dart/lib/src/transport/rate_limit_category.dart +++ b/dart/lib/src/transport/rate_limit_category.dart @@ -1,3 +1,4 @@ +/// Different category types of data sent to Sentry. Used for rate limiting. enum RateLimitCategory { all, rate_limit_default, // default diff --git a/dart/lib/src/transport/rate_limit_parser.dart b/dart/lib/src/transport/rate_limit_parser.dart index db300e168a..4f6ed5903a 100644 --- a/dart/lib/src/transport/rate_limit_parser.dart +++ b/dart/lib/src/transport/rate_limit_parser.dart @@ -1,15 +1,16 @@ import 'rate_limit_category.dart'; import 'rate_limit.dart'; +/// Parse rate limit categories and times from response header payloads. class RateLimitParser { - RateLimitParser(this.header); + RateLimitParser(this._header); static const httpRetryAfterDefaultDelayMillis = 60000; - String? header; + final String? _header; List parseRateLimitHeader() { - final rateLimitHeader = header; + final rateLimitHeader = _header; if (rateLimitHeader == null) { return []; } @@ -46,7 +47,7 @@ class RateLimitParser { List parseRetryAfterHeader() { return [ - RateLimit(RateLimitCategory.all, _parseRetryAfterOrDefault(header)) + RateLimit(RateLimitCategory.all, _parseRetryAfterOrDefault(_header)) ]; } diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index 0f86d13312..7cfb17cef7 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -14,6 +14,7 @@ class RateLimiter { final ClockProvider _clockProvider; final _rateLimitedUntil = {}; + /// Filter out envelopes that are rate limited. SentryEnvelope? filter(SentryEnvelope envelope) { // Optimize for/No allocations if no items are under 429 List? dropItems; @@ -45,6 +46,7 @@ class RateLimiter { } } + /// Update rate limited categories void updateRetryAfterLimits( String? sentryRateLimitHeader, String? retryAfterHeader, int errorCode) { final currentDateTime = _clockProvider().millisecondsSinceEpoch; From 5fe2f13319507f93650639f503152d55305f16ed Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 11:26:50 +0200 Subject: [PATCH 51/83] Make prop final --- dart/lib/src/sentry_envelope_item.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index cf0efdb73b..d7173a5fbd 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -44,9 +44,9 @@ class SentryEnvelopeItem { class _CachedItem { _CachedItem(this._dataFactory); + final Future> Function() _dataFactory; List? _data; - Future> Function() _dataFactory; - + Future> getData() async { _data ??= await _dataFactory(); return _data!; From c8ede690fd52cbdc2cb9c67352329aca362d750c Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 11:30:28 +0200 Subject: [PATCH 52/83] Add self-hosted info --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f8e50b478..13afaec7ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ * Feat: Support envelope based transport for events (#391) * The method signature of `Transport` changed from `Future send(SentryEvent event)` to `Future sendSentryEvent(SentryEvent event)` +## Sentry Self Hosted Compatibility + +* Since version `Unreleased` of the `sentry_dart`, [Sentry's version >= v20.6.0](https://github.com/getsentry/onpremise/releases) is required. This only applies to on-premise Sentry, if you are using sentry.io no action is needed. + # 5.0.0 * Sound null safety From e3c28d8fffcfa853fba697b06afd5f28afaff706 Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 11:31:58 +0200 Subject: [PATCH 53/83] Use correct package name --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13afaec7ea..98c35ff627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ## Sentry Self Hosted Compatibility -* Since version `Unreleased` of the `sentry_dart`, [Sentry's version >= v20.6.0](https://github.com/getsentry/onpremise/releases) is required. This only applies to on-premise Sentry, if you are using sentry.io no action is needed. +* Since version `Unreleased` of the `sentry` package, [Sentry's version >= v20.6.0](https://github.com/getsentry/onpremise/releases) is required. This only applies to on-premise Sentry, if you are using sentry.io no action is needed. # 5.0.0 From 27611aaa683a295d363d93d4d20377cca3152402 Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 15:50:35 +0200 Subject: [PATCH 54/83] Use `StreamedRequest` to send data --- dart/lib/src/sentry_envelope.dart | 15 +++--- dart/lib/src/sentry_envelope_header.dart | 2 +- dart/lib/src/transport/http_transport.dart | 49 ++++++++++---------- dart/test/sentry_envelope_test.dart | 6 ++- dart/test/transport/http_transport_test.dart | 5 +- flutter/lib/src/file_system_transport.dart | 4 +- 6 files changed, 44 insertions(+), 37 deletions(-) diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index 0504e2e3d6..e2268fd2df 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'package:http/http.dart'; + import 'sentry_envelope_header.dart'; import 'sentry_envelope_item.dart'; import 'protocol/sentry_event.dart'; @@ -21,15 +23,14 @@ class SentryEnvelope { [SentryEnvelopeItem.fromEvent(event)]); } - /// Create binary data representation of `Envelope` file encoded in utf8. - Future> toEnvelope() async { - var data = []; - data.addAll(utf8.encode(jsonEncode(header.toJson()))); + /// Stream binary data representation of `Envelope` file encoded in utf8. + Stream> envelopeStream() async* { + yield utf8.encode(jsonEncode(header.toJson())); + final newLineData = utf8.encode('\n'); for (final item in items) { - data.addAll(newLineData); - data.addAll(await item.toEnvelopeItem()); + yield newLineData; + yield await item.toEnvelopeItem(); } - return data; } } diff --git a/dart/lib/src/sentry_envelope_header.dart b/dart/lib/src/sentry_envelope_header.dart index 7dde9d8f9d..426bccc910 100644 --- a/dart/lib/src/sentry_envelope_header.dart +++ b/dart/lib/src/sentry_envelope_header.dart @@ -8,7 +8,7 @@ class SentryEnvelopeHeader { : eventId = SentryId.newId(), sdkVersion = null; - /// The identifier of encoded `SentryEvent`. + /// The identifier of encoded `SentryEvent`. final SentryId? eventId; /// The `SdkVersion` with which the envelope was send. diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index c860dd4c07..d2263be95e 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -56,20 +56,11 @@ class HttpTransport implements Transport { return SentryId.empty(); } - final data = await filteredEnvelope.toEnvelope(); - - final body = _bodyEncoder( - data, - _headers, - compressPayload: _options.compressPayload, - ); - - final response = await _options.httpClient.post( - _dsn.postUri, - headers: _credentialBuilder.configure(_headers), - body: body, - ); - + final streamedRequest = await _createStreamedRequest(filteredEnvelope); + final response = await _options.httpClient + .send(streamedRequest) + .then(Response.fromStream); + _updateRetryAfterLimits(response); if (response.statusCode != 200) { @@ -94,18 +85,26 @@ class HttpTransport implements Transport { return eventId != null ? SentryId.fromId(eventId) : SentryId.empty(); } - List _bodyEncoder( - List data, - Map headers, { - required bool compressPayload, - }) { - // [SentryIOClient] implement gzip compression - // gzip compression is not available on browser - var body = data; - if (compressPayload) { - body = compressBody(data, headers); + Future _createStreamedRequest( + SentryEnvelope envelope) async { + final streamedRequest = StreamedRequest('POST', _dsn.postUri); + + if (_options.compressPayload) { + final envelopeData = []; + await envelope.envelopeStream().forEach(envelopeData.addAll); + + final compressedBody = compressBody(envelopeData, _headers); + streamedRequest.sink.add(compressedBody); + streamedRequest.sink.close(); + } else { + envelope + .envelopeStream() + .listen(streamedRequest.sink.add) + .onDone(streamedRequest.sink.close); } - return body; + streamedRequest.headers.addAll(_credentialBuilder.configure(_headers)); + + return streamedRequest; } void _updateRetryAfterLimits(Response response) { diff --git a/dart/test/sentry_envelope_test.dart b/dart/test/sentry_envelope_test.dart index ebba1093f9..f9f9eef338 100644 --- a/dart/test/sentry_envelope_test.dart +++ b/dart/test/sentry_envelope_test.dart @@ -36,8 +36,10 @@ void main() { final expected = utf8.encode( '$expectesHeaderJsonSerialized\n$expectedItemSerialized\n$expectedItemSerialized'); - final actual = await sut.toEnvelope(); - expect(actual, expected); + + final envelopeData = []; + await sut.envelopeStream().forEach(envelopeData.addAll); + expect(envelopeData, expected); }); test('fromEvent', () async { diff --git a/dart/test/transport/http_transport_test.dart b/dart/test/transport/http_transport_test.dart index c9c8e6bea3..bc3867bba0 100644 --- a/dart/test/transport/http_transport_test.dart +++ b/dart/test/transport/http_transport_test.dart @@ -70,7 +70,10 @@ void main() { final sentryEvent = SentryEvent(); await sut.sendSentryEvent(sentryEvent); - expect(body, await filteredEnvelope.toEnvelope()); + final envelopeData = []; + await filteredEnvelope.envelopeStream().forEach(envelopeData.addAll); + + expect(body, envelopeData); }); test('send nothing when filtered event null', () async { diff --git a/flutter/lib/src/file_system_transport.dart b/flutter/lib/src/file_system_transport.dart index e1ddac7a7d..bca4d85f0e 100644 --- a/flutter/lib/src/file_system_transport.dart +++ b/flutter/lib/src/file_system_transport.dart @@ -17,7 +17,9 @@ class FileSystemTransport implements Transport { @override Future sendSentryEnvelope(SentryEnvelope envelope) async { - final envelopeData = await envelope.toEnvelope(); + final envelopeData = []; + await envelope.envelopeStream().forEach(envelopeData.addAll); + final envelopeString = utf8.decode(envelopeData); final args = [envelopeString]; From 04d444e76035e343425dcc66f2cb70c94450d6ab Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 15:57:58 +0200 Subject: [PATCH 55/83] Change toEnvelopeItem to stream --- dart/lib/src/sentry_envelope.dart | 5 +++-- dart/lib/src/sentry_envelope_item.dart | 12 +++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index e2268fd2df..c2757d01c0 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -26,11 +26,12 @@ class SentryEnvelope { /// Stream binary data representation of `Envelope` file encoded in utf8. Stream> envelopeStream() async* { yield utf8.encode(jsonEncode(header.toJson())); - final newLineData = utf8.encode('\n'); for (final item in items) { yield newLineData; - yield await item.toEnvelopeItem(); + await for (final chunk in item.envelopeStream()) { + yield chunk; + } } } } diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index d7173a5fbd..164f647e54 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -31,13 +31,11 @@ class SentryEnvelopeItem { cachedItem.getData); } - /// Create binary data representation of `Envelope` item encoded in utf8. - Future> toEnvelopeItem() async { - var data = []; - data.addAll(utf8.encode(jsonEncode(await header.toJson()))); - data.addAll(utf8.encode('\n')); - data.addAll(await dataFactory()); - return data; + /// Stream binary data of `Envelope` item encoded in utf8. + Stream> envelopeStream() async* { + yield utf8.encode(jsonEncode(await header.toJson())); + yield utf8.encode('\n'); + yield await dataFactory(); } } From 9dcfb5ccf32fd2fc1de95099f659b2c005435bfc Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 16:11:21 +0200 Subject: [PATCH 56/83] Change envelope item to stream --- dart/lib/src/sentry_envelope.dart | 2 +- dart/lib/src/sentry_envelope_item.dart | 6 +++--- dart/lib/src/transport/http_transport.dart | 2 +- dart/lib/src/transport/rate_limit.dart | 2 +- dart/test/sentry_envelope_item_test.dart | 6 +++++- dart/test/sentry_envelope_test.dart | 17 +++++++++++++---- flutter/lib/src/file_system_transport.dart | 2 +- 7 files changed, 25 insertions(+), 12 deletions(-) diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index c2757d01c0..963509e93b 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -29,7 +29,7 @@ class SentryEnvelope { final newLineData = utf8.encode('\n'); for (final item in items) { yield newLineData; - await for (final chunk in item.envelopeStream()) { + await for (final chunk in item.envelopeItemStream()) { yield chunk; } } diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index 164f647e54..24930be25f 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -4,7 +4,7 @@ import 'sentry_item_type.dart'; import 'protocol/sentry_event.dart'; import 'sentry_envelope_item_header.dart'; -/// Item holding header information and JSON encoded data. +/// Item holding header information and JSON encoded data. class SentryEnvelopeItem { SentryEnvelopeItem(this.header, this.dataFactory); @@ -32,7 +32,7 @@ class SentryEnvelopeItem { } /// Stream binary data of `Envelope` item encoded in utf8. - Stream> envelopeStream() async* { + Stream> envelopeItemStream() async* { yield utf8.encode(jsonEncode(await header.toJson())); yield utf8.encode('\n'); yield await dataFactory(); @@ -44,7 +44,7 @@ class _CachedItem { final Future> Function() _dataFactory; List? _data; - + Future> getData() async { _data ??= await _dataFactory(); return _data!; diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index d2263be95e..88eb2b370b 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -60,7 +60,7 @@ class HttpTransport implements Transport { final response = await _options.httpClient .send(streamedRequest) .then(Response.fromStream); - + _updateRetryAfterLimits(response); if (response.statusCode != 200) { diff --git a/dart/lib/src/transport/rate_limit.dart b/dart/lib/src/transport/rate_limit.dart index e63d4a74ef..f461e33a56 100644 --- a/dart/lib/src/transport/rate_limit.dart +++ b/dart/lib/src/transport/rate_limit.dart @@ -1,6 +1,6 @@ import 'rate_limit_category.dart'; -/// `RateLimit` containing limited `RateLimitCategory` and duration in milliseconds. +/// `RateLimit` containing limited `RateLimitCategory` and duration in milliseconds. class RateLimit { RateLimit(this.category, this.durationInMillis); diff --git a/dart/test/sentry_envelope_item_test.dart b/dart/test/sentry_envelope_item_test.dart index 628901a61b..3e000cf953 100644 --- a/dart/test/sentry_envelope_item_test.dart +++ b/dart/test/sentry_envelope_item_test.dart @@ -23,7 +23,11 @@ void main() { final headerJson = await header.toJson(); final headerJsonEncoded = jsonEncode(headerJson); final expected = utf8.encode('$headerJsonEncoded\n{fixture}'); - expect(await sut.toEnvelopeItem(), expected); + + final actualItem = []; + await sut.envelopeItemStream().forEach(actualItem.addAll); + + expect(actualItem, expected); }); test('fromEvent', () async { diff --git a/dart/test/sentry_envelope_test.dart b/dart/test/sentry_envelope_test.dart index f9f9eef338..d23560b302 100644 --- a/dart/test/sentry_envelope_test.dart +++ b/dart/test/sentry_envelope_test.dart @@ -31,12 +31,13 @@ void main() { final expectesHeaderJson = header.toJson(); final expectesHeaderJsonSerialized = jsonEncode(expectesHeaderJson); - final expectedItem = await item.toEnvelopeItem(); + final expectedItem = []; + await item.envelopeItemStream().forEach(expectedItem.addAll); final expectedItemSerialized = utf8.decode(expectedItem); final expected = utf8.encode( '$expectesHeaderJsonSerialized\n$expectedItemSerialized\n$expectedItemSerialized'); - + final envelopeData = []; await sut.envelopeStream().forEach(envelopeData.addAll); expect(envelopeData, expected); @@ -58,8 +59,16 @@ void main() { expect(sut.items[0].header.type, expectedEnvelopeItem.header.type); expect(await sut.items[0].header.length(), await expectedEnvelopeItem.header.length()); - expect(await sut.items[0].toEnvelopeItem(), - await expectedEnvelopeItem.toEnvelopeItem()); + + final actualItem = []; + await sut.items[0].envelopeItemStream().forEach(actualItem.addAll); + + final expectedItem = []; + await expectedEnvelopeItem + .envelopeItemStream() + .forEach(expectedItem.addAll); + + expect(actualItem, expectedItem); }); }); } diff --git a/flutter/lib/src/file_system_transport.dart b/flutter/lib/src/file_system_transport.dart index bca4d85f0e..eb718d93f5 100644 --- a/flutter/lib/src/file_system_transport.dart +++ b/flutter/lib/src/file_system_transport.dart @@ -19,7 +19,7 @@ class FileSystemTransport implements Transport { Future sendSentryEnvelope(SentryEnvelope envelope) async { final envelopeData = []; await envelope.envelopeStream().forEach(envelopeData.addAll); - + final envelopeString = utf8.decode(envelopeData); final args = [envelopeString]; From 2b470685d317ad5c8525e59c5670f9965587f215 Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 16:17:25 +0200 Subject: [PATCH 57/83] Use temp variable to enable comipler non-null check --- dart/lib/src/sentry_envelope_item_header.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart index 889b0692a0..9c07a7eac4 100644 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -18,8 +18,9 @@ class SentryEnvelopeItemHeader { /// Item header encoded as JSON Future> toJson() async { final json = {}; - if (contentType != null) { - json['content_type'] = contentType!; + final tempContentType = contentType; + if (tempContentType != null) { + json['content_type'] = tempContentType; } json['type'] = type.toStringValue(); json['length'] = await length(); From 94c2099630df3925289bc439652e9cceda926272 Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 16:19:15 +0200 Subject: [PATCH 58/83] Add doc for captureEnvelope --- dart/lib/src/sentry_client.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 552e11ea81..6e865c4ab7 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -197,6 +197,7 @@ class SentryClient { return captureEvent(event, scope: scope, hint: hint); } + /// Reports the [envelope] to Sentry.io. Future captureEnvelope(SentryEnvelope envelope) { return _options.transport.sendSentryEnvelope(envelope); } From 39e26e309d13e0b273a8586d054edaa606b59187 Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 16:24:27 +0200 Subject: [PATCH 59/83] REmove commented out import --- flutter/lib/src/sentry_flutter.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index f80263aeb2..2845f73720 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -6,9 +6,9 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:sentry/sentry.dart'; import 'sentry_flutter_options.dart'; -import 'file_system_transport.dart'; import 'default_integrations.dart'; -//import 'file_system_transport.dart'; +import 'file_system_transport.dart'; + import 'version.dart'; // conditional import for the iOSPlatformChecker // in browser, the iOSPlatformChecker will always return false From cc5862c6e753929ff0de44685b92f5c55be677e7 Mon Sep 17 00:00:00 2001 From: denrase Date: Thu, 8 Apr 2021 16:27:21 +0200 Subject: [PATCH 60/83] Remove unused import --- dart/lib/src/sentry_envelope.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index 963509e93b..bf775df479 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -1,7 +1,5 @@ import 'dart:convert'; -import 'package:http/http.dart'; - import 'sentry_envelope_header.dart'; import 'sentry_envelope_item.dart'; import 'protocol/sentry_event.dart'; From 890a50d1666e0e906b0ea717e0004bbb60067da0 Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 13 Apr 2021 08:57:23 +0200 Subject: [PATCH 61/83] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98c35ff627..310b902067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ## Sentry Self Hosted Compatibility -* Since version `Unreleased` of the `sentry` package, [Sentry's version >= v20.6.0](https://github.com/getsentry/onpremise/releases) is required. This only applies to on-premise Sentry, if you are using sentry.io no action is needed. +* This version of the `sentry` Dart package requires [Sentry server >= v20.6.0](https://github.com/getsentry/onpremise/releases). This only applies to on-premise Sentry, if you are using sentry.io no action is needed. # 5.0.0 From 1df4c678f1ff26ba3c0ae377cd32fb3e01b4d4d3 Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 13 Apr 2021 10:57:41 +0200 Subject: [PATCH 62/83] Use chunked conversion for gzip envelopes --- dart/lib/src/transport/http_transport.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index 88eb2b370b..73fed58210 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'package:http/http.dart'; @@ -90,12 +91,13 @@ class HttpTransport implements Transport { final streamedRequest = StreamedRequest('POST', _dsn.postUri); if (_options.compressPayload) { - final envelopeData = []; - await envelope.envelopeStream().forEach(envelopeData.addAll); - - final compressedBody = compressBody(envelopeData, _headers); - streamedRequest.sink.add(compressedBody); - streamedRequest.sink.close(); + _headers['Content-Encoding'] = 'gzip'; + final byteConversionSink = GZipCodec().encoder + .startChunkedConversion(streamedRequest.sink); + envelope + .envelopeStream() + .listen(byteConversionSink.add) + .onDone(byteConversionSink.close); } else { envelope .envelopeStream() From 67a41c4def2749e19f34a5ad742b9ead6d5e9082 Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 13 Apr 2021 11:01:02 +0200 Subject: [PATCH 63/83] Remove unused import --- dart/lib/src/transport/http_transport.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index 73fed58210..7c2c7510a9 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -10,7 +10,6 @@ import '../sentry_options.dart'; import '../utils.dart'; import '../sentry_envelope.dart'; -import 'noop_encode.dart' if (dart.library.io) 'encode.dart'; import 'transport.dart'; import 'rate_limiter.dart'; From 0600bd78810109d3c091b7fff58d67f3bde9e349 Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 13 Apr 2021 11:09:36 +0200 Subject: [PATCH 64/83] Use Duration tyoe instead of int --- dart/lib/src/transport/rate_limit.dart | 4 +-- dart/lib/src/transport/rate_limit_parser.dart | 14 ++++---- dart/lib/src/transport/rate_limiter.dart | 2 +- .../test/protocol/rate_limit_parser_test.dart | 32 +++++++++---------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/dart/lib/src/transport/rate_limit.dart b/dart/lib/src/transport/rate_limit.dart index f461e33a56..e95285f716 100644 --- a/dart/lib/src/transport/rate_limit.dart +++ b/dart/lib/src/transport/rate_limit.dart @@ -2,8 +2,8 @@ import 'rate_limit_category.dart'; /// `RateLimit` containing limited `RateLimitCategory` and duration in milliseconds. class RateLimit { - RateLimit(this.category, this.durationInMillis); + RateLimit(this.category, this.duration); final RateLimitCategory category; - final int durationInMillis; + final Duration duration; } diff --git a/dart/lib/src/transport/rate_limit_parser.dart b/dart/lib/src/transport/rate_limit_parser.dart index 4f6ed5903a..a1619ef40d 100644 --- a/dart/lib/src/transport/rate_limit_parser.dart +++ b/dart/lib/src/transport/rate_limit_parser.dart @@ -5,7 +5,7 @@ import 'rate_limit.dart'; class RateLimitParser { RateLimitParser(this._header); - static const httpRetryAfterDefaultDelayMillis = 60000; + static const httpRetryAfterDefaultDelay = Duration(milliseconds: 60000); final String? _header; @@ -22,7 +22,7 @@ class RateLimitParser { final durationAndCategories = rateLimitValue.trim().split(':'); if (durationAndCategories.isNotEmpty) { - final durationInMillis = + final duration = _parseRetryAfterOrDefault(durationAndCategories[0]); if (durationAndCategories.length > 1) { @@ -33,11 +33,11 @@ class RateLimitParser { final category = RateLimitCategoryExtension.fromStringValue(categoryValue); if (category != RateLimitCategory.unknown) { - rateLimits.add(RateLimit(category, durationInMillis)); + rateLimits.add(RateLimit(category, duration)); } }); } else { - rateLimits.add(RateLimit(RateLimitCategory.all, durationInMillis)); + rateLimits.add(RateLimit(RateLimitCategory.all, duration)); } } } @@ -53,12 +53,12 @@ class RateLimitParser { // Helper - static int _parseRetryAfterOrDefault(String? value) { + static Duration _parseRetryAfterOrDefault(String? value) { final durationInSeconds = int.tryParse(value ?? ''); if (durationInSeconds != null) { - return durationInSeconds * 1000; + return Duration(seconds: durationInSeconds); } else { - return RateLimitParser.httpRetryAfterDefaultDelayMillis; + return RateLimitParser.httpRetryAfterDefaultDelay; } } } diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index 7cfb17cef7..5f41c34946 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -64,7 +64,7 @@ class RateLimiter { _applyRetryAfterOnlyIfLonger( rateLimit.category, DateTime.fromMillisecondsSinceEpoch( - currentDateTime + rateLimit.durationInMillis), + currentDateTime + rateLimit.duration.inMilliseconds), ); } } diff --git a/dart/test/protocol/rate_limit_parser_test.dart b/dart/test/protocol/rate_limit_parser_test.dart index d39e9e2478..a8d5905c0e 100644 --- a/dart/test/protocol/rate_limit_parser_test.dart +++ b/dart/test/protocol/rate_limit_parser_test.dart @@ -9,7 +9,7 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, 50000); + expect(sut[0].duration.inMilliseconds, 50000); }); test('single rate limit with multiple categories', () { @@ -18,9 +18,9 @@ void main() { expect(sut.length, 2); expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, 50000); + expect(sut[0].duration.inMilliseconds, 50000); expect(sut[1].category, RateLimitCategory.session); - expect(sut[1].durationInMillis, 50000); + expect(sut[1].duration.inMilliseconds, 50000); }); test('don`t apply rate limit for unknown categories ', () { @@ -34,7 +34,7 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.all); - expect(sut[0].durationInMillis, 50000); + expect(sut[0].duration.inMilliseconds, 50000); }); test('multiple rate limits', () { @@ -43,9 +43,9 @@ void main() { expect(sut.length, 2); expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, 50000); + expect(sut[0].duration.inMilliseconds, 50000); expect(sut[1].category, RateLimitCategory.session); - expect(sut[1].durationInMillis, 70000); + expect(sut[1].duration.inMilliseconds, 70000); }); test('multiple rate limits with same category', () { @@ -54,9 +54,9 @@ void main() { expect(sut.length, 2); expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, 50000); + expect(sut[0].duration.inMilliseconds, 50000); expect(sut[1].category, RateLimitCategory.transaction); - expect(sut[1].durationInMillis, 70000); + expect(sut[1].duration.inMilliseconds, 70000); }); test('ignore case', () { @@ -64,7 +64,7 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, 50000); + expect(sut[0].duration.inMilliseconds, 50000); }); test('un-parseable returns default duration', () { @@ -72,8 +72,8 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.transaction); - expect(sut[0].durationInMillis, - RateLimitParser.httpRetryAfterDefaultDelayMillis); + expect(sut[0].duration.inMilliseconds, + RateLimitParser.httpRetryAfterDefaultDelay.inMilliseconds); }); }); @@ -83,8 +83,8 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.all); - expect(sut[0].durationInMillis, - RateLimitParser.httpRetryAfterDefaultDelayMillis); + expect(sut[0].duration.inMilliseconds, + RateLimitParser.httpRetryAfterDefaultDelay.inMilliseconds); }); test('parseable returns default category with duration in millis', () { @@ -92,7 +92,7 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.all); - expect(sut[0].durationInMillis, 8000); + expect(sut[0].duration.inMilliseconds, 8000); }); test('un-parseable returns default category with default duration', () { @@ -100,8 +100,8 @@ void main() { expect(sut.length, 1); expect(sut[0].category, RateLimitCategory.all); - expect(sut[0].durationInMillis, - RateLimitParser.httpRetryAfterDefaultDelayMillis); + expect(sut[0].duration.inMilliseconds, + RateLimitParser.httpRetryAfterDefaultDelay.inMilliseconds); }); }); } From 78d78e6be0b2643bec1448a2f95915c0eb573a33 Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 13 Apr 2021 11:46:32 +0200 Subject: [PATCH 65/83] Move compression in sink to encode.dart and noop_encode.dart --- dart/lib/src/transport/encode.dart | 7 +++++++ dart/lib/src/transport/http_transport.dart | 10 ++++------ dart/lib/src/transport/noop_encode.dart | 5 +++++ dart/lib/src/transport/rate_limit_parser.dart | 3 +-- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/dart/lib/src/transport/encode.dart b/dart/lib/src/transport/encode.dart index 03256130f9..645aaf02cd 100644 --- a/dart/lib/src/transport/encode.dart +++ b/dart/lib/src/transport/encode.dart @@ -5,3 +5,10 @@ List compressBody(List body, Map headers) { headers['Content-Encoding'] = 'gzip'; return gzip.encode(body); } + +/// Encodes bytes in sink using Gzip compression +Sink> compressInSink( + Sink> sink, Map headers) { + headers['Content-Encoding'] = 'gzip'; + return GZipCodec().encoder.startChunkedConversion(sink); +} diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index 7c2c7510a9..d54ed8a4c2 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -1,9 +1,9 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; import 'package:http/http.dart'; +import 'noop_encode.dart' if (dart.library.io) 'encode.dart'; import '../noop_client.dart'; import '../protocol.dart'; import '../sentry_options.dart'; @@ -90,13 +90,11 @@ class HttpTransport implements Transport { final streamedRequest = StreamedRequest('POST', _dsn.postUri); if (_options.compressPayload) { - _headers['Content-Encoding'] = 'gzip'; - final byteConversionSink = GZipCodec().encoder - .startChunkedConversion(streamedRequest.sink); + final compressionSink = compressInSink(streamedRequest.sink, _headers); envelope .envelopeStream() - .listen(byteConversionSink.add) - .onDone(byteConversionSink.close); + .listen(compressionSink.add) + .onDone(compressionSink.close); } else { envelope .envelopeStream() diff --git a/dart/lib/src/transport/noop_encode.dart b/dart/lib/src/transport/noop_encode.dart index 73f92b2fa0..fa1f9c5547 100644 --- a/dart/lib/src/transport/noop_encode.dart +++ b/dart/lib/src/transport/noop_encode.dart @@ -1,2 +1,7 @@ /// gzip compression is not available on browser List compressBody(List body, Map headers) => body; + +/// gzip compression is not available on browser +Sink> compressInSink( + Sink> sink, Map headers) => + sink; diff --git a/dart/lib/src/transport/rate_limit_parser.dart b/dart/lib/src/transport/rate_limit_parser.dart index a1619ef40d..3e881e0112 100644 --- a/dart/lib/src/transport/rate_limit_parser.dart +++ b/dart/lib/src/transport/rate_limit_parser.dart @@ -22,8 +22,7 @@ class RateLimitParser { final durationAndCategories = rateLimitValue.trim().split(':'); if (durationAndCategories.isNotEmpty) { - final duration = - _parseRetryAfterOrDefault(durationAndCategories[0]); + final duration = _parseRetryAfterOrDefault(durationAndCategories[0]); if (durationAndCategories.length > 1) { final allCategories = durationAndCategories[1]; From 950ff4abbcfb5342a42191af0c9d635342103cba Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 19 Apr 2021 10:32:38 +0200 Subject: [PATCH 66/83] Use string types in item headers --- dart/lib/src/sentry_envelope_item_header.dart | 6 ++--- dart/lib/src/sentry_item_type.dart | 26 +++++-------------- dart/lib/src/transport/rate_limiter.dart | 3 +-- 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart index 9c07a7eac4..e7cd428c88 100644 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -1,12 +1,10 @@ -import 'sentry_item_type.dart'; - /// Header with item info about type and length of data in bytes. class SentryEnvelopeItemHeader { SentryEnvelopeItemHeader(this.type, this.length, {this.contentType, this.fileName}); /// Type of encoded data. - final SentryItemType type; + final String type; /// The number of bytes of the encoded item JSON. final Future Function() length; @@ -22,7 +20,7 @@ class SentryEnvelopeItemHeader { if (tempContentType != null) { json['content_type'] = tempContentType; } - json['type'] = type.toStringValue(); + json['type'] = type; json['length'] = await length(); return json; } diff --git a/dart/lib/src/sentry_item_type.dart b/dart/lib/src/sentry_item_type.dart index 1cda04d06f..b90dff8538 100644 --- a/dart/lib/src/sentry_item_type.dart +++ b/dart/lib/src/sentry_item_type.dart @@ -1,21 +1,7 @@ -enum SentryItemType { event, unknown } - -extension SentryItemTypeExtension on SentryItemType { - static SentryItemType fromStringValue(String stringValue) { - switch (stringValue) { - case 'event': - return SentryItemType.event; - default: - return SentryItemType.unknown; - } - } - - String toStringValue() { - switch (this) { - case SentryItemType.event: - return 'event'; - case SentryItemType.unknown: - return '__unknown__'; - } - } +class SentryItemType { + static const String event = 'event'; + static const String userFeedback = 'user_report'; + static const String attachment = 'attachment'; + static const String transaction = 'transaction'; + static const String unknown = '__unknown__'; } diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index 5f41c34946..4713cd46df 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -3,7 +3,6 @@ import '../transport/rate_limit_parser.dart'; import '../sentry_options.dart'; import '../sentry_envelope.dart'; import '../sentry_envelope_item.dart'; -import '../sentry_item_type.dart'; import 'rate_limit.dart'; import 'rate_limit_category.dart'; @@ -20,7 +19,7 @@ class RateLimiter { List? dropItems; for (final item in envelope.items) { // using the raw value of the enum to not expose SentryEnvelopeItemType - if (_isRetryAfter(item.header.type.toStringValue())) { + if (_isRetryAfter(item.header.type)) { dropItems ??= []; dropItems.add(item); } From 009138cca3b0dfb2e2d2ec8b9f8bf766efea3720 Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 17 May 2021 09:34:05 +0200 Subject: [PATCH 67/83] Use for loops and contunie early --- dart/lib/src/transport/rate_limit_parser.dart | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/dart/lib/src/transport/rate_limit_parser.dart b/dart/lib/src/transport/rate_limit_parser.dart index 3e881e0112..9b269a4d24 100644 --- a/dart/lib/src/transport/rate_limit_parser.dart +++ b/dart/lib/src/transport/rate_limit_parser.dart @@ -14,33 +14,31 @@ class RateLimitParser { if (rateLimitHeader == null) { return []; } - final rateLimits = []; - final rateLimitValues = rateLimitHeader.toLowerCase().split(','); - rateLimitValues.forEach((rateLimitValue) { + for (final rateLimitValue in rateLimitValues) { final durationAndCategories = rateLimitValue.trim().split(':'); - - if (durationAndCategories.isNotEmpty) { - final duration = _parseRetryAfterOrDefault(durationAndCategories[0]); - - if (durationAndCategories.length > 1) { - final allCategories = durationAndCategories[1]; - if (allCategories.isNotEmpty) { - final categoryValues = allCategories.split(';'); - categoryValues.forEach((categoryValue) { - final category = - RateLimitCategoryExtension.fromStringValue(categoryValue); - if (category != RateLimitCategory.unknown) { - rateLimits.add(RateLimit(category, duration)); - } - }); - } else { - rateLimits.add(RateLimit(RateLimitCategory.all, duration)); + if (durationAndCategories.isEmpty) { + continue; + } + final duration = _parseRetryAfterOrDefault(durationAndCategories[0]); + if (durationAndCategories.length <= 1) { + continue; + } + final allCategories = durationAndCategories[1]; + if (allCategories.isNotEmpty) { + final categoryValues = allCategories.split(';'); + for (final categoryValue in categoryValues) { + final category = + RateLimitCategoryExtension.fromStringValue(categoryValue); + if (category != RateLimitCategory.unknown) { + rateLimits.add(RateLimit(category, duration)); } } + } else { + rateLimits.add(RateLimit(RateLimitCategory.all, duration)); } - }); + } return rateLimits; } From c6c1fbf8b619d730a8cf4365218464398c338dd6 Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 17 May 2021 09:39:46 +0200 Subject: [PATCH 68/83] Use isAfter method on date --- dart/lib/src/transport/rate_limiter.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index 4713cd46df..a482a0f03f 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -78,8 +78,7 @@ class RateLimiter { // check all categories final dateAllCategories = _rateLimitedUntil[RateLimitCategory.all]; if (dateAllCategories != null) { - if (!(currentDate.millisecondsSinceEpoch > - dateAllCategories.millisecondsSinceEpoch)) { + if (!currentDate.isAfter(dateAllCategories)) { return true; } } @@ -92,8 +91,7 @@ class RateLimiter { // check for specific dataCategory final dateCategory = _rateLimitedUntil[dataCategory]; if (dateCategory != null) { - return !(currentDate.millisecondsSinceEpoch > - dateCategory.millisecondsSinceEpoch); + return !currentDate.isAfter(dateCategory); } return false; From 215ce24b3c5f5c516fdc89e0be123578c677a788 Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 17 May 2021 09:44:58 +0200 Subject: [PATCH 69/83] Use isAfter date method --- dart/lib/src/transport/rate_limiter.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dart/lib/src/transport/rate_limiter.dart b/dart/lib/src/transport/rate_limiter.dart index a482a0f03f..0ce91b3b59 100644 --- a/dart/lib/src/transport/rate_limiter.dart +++ b/dart/lib/src/transport/rate_limiter.dart @@ -117,8 +117,7 @@ class RateLimiter { final oldDate = _rateLimitedUntil[rateLimitCategory]; // only overwrite its previous date if the limit is even longer - if (oldDate == null || - date.millisecondsSinceEpoch > oldDate.millisecondsSinceEpoch) { + if (oldDate == null || date.isAfter(oldDate)) { _rateLimitedUntil[rateLimitCategory] = date; } } From 996f976ce44379a8aad92e2921e967ff87b1d1b9 Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 17 May 2021 10:11:57 +0200 Subject: [PATCH 70/83] Fix typo --- dart/test/sentry_envelope_test.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dart/test/sentry_envelope_test.dart b/dart/test/sentry_envelope_test.dart index d23560b302..d04e0ee482 100644 --- a/dart/test/sentry_envelope_test.dart +++ b/dart/test/sentry_envelope_test.dart @@ -28,15 +28,15 @@ void main() { final header = SentryEnvelopeHeader(eventId, null); final sut = SentryEnvelope(header, [item, item]); - final expectesHeaderJson = header.toJson(); - final expectesHeaderJsonSerialized = jsonEncode(expectesHeaderJson); + final expectedHeaderJson = header.toJson(); + final expectedHeaderJsonSerialized = jsonEncode(expectedHeaderJson); final expectedItem = []; await item.envelopeItemStream().forEach(expectedItem.addAll); final expectedItemSerialized = utf8.decode(expectedItem); final expected = utf8.encode( - '$expectesHeaderJsonSerialized\n$expectedItemSerialized\n$expectedItemSerialized'); + '$expectedHeaderJsonSerialized\n$expectedItemSerialized\n$expectedItemSerialized'); final envelopeData = []; await sut.envelopeStream().forEach(envelopeData.addAll); From 40a3cc29ad5d4287f965d73be8fe61d984c514c5 Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 17 May 2021 10:12:13 +0200 Subject: [PATCH 71/83] no need to temp variable --- dart/lib/src/sentry_envelope_item_header.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart index e7cd428c88..99a4580427 100644 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -16,9 +16,8 @@ class SentryEnvelopeItemHeader { /// Item header encoded as JSON Future> toJson() async { final json = {}; - final tempContentType = contentType; - if (tempContentType != null) { - json['content_type'] = tempContentType; + if (contentType != null) { + json['content_type'] = contentType; } json['type'] = type; json['length'] = await length(); From ecc68cfcbd4737dfd12572c7a735858f30c45b5d Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 17 May 2021 10:19:00 +0200 Subject: [PATCH 72/83] format --- dart/lib/src/transport/rate_limit_parser.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dart/lib/src/transport/rate_limit_parser.dart b/dart/lib/src/transport/rate_limit_parser.dart index 9b269a4d24..fe30121162 100644 --- a/dart/lib/src/transport/rate_limit_parser.dart +++ b/dart/lib/src/transport/rate_limit_parser.dart @@ -30,7 +30,7 @@ class RateLimitParser { final categoryValues = allCategories.split(';'); for (final categoryValue in categoryValues) { final category = - RateLimitCategoryExtension.fromStringValue(categoryValue); + RateLimitCategoryExtension.fromStringValue(categoryValue); if (category != RateLimitCategory.unknown) { rateLimits.add(RateLimit(category, duration)); } From cdc5e184a4aa45645966439fbee0fe06c864cfc1 Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 17 May 2021 10:20:47 +0200 Subject: [PATCH 73/83] Fix path in comments --- flutter/test/mocks.mocks.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/test/mocks.mocks.dart b/flutter/test/mocks.mocks.dart index 2b967534f5..0b1ff36b40 100644 --- a/flutter/test/mocks.mocks.dart +++ b/flutter/test/mocks.mocks.dart @@ -1,5 +1,5 @@ // Mocks generated by Mockito 5.0.3 from annotations -// in sentry_flutter/example/ios/.symlinks/plugins/sentry_flutter/test/mocks.dart. +// in sentry_flutter/test/mocks.dart. // Do not manually edit this file. import 'dart:async' as _i4; From e37bf31f7b2eafb9d409bc3859bd8537fd50a4b7 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 21 May 2021 09:51:04 +0200 Subject: [PATCH 74/83] Remove commented out code --- dart/test/protocol/rate_limiter_test.dart | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/dart/test/protocol/rate_limiter_test.dart b/dart/test/protocol/rate_limiter_test.dart index ea1488be7d..281d5538ca 100644 --- a/dart/test/protocol/rate_limiter_test.dart +++ b/dart/test/protocol/rate_limiter_test.dart @@ -38,14 +38,8 @@ void main() { fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - // TODO Add another envelope item with different type and update rate limit header - - // final transaction = SentryTransaction( - // SentryTracer(TransactionContext('name', 'op'), mock())); - // final transactionItem = - // SentryEnvelopeItem.fromEvent(fixture.serializer, transaction); final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), [eventItem /*, transactionItem*/]); + SentryEnvelopeHeader.newEventId(), [eventItem]); rateLimiter.updateRetryAfterLimits( '50:transaction:key, 2700:default;error;security:organization', @@ -63,13 +57,8 @@ void main() { fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - // TODO Add another envelope item with different type and update rate limit header - - // final transaction = SentryTransaction( - // SentryTracer(TransactionContext('name', 'op'), mock())); - // final transactionItem = SentryEnvelopeItem.fromEvent(transaction); final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), [eventItem /*, transactionItem*/]); + SentryEnvelopeHeader.newEventId(), [eventItem]); rateLimiter.updateRetryAfterLimits( '1:transaction:key, 1:default;error;security:organization', null, 1); @@ -79,7 +68,6 @@ void main() { final result = rateLimiter.filter(envelope); expect(result, isNotNull); expect(1, result!.items.length); - //expect(2, result!.items.length); // TODO Update after added second item }); test( From 00ee4bf040ba2d4e453dc4a9e7b418996bb686ed Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 21 May 2021 15:05:08 +0200 Subject: [PATCH 75/83] format --- dart/test/protocol/rate_limiter_test.dart | 44 +++++++++++++------ flutter/lib/src/widgets_binding_observer.dart | 2 +- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/dart/test/protocol/rate_limiter_test.dart b/dart/test/protocol/rate_limiter_test.dart index 281d5538ca..81775e0b40 100644 --- a/dart/test/protocol/rate_limiter_test.dart +++ b/dart/test/protocol/rate_limiter_test.dart @@ -18,8 +18,10 @@ void main() { fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = - SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); rateLimiter.updateRetryAfterLimits( '50:transaction:key, 1:default;error;security:organization', null, 1); @@ -39,7 +41,9 @@ void main() { final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), [eventItem]); + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); rateLimiter.updateRetryAfterLimits( '50:transaction:key, 2700:default;error;security:organization', @@ -58,7 +62,9 @@ void main() { final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); final envelope = SentryEnvelope( - SentryEnvelopeHeader.newEventId(), [eventItem]); + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); rateLimiter.updateRetryAfterLimits( '1:transaction:key, 1:default;error;security:organization', null, 1); @@ -76,8 +82,10 @@ void main() { final rateLimiter = fixture.getSUT(); fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = - SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); rateLimiter.updateRetryAfterLimits('50::key', null, 1); @@ -91,8 +99,10 @@ void main() { final rateLimiter = fixture.getSUT(); fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = - SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); rateLimiter.updateRetryAfterLimits( '1::key, 60:default;error;security:organization', null, 1); @@ -108,8 +118,10 @@ void main() { final rateLimiter = fixture.getSUT(); fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = - SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); rateLimiter.updateRetryAfterLimits( '60:error:key, 1:error:organization', null, 1); @@ -124,8 +136,10 @@ void main() { final rateLimiter = fixture.getSUT(); fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = - SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); rateLimiter.updateRetryAfterLimits( '1:error:key, 5:error:organization', null, 1); @@ -140,8 +154,10 @@ void main() { final rateLimiter = fixture.getSUT(); fixture.dateTimeToReturn = 0; final eventItem = SentryEnvelopeItem.fromEvent(SentryEvent()); - final envelope = - SentryEnvelope(SentryEnvelopeHeader.newEventId(), [eventItem]); + final envelope = SentryEnvelope( + SentryEnvelopeHeader.newEventId(), + [eventItem], + ); rateLimiter.updateRetryAfterLimits(null, null, 429); diff --git a/flutter/lib/src/widgets_binding_observer.dart b/flutter/lib/src/widgets_binding_observer.dart index 2cfc193d27..6a591bce9f 100644 --- a/flutter/lib/src/widgets_binding_observer.dart +++ b/flutter/lib/src/widgets_binding_observer.dart @@ -21,7 +21,7 @@ class SentryWidgetsBindingObserver with WidgetsBindingObserver { SentryWidgetsBindingObserver({ Hub? hub, required SentryFlutterOptions options, - }) : _hub = hub ?? HubAdapter(), + }) : _hub = hub ?? HubAdapter(), _options = options; final Hub _hub; From d01bb25e6a5cd15bf3c0775d5a7fe24ccd8c8990 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 21 May 2021 15:39:38 +0200 Subject: [PATCH 76/83] Use fixture pattern --- dart/test/sentry_client_test.dart | 23 +++---- dart/test/transport/http_transport_test.dart | 72 ++++++++++---------- 2 files changed, 46 insertions(+), 49 deletions(-) diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart index 0b702b8d26..c752af949d 100644 --- a/dart/test/sentry_client_test.dart +++ b/dart/test/sentry_client_test.dart @@ -405,7 +405,7 @@ void main() { test('sendDefaultPii is disabled', () async { final transport = MockTransport(); - final client = fixture.getSut(false, transport); + final client = fixture.getSut(transport, sendDefaultPii: false); await client.captureEvent(fakeEvent); @@ -414,7 +414,7 @@ void main() { test('sendDefaultPii is enabled and event has no user', () async { final transport = MockTransport(); - final client = fixture.getSut(true, transport); + final client = fixture.getSut(transport, sendDefaultPii: true); var fakeEvent = SentryEvent(); await client.captureEvent(fakeEvent); @@ -427,7 +427,7 @@ void main() { test('sendDefaultPii is enabled and event has a user with IP address', () async { final transport = MockTransport(); - final client = fixture.getSut(true, transport); + final client = fixture.getSut(transport, sendDefaultPii: true); await client.captureEvent(fakeEvent); @@ -442,7 +442,7 @@ void main() { test('sendDefaultPii is enabled and event has a user without IP address', () async { final transport = MockTransport(); - final client = fixture.getSut(true, transport); + final client = fixture.getSut(transport, sendDefaultPii: true); final event = fakeEvent.copyWith(user: fakeUser); @@ -594,19 +594,18 @@ void main() { }); group('SentryClient captures envelope', () { - var options = SentryOptions(dsn: fakeDsn); + late Fixture fixture; setUp(() { - options = SentryOptions(dsn: fakeDsn); - options.transport = MockTransport(); + fixture = Fixture(); }); test('should capture envelope', () async { - final client = SentryClient(options); + final client = fixture.getSut(MockTransport()); await client.captureEnvelope(fakeEnvelope); final capturedEnvelope = - (options.transport as MockTransport).envelopes.first; + (fixture.options.transport as MockTransport).envelopes.first; expect(capturedEnvelope, fakeEnvelope); }); @@ -632,9 +631,9 @@ SentryEvent? eventProcessorDropEvent(SentryEvent event, {dynamic hint}) { } class Fixture { - /// Test Fixture for tests with [SentryOptions.sendDefaultPii] - SentryClient getSut(bool sendDefaultPii, Transport transport) { - var options = SentryOptions(dsn: fakeDsn); + final options = SentryOptions(dsn: fakeDsn); + + SentryClient getSut(Transport transport, {bool sendDefaultPii = false}) { options.sendDefaultPii = sendDefaultPii; options.transport = transport; return SentryClient(options); diff --git a/dart/test/transport/http_transport_test.dart b/dart/test/transport/http_transport_test.dart index bc3867bba0..25d80a7100 100644 --- a/dart/test/transport/http_transport_test.dart +++ b/dart/test/transport/http_transport_test.dart @@ -6,6 +6,7 @@ import 'package:sentry/src/sentry_envelope_header.dart'; import 'package:sentry/src/sentry_envelope_item.dart'; import 'package:sentry/src/sentry_envelope_item_header.dart'; import 'package:sentry/src/sentry_item_type.dart'; +import 'package:sentry/src/transport/rate_limiter.dart'; import 'package:test/test.dart'; import 'package:sentry/sentry.dart'; @@ -28,19 +29,20 @@ void main() { } group('filter', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + test('filter called', () async { final httpMock = MockClient((http.Request request) async { return http.Response('{}', 200); }); - final options = - SentryOptions(dsn: 'https://public:secret@sentry.example.com/1') - ..compressPayload = false - ..httpClient = httpMock; - + fixture.options.compressPayload = false; final mockRateLimiter = MockRateLimiter(); - - final sut = HttpTransport(options, mockRateLimiter); + final sut = fixture.getSut(httpMock, mockRateLimiter); final sentryEnvelope = givenEnvelope(); await sut.sendSentryEnvelope(sentryEnvelope); @@ -58,15 +60,11 @@ void main() { final filteredEnvelope = givenEnvelope(); - final mockRateLimiter = MockRateLimiter(); - mockRateLimiter.filteredEnvelope = filteredEnvelope; + fixture.options.compressPayload = false; + final mockRateLimiter = MockRateLimiter() + ..filteredEnvelope = filteredEnvelope; + final sut = fixture.getSut(httpMock, mockRateLimiter); - final options = - SentryOptions(dsn: 'https://public:secret@sentry.example.com/1') - ..compressPayload = false - ..httpClient = httpMock; - - final sut = HttpTransport(options, mockRateLimiter); final sentryEvent = SentryEvent(); await sut.sendSentryEvent(sentryEvent); @@ -83,15 +81,9 @@ void main() { return http.Response('{}', 200); }); - final options = - SentryOptions(dsn: 'https://public:secret@sentry.example.com/1') - ..compressPayload = false - ..httpClient = httpMock; - - final mockRateLimiter = MockRateLimiter(); - mockRateLimiter.filterReturnsNull = true; - - final sut = HttpTransport(options, mockRateLimiter); + fixture.options.compressPayload = false; + final mockRateLimiter = MockRateLimiter()..filterReturnsNull = true; + final sut = fixture.getSut(httpMock, mockRateLimiter); final sentryEvent = SentryEvent(); final eventId = await sut.sendSentryEvent(sentryEvent); @@ -102,21 +94,21 @@ void main() { }); group('updateRetryAfterLimits', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + test('retryAfterHeader', () async { final httpMock = MockClient((http.Request request) async { return http.Response('{}', 429, headers: {'Retry-After': '1'}); }); - final mockRateLimiter = MockRateLimiter(); + final sut = fixture.getSut(httpMock, mockRateLimiter); - final options = - SentryOptions(dsn: 'https://public:secret@sentry.example.com/1') - ..httpClient = httpMock; - - final sut = HttpTransport(options, mockRateLimiter); final sentryEvent = SentryEvent(); await sut.sendSentryEvent(sentryEvent); - expect(mockRateLimiter.envelopeToFilter?.header.eventId, sentryEvent.eventId); @@ -130,14 +122,9 @@ void main() { return http.Response('{}', 200, headers: {'X-Sentry-Rate-Limits': 'fixture-sentryRateLimitHeader'}); }); - final mockRateLimiter = MockRateLimiter(); + final sut = fixture.getSut(httpMock, mockRateLimiter); - final options = - SentryOptions(dsn: 'https://public:secret@sentry.example.com/1') - ..httpClient = httpMock; - - final sut = HttpTransport(options, mockRateLimiter); final sentryEvent = SentryEvent(); await sut.sendSentryEvent(sentryEvent); @@ -148,3 +135,14 @@ void main() { }); }); } + +class Fixture { + final options = SentryOptions( + dsn: 'https://public:secret@sentry.example.com/1', + ); + + HttpTransport getSut(http.Client client, RateLimiter rateLimiter) { + options.httpClient = client; + return HttpTransport(options, rateLimiter); + } +} From 2e204d45feefe1273fd8cfdc3f47557fa9e580b7 Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 25 May 2021 10:43:19 +0200 Subject: [PATCH 77/83] Run format --- flutter/lib/src/widgets_binding_observer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/src/widgets_binding_observer.dart b/flutter/lib/src/widgets_binding_observer.dart index 6a591bce9f..2cfc193d27 100644 --- a/flutter/lib/src/widgets_binding_observer.dart +++ b/flutter/lib/src/widgets_binding_observer.dart @@ -21,7 +21,7 @@ class SentryWidgetsBindingObserver with WidgetsBindingObserver { SentryWidgetsBindingObserver({ Hub? hub, required SentryFlutterOptions options, - }) : _hub = hub ?? HubAdapter(), + }) : _hub = hub ?? HubAdapter(), _options = options; final Hub _hub; From f9a4baedc7b556c31e4747cf477353a1763816de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Andra=C5=A1ec?= Date: Wed, 26 May 2021 13:38:42 +0200 Subject: [PATCH 78/83] Envelope Only Transport API (#426) --- CHANGELOG.md | 4 +- dart/lib/src/protocol/breadcrumb.dart | 14 + dart/lib/src/protocol/contexts.dart | 1 + dart/lib/src/protocol/debug_image.dart | 16 ++ dart/lib/src/protocol/debug_meta.dart | 14 + dart/lib/src/protocol/mechanism.dart | 14 + dart/lib/src/protocol/sdk_info.dart | 11 + dart/lib/src/protocol/sdk_version.dart | 14 + dart/lib/src/protocol/sentry_app.dart | 25 +- dart/lib/src/protocol/sentry_browser.dart | 11 +- dart/lib/src/protocol/sentry_device.dart | 125 ++++----- dart/lib/src/protocol/sentry_event.dart | 82 ++++++ dart/lib/src/protocol/sentry_exception.dart | 18 ++ dart/lib/src/protocol/sentry_gpu.dart | 1 + dart/lib/src/protocol/sentry_level.dart | 14 + dart/lib/src/protocol/sentry_message.dart | 10 + .../src/protocol/sentry_operating_system.dart | 21 +- dart/lib/src/protocol/sentry_package.dart | 8 + dart/lib/src/protocol/sentry_request.dart | 15 ++ dart/lib/src/protocol/sentry_runtime.dart | 13 +- dart/lib/src/protocol/sentry_stack_frame.dart | 30 ++- dart/lib/src/protocol/sentry_stack_trace.dart | 14 + dart/lib/src/protocol/sentry_user.dart | 11 + dart/lib/src/sentry_client.dart | 5 +- dart/lib/src/sentry_envelope.dart | 2 +- dart/lib/src/sentry_envelope_item.dart | 4 +- dart/lib/src/sentry_envelope_item_header.dart | 3 + dart/lib/src/transport/http_transport.dart | 8 +- dart/lib/src/transport/noop_transport.dart | 6 +- dart/lib/src/transport/transport.dart | 3 +- dart/test/contexts_test.dart | 89 ++++--- dart/test/mocks/mock_transport.dart | 11 +- dart/test/protocol/app_test.dart | 50 ---- dart/test/protocol/breadcrumb_test.dart | 105 +++++--- dart/test/protocol/browser_test.dart | 33 --- dart/test/protocol/contexts_test.dart | 152 +++++++---- dart/test/protocol/debug_image_test.dart | 120 ++++++--- dart/test/protocol/debug_meta_test.dart | 94 ++++--- dart/test/protocol/gpu_test.dart | 54 ---- dart/test/protocol/mechanism_test.dart | 100 ++++--- dart/test/protocol/message_test.dart | 36 --- dart/test/protocol/operating_system_test.dart | 42 --- dart/test/protocol/request_test.dart | 42 --- dart/test/protocol/sdk_info_test.dart | 80 ++++-- dart/test/protocol/sdk_version_test.dart | 102 +++++--- dart/test/protocol/sentry_app_test.dart | 83 ++++++ dart/test/protocol/sentry_browser_test.dart | 60 +++++ dart/test/protocol/sentry_device_test.dart | 156 +++++++++++ dart/test/protocol/sentry_exception_test.dart | 244 ++++++++++-------- dart/test/protocol/sentry_gpu_test.dart | 86 ++++++ dart/test/protocol/sentry_message_test.dart | 64 +++++ .../sentry_operating_system_test.dart | 74 ++++++ dart/test/protocol/sentry_package_test.dart | 66 +++-- dart/test/protocol/sentry_request_test.dart | 78 ++++++ dart/test/protocol/sentry_runtime_test.dart | 80 ++++-- .../protocol/sentry_stack_frame_test.dart | 178 ++++++++----- .../protocol/sentry_stack_trace_test.dart | 85 ++++-- dart/test/protocol/sentry_user_test.dart | 95 +++++++ dart/test/protocol/user_test.dart | 64 ----- dart/test/sentry_client_test.dart | 145 ++++++++--- dart/test/sentry_envelope_header_test.dart | 2 +- dart/test/sentry_envelope_test.dart | 2 +- dart/test/sentry_envelope_vm_test.dart | 46 ++++ dart/test/sentry_event_test.dart | 94 +++++++ dart/test/transport/http_transport_test.dart | 19 +- .../envelope-with-image.envelope | Bin 0 -> 3712 bytes dart/test_resources/sentry.png | Bin 0 -> 3535 bytes .../io/sentry/flutter/SentryFlutterPlugin.kt | 8 +- .../Classes/SentryFlutterPluginApple.swift | 64 +---- flutter/lib/src/file_system_transport.dart | 16 +- flutter/test/file_system_transport_test.dart | 20 +- flutter/test/mocks.mocks.dart | 27 +- 72 files changed, 2363 insertions(+), 1120 deletions(-) delete mode 100644 dart/test/protocol/app_test.dart delete mode 100644 dart/test/protocol/browser_test.dart delete mode 100644 dart/test/protocol/gpu_test.dart delete mode 100644 dart/test/protocol/message_test.dart delete mode 100644 dart/test/protocol/operating_system_test.dart delete mode 100644 dart/test/protocol/request_test.dart create mode 100644 dart/test/protocol/sentry_app_test.dart create mode 100644 dart/test/protocol/sentry_browser_test.dart create mode 100644 dart/test/protocol/sentry_device_test.dart create mode 100644 dart/test/protocol/sentry_gpu_test.dart create mode 100644 dart/test/protocol/sentry_message_test.dart create mode 100644 dart/test/protocol/sentry_operating_system_test.dart create mode 100644 dart/test/protocol/sentry_request_test.dart create mode 100644 dart/test/protocol/sentry_user_test.dart delete mode 100644 dart/test/protocol/user_test.dart create mode 100644 dart/test/sentry_envelope_vm_test.dart create mode 100644 dart/test_resources/envelope-with-image.envelope create mode 100644 dart/test_resources/sentry.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b31045d60..6b6429d377 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +* Feature: Envelope Only Transport API #426 (#463) + # 5.1.0-beta.1 * Fix: `Sentry.close()` closes native SDK integrations (#388) @@ -25,7 +27,7 @@ ## Breaking Changes: * Feat: Support envelope based transport for events (#391) - * The method signature of `Transport` changed from `Future send(SentryEvent event)` to `Future sendSentryEvent(SentryEvent event)` + * The method signature of `Transport` changed from `Future send(SentryEvent event)` to `Future send(SentryEnvelope envelope)` ## Sentry Self Hosted Compatibility diff --git a/dart/lib/src/protocol/breadcrumb.dart b/dart/lib/src/protocol/breadcrumb.dart index b07460ce8f..cbbfa7cbd7 100644 --- a/dart/lib/src/protocol/breadcrumb.dart +++ b/dart/lib/src/protocol/breadcrumb.dart @@ -76,6 +76,20 @@ class Breadcrumb { /// The value is submitted to Sentry with second precision. final DateTime timestamp; + /// Deserializes a [Breadcrumb] from JSON [Map]. + factory Breadcrumb.fromJson(Map json) { + final levelName = json['level']; + final timestamp = json['timestamp']; + return Breadcrumb( + timestamp: timestamp != null ? DateTime.tryParse(timestamp) : null, + message: json['message'], + category: json['category'], + data: json['data'], + level: levelName != null ? SentryLevel.fromName(levelName) : null, + type: json['type'], + ); + } + /// Converts this breadcrumb to a map that can be serialized to JSON according /// to the Sentry protocol. Map toJson() { diff --git a/dart/lib/src/protocol/contexts.dart b/dart/lib/src/protocol/contexts.dart index a1205e9650..d7b8586515 100644 --- a/dart/lib/src/protocol/contexts.dart +++ b/dart/lib/src/protocol/contexts.dart @@ -25,6 +25,7 @@ class Contexts extends MapView { SentryGpu.type: gpu, }); + /// Deserializes [Contexts] from JSON [Map]. factory Contexts.fromJson(Map data) { final contexts = Contexts( device: data[SentryDevice.type] != null diff --git a/dart/lib/src/protocol/debug_image.dart b/dart/lib/src/protocol/debug_image.dart index 4fd3eb1972..b75dea5bb0 100644 --- a/dart/lib/src/protocol/debug_image.dart +++ b/dart/lib/src/protocol/debug_image.dart @@ -50,6 +50,22 @@ class DebugImage { this.codeId, }); + /// Deserializes a [DebugImage] from JSON [Map]. + factory DebugImage.fromJson(Map json) { + return DebugImage( + type: json['type'], + imageAddr: json['image_addr'], + debugId: json['debug_id'], + debugFile: json['debug_file'], + imageSize: json['image_size'], + uuid: json['uuid'], + codeFile: json['code_file'], + arch: json['arch'], + codeId: json['code_id'], + ); + } + + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; diff --git a/dart/lib/src/protocol/debug_meta.dart b/dart/lib/src/protocol/debug_meta.dart index 1b6aebd993..24d8183d54 100644 --- a/dart/lib/src/protocol/debug_meta.dart +++ b/dart/lib/src/protocol/debug_meta.dart @@ -18,6 +18,20 @@ class DebugMeta { DebugMeta({this.sdk, List? images}) : _images = images; + /// Deserializes a [DebugMeta] from JSON [Map]. + factory DebugMeta.fromJson(Map json) { + final sdkInfoJson = json['sdk_info']; + final debugImagesJson = json['images'] as List?; + return DebugMeta( + sdk: sdkInfoJson != null ? SdkInfo.fromJson(sdkInfoJson) : null, + images: debugImagesJson + ?.map((debugImageJson) => + DebugImage.fromJson(debugImageJson as Map)) + .toList(), + ); + } + + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; diff --git a/dart/lib/src/protocol/mechanism.dart b/dart/lib/src/protocol/mechanism.dart index 69b6e0320b..8d8977b97c 100644 --- a/dart/lib/src/protocol/mechanism.dart +++ b/dart/lib/src/protocol/mechanism.dart @@ -74,6 +74,20 @@ class Mechanism { synthetic: synthetic ?? this.synthetic, ); + /// Deserializes a [Mechanism] from JSON [Map]. + factory Mechanism.fromJson(Map json) { + return Mechanism( + type: json['type'], + description: json['description'], + helpLink: json['help_link'], + handled: json['handled'], + meta: json['meta'], + data: json['data'], + synthetic: json['synthetic'], + ); + } + + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; diff --git a/dart/lib/src/protocol/sdk_info.dart b/dart/lib/src/protocol/sdk_info.dart index a58ab63e73..a3434afd83 100644 --- a/dart/lib/src/protocol/sdk_info.dart +++ b/dart/lib/src/protocol/sdk_info.dart @@ -15,6 +15,17 @@ class SdkInfo { this.versionPatchlevel, }); + /// Deserializes a [SdkInfo] from JSON [Map]. + factory SdkInfo.fromJson(Map json) { + return SdkInfo( + sdkName: json['sdk_name'], + versionMajor: json['version_major'], + versionMinor: json['version_minor'], + versionPatchlevel: json['version_patchlevel'], + ); + } + + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; if (sdkName != null) { diff --git a/dart/lib/src/protocol/sdk_version.dart b/dart/lib/src/protocol/sdk_version.dart index dc4a7c9ad3..b69ca3c130 100644 --- a/dart/lib/src/protocol/sdk_version.dart +++ b/dart/lib/src/protocol/sdk_version.dart @@ -63,6 +63,20 @@ class SdkVersion { String get identifier => '$name/$version'; + /// Deserializes a [SdkVersion] from JSON [Map]. + factory SdkVersion.fromJson(Map json) { + final packagesJson = json['packages'] as List?; + final integrationsJson = json['integrations'] as List?; + return SdkVersion( + name: json['name'], + version: json['version'], + packages: packagesJson + ?.map((e) => SentryPackage.fromJson(e as Map)) + .toList(), + integrations: integrationsJson?.map((e) => e as String).toList(), + ); + } + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; diff --git a/dart/lib/src/protocol/sentry_app.dart b/dart/lib/src/protocol/sentry_app.dart index 1e4c87ecbe..90581e1fb4 100644 --- a/dart/lib/src/protocol/sentry_app.dart +++ b/dart/lib/src/protocol/sentry_app.dart @@ -18,18 +18,6 @@ class SentryApp { this.deviceAppHash, }); - factory SentryApp.fromJson(Map data) => SentryApp( - name: data['app_name'], - version: data['app_version'], - identifier: data['app_identifier'], - build: data['app_build'], - buildType: data['build_type'], - startTime: data['app_start_time'] != null - ? DateTime.tryParse(data['app_start_time']) - : null, - deviceAppHash: data['device_app_hash'], - ); - /// Human readable application name, as it appears on the platform. final String? name; @@ -51,6 +39,19 @@ class SentryApp { /// Application specific device identifier. final String? deviceAppHash; + /// Deserializes a [SentryApp] from JSON [Map]. + factory SentryApp.fromJson(Map data) => SentryApp( + name: data['app_name'], + version: data['app_version'], + identifier: data['app_identifier'], + build: data['app_build'], + buildType: data['build_type'], + startTime: data['app_start_time'] != null + ? DateTime.tryParse(data['app_start_time']) + : null, + deviceAppHash: data['device_app_hash'], + ); + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; diff --git a/dart/lib/src/protocol/sentry_browser.dart b/dart/lib/src/protocol/sentry_browser.dart index 32038bf2a4..5c536d5819 100644 --- a/dart/lib/src/protocol/sentry_browser.dart +++ b/dart/lib/src/protocol/sentry_browser.dart @@ -11,17 +11,18 @@ class SentryBrowser { /// Creates an instance of [SentryBrowser]. const SentryBrowser({this.name, this.version}); - factory SentryBrowser.fromJson(Map data) => SentryBrowser( - name: data['name'], - version: data['version'], - ); - /// Human readable application name, as it appears on the platform. final String? name; /// Human readable application version, as it appears on the platform. final String? version; + /// Deserializes a [SentryBrowser] from JSON [Map]. + factory SentryBrowser.fromJson(Map data) => SentryBrowser( + name: data['name'], + version: data['version'], + ); + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; diff --git a/dart/lib/src/protocol/sentry_device.dart b/dart/lib/src/protocol/sentry_device.dart index 517593f01a..2630b8d6c1 100644 --- a/dart/lib/src/protocol/sentry_device.dart +++ b/dart/lib/src/protocol/sentry_device.dart @@ -41,39 +41,6 @@ class SentryDevice { batteryLevel == null || (batteryLevel >= 0 && batteryLevel <= 100), ); - factory SentryDevice.fromJson(Map data) => SentryDevice( - name: data['name'], - family: data['family'], - model: data['model'], - modelId: data['model_id'], - arch: data['arch'], - batteryLevel: data['battery_level'], - orientation: data['orientation'], - manufacturer: data['manufacturer'], - brand: data['brand'], - screenResolution: data['screen_resolution'], - screenHeightPixels: data['screen_height_pixels'], - screenWidthPixels: data['screen_width_pixels'], - screenDensity: data['screen_density'], - screenDpi: data['screen_dpi'], - online: data['online'], - charging: data['charging'], - lowMemory: data['low_memory'], - simulator: data['simulator'], - memorySize: data['memory_size'], - freeMemory: data['free_memory'], - usableMemory: data['usable_memory'], - storageSize: data['storage_size'], - freeStorage: data['free_storage'], - externalStorageSize: data['external_storage_size'], - externalFreeStorage: data['external_free_storage'], - bootTime: data['boot_time'] != null - ? DateTime.tryParse(data['boot_time']) - : null, - timezone: data['timezone'], - language: data['language'], - ); - /// The name of the device. This is typically a hostname. final String? name; @@ -165,6 +132,44 @@ class SentryDevice { /// The language of the device, e.g.: `en_US`. final String? language; + /// Deserializes a [SentryDevice] from JSON [Map]. + factory SentryDevice.fromJson(Map data) => SentryDevice( + name: data['name'], + family: data['family'], + model: data['model'], + modelId: data['model_id'], + arch: data['arch'], + batteryLevel: data['battery_level'], + orientation: data['orientation'] == 'portrait' + ? SentryOrientation.portrait + : data['orientation'] == 'landscape' + ? SentryOrientation.landscape + : null, + manufacturer: data['manufacturer'], + brand: data['brand'], + screenResolution: data['screen_resolution'], + screenHeightPixels: data['screen_height_pixels'], + screenWidthPixels: data['screen_width_pixels'], + screenDensity: data['screen_density'], + screenDpi: data['screen_dpi'], + online: data['online'], + charging: data['charging'], + lowMemory: data['low_memory'], + simulator: data['simulator'], + memorySize: data['memory_size'], + freeMemory: data['free_memory'], + usableMemory: data['usable_memory'], + storageSize: data['storage_size'], + freeStorage: data['free_storage'], + externalStorageSize: data['external_storage_size'], + externalFreeStorage: data['external_free_storage'], + bootTime: data['boot_time'] != null + ? DateTime.tryParse(data['boot_time']) + : null, + timezone: data['timezone'], + language: data['language'], + ); + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; @@ -299,32 +304,34 @@ class SentryDevice { } SentryDevice clone() => SentryDevice( - name: name, - family: family, - model: model, - modelId: modelId, - arch: arch, - batteryLevel: batteryLevel, - orientation: orientation, - manufacturer: manufacturer, - brand: brand, - screenResolution: screenResolution, - screenDensity: screenDensity, - screenDpi: screenDpi, - online: online, - charging: charging, - lowMemory: lowMemory, - simulator: simulator, - memorySize: memorySize, - freeMemory: freeMemory, - usableMemory: usableMemory, - storageSize: storageSize, - freeStorage: freeStorage, - externalStorageSize: externalStorageSize, - externalFreeStorage: externalFreeStorage, - bootTime: bootTime, - timezone: timezone, - ); + name: name, + family: family, + model: model, + modelId: modelId, + arch: arch, + batteryLevel: batteryLevel, + orientation: orientation, + manufacturer: manufacturer, + brand: brand, + screenResolution: screenResolution, + screenHeightPixels: screenHeightPixels, + screenWidthPixels: screenWidthPixels, + screenDensity: screenDensity, + screenDpi: screenDpi, + online: online, + charging: charging, + lowMemory: lowMemory, + simulator: simulator, + memorySize: memorySize, + freeMemory: freeMemory, + usableMemory: usableMemory, + storageSize: storageSize, + freeStorage: freeStorage, + externalStorageSize: externalStorageSize, + externalFreeStorage: externalFreeStorage, + bootTime: bootTime, + timezone: timezone, + language: language); SentryDevice copyWith({ String? name, diff --git a/dart/lib/src/protocol/sentry_event.dart b/dart/lib/src/protocol/sentry_event.dart index 7472ce1950..c7bf163415 100644 --- a/dart/lib/src/protocol/sentry_event.dart +++ b/dart/lib/src/protocol/sentry_event.dart @@ -226,6 +226,88 @@ class SentryEvent { debugMeta: debugMeta ?? this.debugMeta, ); + /// Deserializes a [SentryEvent] from JSON [Map]. + factory SentryEvent.fromJson(Map json) { + final breadcrumbsJson = json['breadcrumbs'] as List?; + final breadcrumbs = breadcrumbsJson?.map((e) { + return Breadcrumb.fromJson(e); + }).toList(); + + final stackTraceValuesJson = json['threads']?['values']; + Map? stackTraceValuesStacktraceJson; + if (stackTraceValuesJson?.isNotEmpty == true) { + stackTraceValuesStacktraceJson = {}; + stackTraceValuesStacktraceJson = + stackTraceValuesJson?.first['stacktrace']; + } + + final exceptionValuesJson = json['exception']?['values']; + Map? exceptionValuesItemJson; + if (exceptionValuesJson?.isNotEmpty == true) { + exceptionValuesItemJson = {}; + exceptionValuesItemJson = exceptionValuesJson?.first; + } + + final modules = json['modules']?.cast(); + final tags = json['tags']?.cast(); + + final timestampJson = json['timestamp']; + final levelJson = json['level']; + final fingerprintJson = json['fingerprint'] as List?; + final sdkVersionJson = json['sdk'] as Map?; + final messageJson = json['message'] as Map?; + final userJson = json['user'] as Map?; + final contextsJson = json['contexts'] as Map?; + final requestJson = json['request'] as Map?; + final debugMetaJson = json['debug_meta'] as Map?; + + return SentryEvent( + eventId: SentryId.fromId(json['event_id']), + timestamp: + timestampJson != null ? DateTime.tryParse(timestampJson) : null, + modules: modules, + tags: tags, + extra: json['extra'], + fingerprint: fingerprintJson?.map((e) => e as String).toList(), + breadcrumbs: breadcrumbs, + sdk: sdkVersionJson != null && sdkVersionJson.isNotEmpty + ? SdkVersion.fromJson(sdkVersionJson) + : null, + platform: json['platform'], + logger: json['logger'], + serverName: json['server_name'], + release: json['release'], + dist: json['dist'], + environment: json['environment'], + message: messageJson != null && messageJson.isNotEmpty + ? SentryMessage.fromJson(messageJson) + : null, + transaction: json['transaction'], + stackTrace: stackTraceValuesStacktraceJson != null && + stackTraceValuesStacktraceJson.isNotEmpty + ? SentryStackTrace.fromJson(stackTraceValuesStacktraceJson) + : null, + exception: + exceptionValuesItemJson != null && exceptionValuesItemJson.isNotEmpty + ? SentryException.fromJson(exceptionValuesItemJson) + : null, + level: levelJson != null ? SentryLevel.fromName(levelJson) : null, + culprit: json['culprit'], + user: userJson != null && userJson.isNotEmpty + ? SentryUser.fromJson(userJson) + : null, + contexts: contextsJson != null && contextsJson.isNotEmpty + ? Contexts.fromJson(contextsJson) + : null, + request: requestJson != null && requestJson.isNotEmpty + ? SentryRequest.fromJson(requestJson) + : null, + debugMeta: debugMetaJson != null && debugMetaJson.isNotEmpty + ? DebugMeta.fromJson(debugMetaJson) + : null, + ); + } + /// Serializes this event to JSON. Map toJson() { final json = {}; diff --git a/dart/lib/src/protocol/sentry_exception.dart b/dart/lib/src/protocol/sentry_exception.dart index c4d2558240..8359bfd1a3 100644 --- a/dart/lib/src/protocol/sentry_exception.dart +++ b/dart/lib/src/protocol/sentry_exception.dart @@ -32,6 +32,24 @@ class SentryException { this.threadId, }); + /// Deserializes a [SentryException] from JSON [Map]. + factory SentryException.fromJson(Map json) { + final stackTraceJson = json['stacktrace']; + final mechanismJson = json['mechanism']; + return SentryException( + type: json['type'], + value: json['value'], + module: json['module'], + stackTrace: stackTraceJson != null + ? SentryStackTrace.fromJson(stackTraceJson) + : null, + mechanism: + mechanismJson != null ? Mechanism.fromJson(mechanismJson) : null, + threadId: json['thread_id'], + ); + } + + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; diff --git a/dart/lib/src/protocol/sentry_gpu.dart b/dart/lib/src/protocol/sentry_gpu.dart index bba5cfe07b..6696f7b4f7 100644 --- a/dart/lib/src/protocol/sentry_gpu.dart +++ b/dart/lib/src/protocol/sentry_gpu.dart @@ -56,6 +56,7 @@ class SentryGpu { this.npotSupport, }); + /// Deserializes a [SentryGpu] from JSON [Map]. factory SentryGpu.fromJson(Map data) => SentryGpu( name: data['name'], id: data['id'], diff --git a/dart/lib/src/protocol/sentry_level.dart b/dart/lib/src/protocol/sentry_level.dart index a2524ce1ac..264a7c43ab 100644 --- a/dart/lib/src/protocol/sentry_level.dart +++ b/dart/lib/src/protocol/sentry_level.dart @@ -15,6 +15,20 @@ class SentryLevel { final String name; final int ordinal; + factory SentryLevel.fromName(String name) { + switch (name) { + case 'fatal': + return SentryLevel.fatal; + case 'error': + return SentryLevel.error; + case 'warning': + return SentryLevel.warning; + case 'info': + return SentryLevel.info; + } + return SentryLevel.debug; + } + /// For use with Dart's /// [`log`](https://api.dart.dev/stable/2.12.4/dart-developer/log.html) /// function. diff --git a/dart/lib/src/protocol/sentry_message.dart b/dart/lib/src/protocol/sentry_message.dart index 3842e35b1d..e234de5584 100644 --- a/dart/lib/src/protocol/sentry_message.dart +++ b/dart/lib/src/protocol/sentry_message.dart @@ -22,6 +22,16 @@ class SentryMessage { const SentryMessage(this.formatted, {this.template, this.params}); + /// Deserializes a [SentryMessage] from JSON [Map]. + factory SentryMessage.fromJson(Map json) { + return SentryMessage( + json['formatted'], + template: json['message'], + params: json['params'], + ); + } + + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; diff --git a/dart/lib/src/protocol/sentry_operating_system.dart b/dart/lib/src/protocol/sentry_operating_system.dart index 60d7ce6848..4b6376a405 100644 --- a/dart/lib/src/protocol/sentry_operating_system.dart +++ b/dart/lib/src/protocol/sentry_operating_system.dart @@ -17,16 +17,6 @@ class SentryOperatingSystem { this.rawDescription, }); - factory SentryOperatingSystem.fromJson(Map data) => - SentryOperatingSystem( - name: data['name'], - version: data['version'], - build: data['build'], - kernelVersion: data['kernel_version'], - rooted: data['rooted'], - rawDescription: data['raw_description'], - ); - /// The name of the operating system. final String? name; @@ -50,6 +40,17 @@ class SentryOperatingSystem { /// version from this string, if they are not explicitly given. final String? rawDescription; + /// Deserializes a [SentryOperatingSystem] from JSON [Map]. + factory SentryOperatingSystem.fromJson(Map data) => + SentryOperatingSystem( + name: data['name'], + version: data['version'], + build: data['build'], + kernelVersion: data['kernel_version'], + rooted: data['rooted'], + rawDescription: data['raw_description'], + ); + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; diff --git a/dart/lib/src/protocol/sentry_package.dart b/dart/lib/src/protocol/sentry_package.dart index 57e5e4a9f4..51fad73377 100644 --- a/dart/lib/src/protocol/sentry_package.dart +++ b/dart/lib/src/protocol/sentry_package.dart @@ -12,6 +12,14 @@ class SentryPackage { /// The version of the SDK. final String version; + /// Deserializes a [SentryPackage] from JSON [Map]. + factory SentryPackage.fromJson(Map json) { + return SentryPackage( + json['name'], + json['version'], + ); + } + /// Produces a [Map] that can be serialized to JSON. Map toJson() { return { diff --git a/dart/lib/src/protocol/sentry_request.dart b/dart/lib/src/protocol/sentry_request.dart index acaef3370e..7c326df727 100644 --- a/dart/lib/src/protocol/sentry_request.dart +++ b/dart/lib/src/protocol/sentry_request.dart @@ -69,6 +69,21 @@ class SentryRequest { _env = env != null ? Map.from(env) : null, _other = other != null ? Map.from(other) : null; + /// Deserializes a [SentryRequest] from JSON [Map]. + factory SentryRequest.fromJson(Map json) { + return SentryRequest( + url: json['url'], + method: json['method'], + queryString: json['query_string'], + cookies: json['cookies'], + data: json['data'], + headers: json['headers'], + env: json['env'], + other: json['other'], + ); + } + + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; diff --git a/dart/lib/src/protocol/sentry_runtime.dart b/dart/lib/src/protocol/sentry_runtime.dart index 54bf50a266..6bba34ff8c 100644 --- a/dart/lib/src/protocol/sentry_runtime.dart +++ b/dart/lib/src/protocol/sentry_runtime.dart @@ -13,12 +13,6 @@ class SentryRuntime { const SentryRuntime({this.key, this.name, this.version, this.rawDescription}) : assert(key == null || key.length >= 1); - factory SentryRuntime.fromJson(Map data) => SentryRuntime( - name: data['name'], - version: data['version'], - rawDescription: data['raw_description'], - ); - /// Key used in the JSON and which will be displayed /// in the Sentry UI. Defaults to lower case version of [name]. /// @@ -37,6 +31,13 @@ class SentryRuntime { /// and version from this string, if they are not explicitly given. final String? rawDescription; + /// Deserializes a [SentryRuntime] from JSON [Map]. + factory SentryRuntime.fromJson(Map data) => SentryRuntime( + name: data['name'], + version: data['version'], + rawDescription: data['raw_description'], + ); + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; diff --git a/dart/lib/src/protocol/sentry_stack_frame.dart b/dart/lib/src/protocol/sentry_stack_frame.dart index 685cac4df2..27085f8522 100644 --- a/dart/lib/src/protocol/sentry_stack_frame.dart +++ b/dart/lib/src/protocol/sentry_stack_frame.dart @@ -108,6 +108,32 @@ class SentryStackFrame { /// The original function name, if the function name is shortened or demangled. Sentry shows the raw function when clicking on the shortened one in the UI. final String? rawFunction; + /// Deserializes a [SentryStackFrame] from JSON [Map]. + factory SentryStackFrame.fromJson(Map json) { + return SentryStackFrame( + absPath: json['abs_path'], + fileName: json['filename'], + function: json['function'], + module: json['module'], + lineNo: json['lineno'], + colNo: json['colno'], + contextLine: json['context_line'], + inApp: json['in_app'], + package: json['package'], + native: json['native'], + platform: json['platform'], + imageAddr: json['image_addr'], + symbolAddr: json['symbol_addr'], + instructionAddr: json['instruction_addr'], + rawFunction: json['raw_function'], + framesOmitted: json['frames_omitted'], + preContext: json['pre_context'], + postContext: json['post_context'], + vars: json['vars'], + ); + } + + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; @@ -163,10 +189,6 @@ class SentryStackFrame { json['in_app'] = inApp; } - if (package != null) { - json['package'] = package; - } - if (native != null) { json['native'] = native; } diff --git a/dart/lib/src/protocol/sentry_stack_trace.dart b/dart/lib/src/protocol/sentry_stack_trace.dart index 112ecd81c4..abb74fdcca 100644 --- a/dart/lib/src/protocol/sentry_stack_trace.dart +++ b/dart/lib/src/protocol/sentry_stack_trace.dart @@ -25,6 +25,20 @@ class SentryStackTrace { /// thus mapping to the last frame in the list. Map get registers => Map.unmodifiable(_registers ?? const {}); + /// Deserializes a [SentryStackTrace] from JSON [Map]. + factory SentryStackTrace.fromJson(Map json) { + final framesJson = json['frames'] as List?; + return SentryStackTrace( + frames: framesJson != null + ? framesJson + .map((frameJson) => SentryStackFrame.fromJson(frameJson)) + .toList() + : [], + registers: json['registers'], + ); + } + + /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; diff --git a/dart/lib/src/protocol/sentry_user.dart b/dart/lib/src/protocol/sentry_user.dart index 34ff14080d..54d363e0b7 100644 --- a/dart/lib/src/protocol/sentry_user.dart +++ b/dart/lib/src/protocol/sentry_user.dart @@ -53,6 +53,17 @@ class SentryUser { /// by Sentry. final Map? extras; + /// Deserializes a [SentryUser] from JSON [Map]. + factory SentryUser.fromJson(Map json) { + return SentryUser( + id: json['id'], + username: json['username'], + email: json['email'], + ipAddress: json['ip_address'], + extras: json['extras'], + ); + } + /// Produces a [Map] that can be serialized to JSON. Map toJson() { return { diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 5b6ef9ef99..b193ffec9b 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -106,7 +106,8 @@ class SentryClient { return _sentryId; } } - return await _options.transport.sendSentryEvent(preparedEvent); + final envelope = SentryEnvelope.fromEvent(preparedEvent, _options.sdk); + return await _options.transport.send(envelope); } SentryEvent _prepareEvent(SentryEvent event, {dynamic stackTrace}) { @@ -199,7 +200,7 @@ class SentryClient { /// Reports the [envelope] to Sentry.io. Future captureEnvelope(SentryEnvelope envelope) { - return _options.transport.sendSentryEnvelope(envelope); + return _options.transport.send(envelope); } void close() => _options.httpClient.close(); diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index bf775df479..18a1835231 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -21,7 +21,7 @@ class SentryEnvelope { [SentryEnvelopeItem.fromEvent(event)]); } - /// Stream binary data representation of `Envelope` file encoded in utf8. + /// Stream binary data representation of `Envelope` file encoded. Stream> envelopeStream() async* { yield utf8.encode(jsonEncode(header.toJson())); final newLineData = utf8.encode('\n'); diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index 24930be25f..5c00a4e1e6 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -11,7 +11,7 @@ class SentryEnvelopeItem { /// Header with info about type and length of data in bytes. final SentryEnvelopeItemHeader header; - /// Create binary data representation of item JSON encoded in utf8. + /// Create binary data representation of item data. final Future> Function() dataFactory; /// Create an `SentryEnvelopeItem` which holds the `SentyEvent` data. @@ -31,7 +31,7 @@ class SentryEnvelopeItem { cachedItem.getData); } - /// Stream binary data of `Envelope` item encoded in utf8. + /// Stream binary data of `Envelope` item. Stream> envelopeItemStream() async* { yield utf8.encode(jsonEncode(await header.toJson())); yield utf8.encode('\n'); diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart index 99a4580427..013a62d35b 100644 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -19,6 +19,9 @@ class SentryEnvelopeItemHeader { if (contentType != null) { json['content_type'] = contentType; } + if (fileName != null) { + json['filename'] = fileName; + } json['type'] = type; json['length'] = await length(); return json; diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index 778d1d4252..3a93da51f1 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -45,13 +45,7 @@ class HttpTransport implements Transport { } @override - Future sendSentryEvent(SentryEvent event) async { - final envelope = SentryEnvelope.fromEvent(event, _options.sdk); - return await sendSentryEnvelope(envelope); - } - - @override - Future sendSentryEnvelope(SentryEnvelope envelope) async { + Future send(SentryEnvelope envelope) async { final filteredEnvelope = _rateLimiter.filter(envelope); if (filteredEnvelope == null) { return SentryId.empty(); diff --git a/dart/lib/src/transport/noop_transport.dart b/dart/lib/src/transport/noop_transport.dart index 0e36b14d0d..705dfc3a9c 100644 --- a/dart/lib/src/transport/noop_transport.dart +++ b/dart/lib/src/transport/noop_transport.dart @@ -7,10 +7,6 @@ import 'transport.dart'; class NoOpTransport implements Transport { @override - Future sendSentryEvent(SentryEvent event) => - Future.value(SentryId.empty()); - - @override - Future sendSentryEnvelope(SentryEnvelope envelope) => + Future send(SentryEnvelope envelope) => Future.value(SentryId.empty()); } diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index ca1e05cfd9..08de9e9322 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -6,6 +6,5 @@ import '../protocol.dart'; /// A transport is in charge of sending the event/envelope either via http /// or caching in the disk. abstract class Transport { - Future sendSentryEvent(SentryEvent event); - Future sendSentryEnvelope(SentryEnvelope envelope); + Future send(SentryEnvelope envelope); } diff --git a/dart/test/contexts_test.dart b/dart/test/contexts_test.dart index 5647f03144..5bd97189d0 100644 --- a/dart/test/contexts_test.dart +++ b/dart/test/contexts_test.dart @@ -61,50 +61,59 @@ void main() { ..['theme'] = {'value': 'material'} ..['version'] = {'value': 9}; + final contextsJson = { + 'device': { + 'name': 'testDevice', + 'family': 'testFamily', + 'model': 'testModel', + 'model_id': 'testModelId', + 'arch': 'testArch', + 'battery_level': 23.0, + 'orientation': 'landscape', + 'manufacturer': 'testOEM', + 'brand': 'testBrand', + 'screen_resolution': '123x345', + 'screen_density': 99.1, + 'screen_dpi': 100, + 'online': false, + 'charging': true, + 'low_memory': false, + 'simulator': true, + 'memory_size': 1234567, + 'free_memory': 12345, + 'usable_memory': 9876, + 'storage_size': 1234567, + 'free_storage': 1234567, + 'external_storage_size': 98765, + 'external_free_storage': 98765, + 'boot_time': testBootTime.toIso8601String(), + 'timezone': 'Australia/Melbourne', + }, + 'os': { + 'name': 'testOS', + }, + 'app': {'app_version': '1.2.3'}, + 'browser': {'version': '12.3.4'}, + 'gpu': {'name': 'Radeon', 'version': '1'}, + 'testrt1': {'name': 'testRT1', 'type': 'runtime', 'version': '1.0'}, + 'testrt2': {'name': 'testRT2', 'type': 'runtime', 'version': '2.3.1'}, + 'theme': {'value': 'material'}, + 'version': {'value': 9}, + }; + test('serializes to JSON', () { final event = SentryEvent(contexts: contexts); + expect(event.toJson()['contexts'], contextsJson); + }); + + test('deserializes/serializes JSON', () { + final contexts = Contexts.fromJson(contextsJson); + final json = contexts.toJson(); + expect( - event.toJson()['contexts'], - { - 'device': { - 'name': 'testDevice', - 'family': 'testFamily', - 'model': 'testModel', - 'model_id': 'testModelId', - 'arch': 'testArch', - 'battery_level': 23, - 'orientation': 'landscape', - 'manufacturer': 'testOEM', - 'brand': 'testBrand', - 'screen_resolution': '123x345', - 'screen_density': 99.1, - 'screen_dpi': 100, - 'online': false, - 'charging': true, - 'low_memory': false, - 'simulator': true, - 'memory_size': 1234567, - 'free_memory': 12345, - 'usable_memory': 9876, - 'storage_size': 1234567, - 'free_storage': 1234567, - 'external_storage_size': 98765, - 'external_free_storage': 98765, - 'boot_time': testBootTime.toIso8601String(), - 'timezone': 'Australia/Melbourne', - }, - 'os': { - 'name': 'testOS', - }, - 'testrt1': {'name': 'testRT1', 'type': 'runtime', 'version': '1.0'}, - 'testrt2': {'name': 'testRT2', 'type': 'runtime', 'version': '2.3.1'}, - 'app': {'app_version': '1.2.3'}, - 'browser': {'version': '12.3.4'}, - 'gpu': {'name': 'Radeon', 'version': '1'}, - 'theme': {'value': 'material'}, - 'version': {'value': 9}, - }, + DeepCollectionEquality().equals(contextsJson, json), + true, ); }); diff --git a/dart/test/mocks/mock_transport.dart b/dart/test/mocks/mock_transport.dart index cb5d4f43f7..64ed6902c5 100644 --- a/dart/test/mocks/mock_transport.dart +++ b/dart/test/mocks/mock_transport.dart @@ -1,21 +1,14 @@ import 'package:sentry/sentry.dart'; class MockTransport implements Transport { - List events = []; List envelopes = []; bool called(int calls) { - return events.length == calls; + return envelopes.length == calls; } @override - Future sendSentryEvent(SentryEvent event) async { - events.add(event); - return event.eventId; - } - - @override - Future sendSentryEnvelope(SentryEnvelope envelope) async { + Future send(SentryEnvelope envelope) async { envelopes.add(envelope); return envelope.header.eventId ?? SentryId.empty(); } diff --git a/dart/test/protocol/app_test.dart b/dart/test/protocol/app_test.dart deleted file mode 100644 index fe2abb0957..0000000000 --- a/dart/test/protocol/app_test.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); - - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = _generate(); - - final startTime = DateTime.now(); - - final copy = data.copyWith( - name: 'name1', - version: 'version1', - identifier: 'identifier1', - build: 'build1', - buildType: 'buildType1', - startTime: startTime, - deviceAppHash: 'hash1', - ); - - expect('name1', copy.name); - expect('version1', copy.version); - expect('identifier1', copy.identifier); - expect('build1', copy.build); - expect('buildType1', copy.buildType); - expect(startTime, copy.startTime); - expect('hash1', copy.deviceAppHash); - }); -} - -SentryApp _generate({DateTime? startTime}) => SentryApp( - name: 'name', - version: 'version', - identifier: 'identifier', - build: 'build', - buildType: 'buildType', - startTime: startTime ?? DateTime.now(), - deviceAppHash: 'hash', - ); diff --git a/dart/test/protocol/breadcrumb_test.dart b/dart/test/protocol/breadcrumb_test.dart index 2c0f6a97a7..e34b8d2553 100644 --- a/dart/test/protocol/breadcrumb_test.dart +++ b/dart/test/protocol/breadcrumb_test.dart @@ -1,47 +1,80 @@ import 'package:collection/collection.dart'; import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; +import 'package:sentry/src/utils.dart'; void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); + final timestamp = DateTime.now(); - final copy = data.copyWith(); + final breadcrumb = Breadcrumb( + message: 'message', + timestamp: timestamp, + data: {'key': 'value'}, + level: SentryLevel.warning, + category: 'category', + type: 'type', + ); - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); + final breadcrumbJson = { + 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), + 'message': 'message', + 'category': 'category', + 'data': {'key': 'value'}, + 'level': 'warning', + 'type': 'type', + }; + + group('json', () { + test('toJson', () { + final json = breadcrumb.toJson(); + + expect( + DeepCollectionEquality().equals(breadcrumbJson, json), + true, + ); + }); + test('fromJson', () { + final breadcrumb = Breadcrumb.fromJson(breadcrumbJson); + final json = breadcrumb.toJson(); + + expect( + DeepCollectionEquality().equals(breadcrumbJson, json), + true, + ); + }); }); - test('copyWith takes new values', () { - final data = _generate(); - - final timestamp = DateTime.now(); - - final copy = data.copyWith( - message: 'message1', - timestamp: timestamp, - data: {'key1': 'value1'}, - level: SentryLevel.fatal, - category: 'category1', - type: 'type1', - ); - - expect('message1', copy.message); - expect(timestamp, copy.timestamp); - expect({'key1': 'value1'}, copy.data); - expect(SentryLevel.fatal, copy.level); - expect('category1', copy.category); - expect('type1', copy.type); + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = breadcrumb; + + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + test('copyWith takes new values', () { + final data = breadcrumb; + + final timestamp = DateTime.now(); + + final copy = data.copyWith( + message: 'message1', + timestamp: timestamp, + data: {'key1': 'value1'}, + level: SentryLevel.fatal, + category: 'category1', + type: 'type1', + ); + + expect('message1', copy.message); + expect(timestamp, copy.timestamp); + expect({'key1': 'value1'}, copy.data); + expect(SentryLevel.fatal, copy.level); + expect('category1', copy.category); + expect('type1', copy.type); + }); }); } - -Breadcrumb _generate({DateTime? timestamp}) => Breadcrumb( - message: 'message', - timestamp: timestamp ?? DateTime.now(), - data: {'key': 'value'}, - level: SentryLevel.warning, - category: 'category', - type: 'type', - ); diff --git a/dart/test/protocol/browser_test.dart b/dart/test/protocol/browser_test.dart deleted file mode 100644 index d3123fb638..0000000000 --- a/dart/test/protocol/browser_test.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); - - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = _generate(); - - final copy = data.copyWith( - name: 'name1', - version: 'version1', - ); - - expect('name1', copy.name); - expect('version1', copy.version); - }); -} - -SentryBrowser _generate() => SentryBrowser( - name: 'name', - version: 'version', - ); diff --git a/dart/test/protocol/contexts_test.dart b/dart/test/protocol/contexts_test.dart index 4533b26879..74687860dd 100644 --- a/dart/test/protocol/contexts_test.dart +++ b/dart/test/protocol/contexts_test.dart @@ -3,53 +3,117 @@ import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); + final contexts = Contexts( + device: SentryDevice(batteryLevel: 90), + operatingSystem: SentryOperatingSystem(name: 'name'), + runtimes: [SentryRuntime(name: 'name')], + app: SentryApp(name: 'name'), + browser: SentryBrowser(name: 'name'), + gpu: SentryGpu(id: 1), + ); - final copy = data.copyWith(); + final contextsJson = { + 'device': {'battery_level': 90.0}, + 'os': {'name': 'name'}, + 'runtime': {'name': 'name'}, + 'app': {'app_name': 'name'}, + 'browser': {'name': 'name'}, + 'gpu': {'id': 1}, + }; - // MapEquality fails for some reason, it probably check the instances equality too - expect(data.toJson(), copy.toJson()); + final contextsMutlipleRuntimes = Contexts( + runtimes: [ + SentryRuntime(name: 'name'), + SentryRuntime(name: 'name'), + SentryRuntime(key: 'key') + ], + ); + + final contextsMutlipleRuntimesJson = { + 'name': {'name': 'name', 'type': 'runtime'}, + 'name0': {'name': 'name', 'type': 'runtime'}, + }; + + group('json', () { + test('toJson', () { + final json = contexts.toJson(); + + expect( + DeepCollectionEquality().equals(contextsJson, json), + true, + ); + }); + test('toJson multiple runtimes', () { + final json = contextsMutlipleRuntimes.toJson(); + + expect( + DeepCollectionEquality().equals(contextsMutlipleRuntimesJson, json), + true, + ); + }); + test('fromJson', () { + final contexts = Contexts.fromJson(contextsJson); + final json = contexts.toJson(); + + expect( + DeepCollectionEquality().equals(contextsJson, json), + true, + ); + }); + test('fromJson multiple runtimes', () { + final contextsMutlipleRuntimes = + Contexts.fromJson(contextsMutlipleRuntimesJson); + final json = contextsMutlipleRuntimes.toJson(); + + expect( + DeepCollectionEquality().equals(contextsMutlipleRuntimesJson, json), + true, + ); + }); }); - test('copyWith takes new values', () { - final data = _generate(); - data['extra'] = 'value'; - - final device = SentryDevice(batteryLevel: 100); - final os = SentryOperatingSystem(name: 'name1'); - final runtimes = [SentryRuntime(name: 'name1')]; - final app = SentryApp(name: 'name1'); - final browser = SentryBrowser(name: 'name1'); - final gpu = SentryGpu(id: 2); - - final copy = data.copyWith( - device: device, - operatingSystem: os, - runtimes: runtimes, - app: app, - browser: browser, - gpu: gpu, - ); - - expect(device.toJson(), copy.device!.toJson()); - expect(os.toJson(), copy.operatingSystem!.toJson()); - expect( - ListEquality().equals(runtimes, copy.runtimes), - true, - ); - expect(app.toJson(), copy.app!.toJson()); - expect(browser.toJson(), copy.browser!.toJson()); - expect(gpu.toJson(), copy.gpu!.toJson()); - expect('value', copy['extra']); + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = contexts; + + final copy = data.copyWith(); + + expect( + DeepCollectionEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = contexts; + data['extra'] = 'value'; + + final device = SentryDevice(batteryLevel: 100); + final os = SentryOperatingSystem(name: 'name1'); + final runtimes = [SentryRuntime(name: 'name1')]; + final app = SentryApp(name: 'name1'); + final browser = SentryBrowser(name: 'name1'); + final gpu = SentryGpu(id: 2); + + final copy = data.copyWith( + device: device, + operatingSystem: os, + runtimes: runtimes, + app: app, + browser: browser, + gpu: gpu, + ); + + expect(device.toJson(), copy.device!.toJson()); + expect(os.toJson(), copy.operatingSystem!.toJson()); + expect( + ListEquality().equals(runtimes, copy.runtimes), + true, + ); + expect(app.toJson(), copy.app!.toJson()); + expect(browser.toJson(), copy.browser!.toJson()); + expect(gpu.toJson(), copy.gpu!.toJson()); + expect('value', copy['extra']); + }); }); } - -Contexts _generate() => Contexts( - device: SentryDevice(batteryLevel: 90), - operatingSystem: SentryOperatingSystem(name: 'name'), - runtimes: [SentryRuntime(name: 'name')], - app: SentryApp(name: 'name'), - browser: SentryBrowser(name: 'name'), - gpu: SentryGpu(id: 1), - ); diff --git a/dart/test/protocol/debug_image_test.dart b/dart/test/protocol/debug_image_test.dart index a22b947b8d..427eb19b8a 100644 --- a/dart/test/protocol/debug_image_test.dart +++ b/dart/test/protocol/debug_image_test.dart @@ -3,52 +3,86 @@ import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); + final debugImage = DebugImage( + type: 'type', + imageAddr: 'imageAddr', + debugId: 'debugId', + debugFile: 'debugFile', + imageSize: 1, + uuid: 'uuid', + codeFile: 'codeFile', + arch: 'arch', + codeId: 'codeId', + ); - final copy = data.copyWith(); + final debugImageJson = { + 'uuid': 'uuid', + 'type': 'type', + 'debug_id': 'debugId', + 'debug_file': 'debugFile', + 'code_file': 'codeFile', + 'image_addr': 'imageAddr', + 'image_size': 1, + 'arch': 'arch', + 'code_id': 'codeId', + }; - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); + group('json', () { + test('toJson', () { + final json = debugImage.toJson(); + + expect( + MapEquality().equals(debugImageJson, json), + true, + ); + }); + test('fromJson', () { + final debugImage = DebugImage.fromJson(debugImageJson); + final json = debugImage.toJson(); + + expect( + MapEquality().equals(debugImageJson, json), + true, + ); + }); }); - test('copyWith takes new values', () { - final data = _generate(); - - final copy = data.copyWith( - type: 'type1', - imageAddr: 'imageAddr1', - debugId: 'debugId1', - debugFile: 'debugFile1', - imageSize: 2, - uuid: 'uuid1', - codeFile: 'codeFile1', - arch: 'arch1', - codeId: 'codeId1', - ); - - expect('type1', copy.type); - expect('imageAddr1', copy.imageAddr); - expect('debugId1', copy.debugId); - expect('debugFile1', copy.debugFile); - expect(2, copy.imageSize); - expect('uuid1', copy.uuid); - expect('codeFile1', copy.codeFile); - expect('arch1', copy.arch); - expect('codeId1', copy.codeId); + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = debugImage; + + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = debugImage; + + final copy = data.copyWith( + type: 'type1', + imageAddr: 'imageAddr1', + debugId: 'debugId1', + debugFile: 'debugFile1', + imageSize: 2, + uuid: 'uuid1', + codeFile: 'codeFile1', + arch: 'arch1', + codeId: 'codeId1', + ); + + expect('type1', copy.type); + expect('imageAddr1', copy.imageAddr); + expect('debugId1', copy.debugId); + expect('debugFile1', copy.debugFile); + expect(2, copy.imageSize); + expect('uuid1', copy.uuid); + expect('codeFile1', copy.codeFile); + expect('arch1', copy.arch); + expect('codeId1', copy.codeId); + }); }); } - -DebugImage _generate() => DebugImage( - type: 'type', - imageAddr: 'imageAddr', - debugId: 'debugId', - debugFile: 'debugFile', - imageSize: 1, - uuid: 'uuid', - codeFile: 'codeFile', - arch: 'arch', - codeId: 'codeId', - ); diff --git a/dart/test/protocol/debug_meta_test.dart b/dart/test/protocol/debug_meta_test.dart index b611bc9828..21caf02747 100644 --- a/dart/test/protocol/debug_meta_test.dart +++ b/dart/test/protocol/debug_meta_test.dart @@ -3,42 +3,70 @@ import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); + final debugMeta = DebugMeta( + sdk: SdkInfo( + sdkName: 'sdkName', + ), + images: [DebugImage(type: 'macho', uuid: 'uuid')], + ); - final copy = data.copyWith(); + final debugMetaJson = { + 'sdk_info': {'sdk_name': 'sdkName'}, + 'images': [ + {'uuid': 'uuid', 'type': 'macho'} + ] + }; - // MapEquality fails for some reason, it probably check the instances equality too - expect(data.toJson(), copy.toJson()); + group('json', () { + test('toJson', () { + final json = debugMeta.toJson(); + + expect( + DeepCollectionEquality().equals(debugMetaJson, json), + true, + ); + }); + test('fromJson', () { + final debugMeta = DebugMeta.fromJson(debugMetaJson); + final json = debugMeta.toJson(); + + expect( + DeepCollectionEquality().equals(debugMetaJson, json), + true, + ); + }); }); - test('copyWith takes new values', () { - final data = _generate(); - - final newSdkInfo = SdkInfo( - sdkName: 'sdkName1', - ); - final newImageList = [DebugImage(type: 'macho', uuid: 'uuid1')]; - - final copy = data.copyWith( - sdk: newSdkInfo, - images: newImageList, - ); - - expect( - ListEquality().equals(newImageList, copy.images), - true, - ); - expect( - MapEquality().equals(newSdkInfo.toJson(), copy.sdk!.toJson()), - true, - ); + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = debugMeta; + + final copy = data.copyWith(); + + // MapEquality fails for some reason, it probably check the instances equality too + expect(data.toJson(), copy.toJson()); + }); + test('copyWith takes new values', () { + final data = debugMeta; + + final newSdkInfo = SdkInfo( + sdkName: 'sdkName1', + ); + final newImageList = [DebugImage(type: 'macho', uuid: 'uuid1')]; + + final copy = data.copyWith( + sdk: newSdkInfo, + images: newImageList, + ); + + expect( + ListEquality().equals(newImageList, copy.images), + true, + ); + expect( + MapEquality().equals(newSdkInfo.toJson(), copy.sdk!.toJson()), + true, + ); + }); }); } - -DebugMeta _generate() => DebugMeta( - sdk: SdkInfo( - sdkName: 'sdkName', - ), - images: [DebugImage(type: 'macho', uuid: 'uuid')], - ); diff --git a/dart/test/protocol/gpu_test.dart b/dart/test/protocol/gpu_test.dart deleted file mode 100644 index 872632e4c0..0000000000 --- a/dart/test/protocol/gpu_test.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); - - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = _generate(); - - final copy = data.copyWith( - name: 'name1', - id: 11, - vendorId: 22, - vendorName: 'vendorName1', - memorySize: 33, - apiType: 'apiType1', - multiThreadedRendering: false, - version: 'version1', - npotSupport: 'npotSupport1', - ); - - expect('name1', copy.name); - expect(11, copy.id); - expect(22, copy.vendorId); - expect('vendorName1', copy.vendorName); - expect(33, copy.memorySize); - expect('apiType1', copy.apiType); - expect(false, copy.multiThreadedRendering); - expect('version1', copy.version); - expect('npotSupport1', copy.npotSupport); - }); -} - -SentryGpu _generate() => SentryGpu( - name: 'name', - id: 1, - vendorId: 2, - vendorName: 'vendorName', - memorySize: 3, - apiType: 'apiType', - multiThreadedRendering: true, - version: 'version', - npotSupport: 'npotSupport', - ); diff --git a/dart/test/protocol/mechanism_test.dart b/dart/test/protocol/mechanism_test.dart index dc940dfc47..4b484d2ca3 100644 --- a/dart/test/protocol/mechanism_test.dart +++ b/dart/test/protocol/mechanism_test.dart @@ -1,44 +1,76 @@ +import 'package:collection/collection.dart'; import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); + final mechanism = Mechanism( + type: 'type', + description: 'description', + helpLink: 'helpLink', + handled: true, + synthetic: true, + meta: {'key': 'value'}, + data: {'keyb': 'valueb'}, + ); - final copy = data.copyWith(); + final mechanismJson = { + 'type': 'type', + 'description': 'description', + 'help_link': 'helpLink', + 'handled': true, + 'meta': {'key': 'value'}, + 'data': {'keyb': 'valueb'}, + 'synthetic': true, + }; - expect(data.toJson(), copy.toJson()); + group('json', () { + test('toJson', () { + final json = mechanism.toJson(); + + expect( + DeepCollectionEquality().equals(mechanismJson, json), + true, + ); + }); + test('fromJson', () { + final mechanism = Mechanism.fromJson(mechanismJson); + final json = mechanism.toJson(); + + expect( + DeepCollectionEquality().equals(mechanismJson, json), + true, + ); + }); }); - test('copyWith takes new values', () { - final data = _generate(); - - final copy = data.copyWith( - type: 'type1', - description: 'description1', - helpLink: 'helpLink1', - handled: false, - synthetic: false, - meta: {'key1': 'value1'}, - data: {'keyb1': 'valueb1'}, - ); - - expect('type1', copy.type); - expect('description1', copy.description); - expect('helpLink1', copy.helpLink); - expect(false, copy.handled); - expect(false, copy.synthetic); - expect({'key1': 'value1'}, copy.meta); - expect({'keyb1': 'valueb1'}, copy.data); + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = mechanism; + + final copy = data.copyWith(); + + expect(data.toJson(), copy.toJson()); + }); + test('copyWith takes new values', () { + final data = mechanism; + + final copy = data.copyWith( + type: 'type1', + description: 'description1', + helpLink: 'helpLink1', + handled: false, + synthetic: false, + meta: {'key1': 'value1'}, + data: {'keyb1': 'valueb1'}, + ); + + expect('type1', copy.type); + expect('description1', copy.description); + expect('helpLink1', copy.helpLink); + expect(false, copy.handled); + expect(false, copy.synthetic); + expect({'key1': 'value1'}, copy.meta); + expect({'keyb1': 'valueb1'}, copy.data); + }); }); } - -Mechanism _generate() => Mechanism( - type: 'type', - description: 'description', - helpLink: 'helpLink', - handled: true, - synthetic: true, - meta: {'key': 'value'}, - data: {'keyb': 'valueb'}, - ); diff --git a/dart/test/protocol/message_test.dart b/dart/test/protocol/message_test.dart deleted file mode 100644 index 7705b82673..0000000000 --- a/dart/test/protocol/message_test.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); - - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = _generate(); - - final copy = data.copyWith( - formatted: 'message 21', - template: 'message 2 %d', - params: ['2'], - ); - - expect('message 21', copy.formatted); - expect('message 2 %d', copy.template); - expect(['2'], copy.params); - }); -} - -SentryMessage _generate() => SentryMessage( - 'message 1', - template: 'message %d', - params: ['1'], - ); diff --git a/dart/test/protocol/operating_system_test.dart b/dart/test/protocol/operating_system_test.dart deleted file mode 100644 index 33d51d5df1..0000000000 --- a/dart/test/protocol/operating_system_test.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); - - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = _generate(); - - final copy = data.copyWith( - name: 'name1', - version: 'version1', - build: 'build1', - kernelVersion: 'kernelVersion1', - rooted: true, - ); - - expect('name1', copy.name); - expect('version1', copy.version); - expect('build1', copy.build); - expect('kernelVersion1', copy.kernelVersion); - expect(true, copy.rooted); - }); -} - -SentryOperatingSystem _generate() => SentryOperatingSystem( - name: 'name', - version: 'version', - build: 'build', - kernelVersion: 'kernelVersion', - rooted: false, - ); diff --git a/dart/test/protocol/request_test.dart b/dart/test/protocol/request_test.dart deleted file mode 100644 index ef5e68fa45..0000000000 --- a/dart/test/protocol/request_test.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); - - final copy = data.copyWith(); - - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); - }); - - test('copyWith takes new values', () { - final data = _generate(); - - final copy = data.copyWith( - url: 'url1', - method: 'method1', - queryString: 'queryString1', - cookies: 'cookies1', - data: {'key1': 'value1'}, - ); - - expect('url1', copy.url); - expect('method1', copy.method); - expect('queryString1', copy.queryString); - expect('cookies1', copy.cookies); - expect({'key1': 'value1'}, copy.data); - }); -} - -SentryRequest _generate() => SentryRequest( - url: 'url', - method: 'method', - queryString: 'queryString', - cookies: 'cookies', - data: {'key': 'value'}, - ); diff --git a/dart/test/protocol/sdk_info_test.dart b/dart/test/protocol/sdk_info_test.dart index bee6f36ff8..50e3c3fdcd 100644 --- a/dart/test/protocol/sdk_info_test.dart +++ b/dart/test/protocol/sdk_info_test.dart @@ -3,37 +3,65 @@ import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); + final sdkInfo = SdkInfo( + sdkName: 'sdkName', + versionMajor: 1, + versionMinor: 2, + versionPatchlevel: 3, + ); - final copy = data.copyWith(); + final sdkInfoJson = { + 'sdk_name': 'sdkName', + 'version_major': 1, + 'version_minor': 2, + 'version_patchlevel': 3, + }; - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); + group('json', () { + test('toJson', () { + final json = sdkInfo.toJson(); + + expect( + MapEquality().equals(sdkInfoJson, json), + true, + ); + }); + test('fromJson', () { + final sdkInfo = SdkInfo.fromJson(sdkInfoJson); + final json = sdkInfo.toJson(); + + expect( + MapEquality().equals(sdkInfoJson, json), + true, + ); + }); }); - test('copyWith takes new values', () { - final data = _generate(); + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sdkInfo; + + final copy = data.copyWith(); - final copy = data.copyWith( - sdkName: 'sdkName1', - versionMajor: 11, - versionMinor: 22, - versionPatchlevel: 33, - ); + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + test('copyWith takes new values', () { + final data = sdkInfo; - expect('sdkName1', copy.sdkName); - expect(11, copy.versionMajor); - expect(22, copy.versionMinor); - expect(33, copy.versionPatchlevel); + final copy = data.copyWith( + sdkName: 'sdkName1', + versionMajor: 11, + versionMinor: 22, + versionPatchlevel: 33, + ); + + expect('sdkName1', copy.sdkName); + expect(11, copy.versionMajor); + expect(22, copy.versionMinor); + expect(33, copy.versionPatchlevel); + }); }); } - -SdkInfo _generate() => SdkInfo( - sdkName: 'sdkName', - versionMajor: 1, - versionMinor: 2, - versionPatchlevel: 3, - ); diff --git a/dart/test/protocol/sdk_version_test.dart b/dart/test/protocol/sdk_version_test.dart index 35d6afcdfc..218ca72410 100644 --- a/dart/test/protocol/sdk_version_test.dart +++ b/dart/test/protocol/sdk_version_test.dart @@ -3,43 +3,77 @@ import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); + final sdkVersion = SdkVersion( + name: 'name', + version: 'version', + integrations: ['test'], + packages: [SentryPackage('name', 'version')], + ); - final copy = data.copyWith(); + final sdkVersionJson = { + 'name': 'name', + 'version': 'version', + 'integrations': ['test'], + 'packages': [ + { + 'name': 'name', + 'version': 'version', + } + ], + }; - expect(data.toJson(), copy.toJson()); + group('json', () { + test('toJson', () { + final json = sdkVersion.toJson(); + + expect( + DeepCollectionEquality().equals(sdkVersionJson, json), + true, + ); + }); + test('fromJson', () { + final sdkVersion = SdkVersion.fromJson(sdkVersionJson); + final json = sdkVersion.toJson(); + + expect( + DeepCollectionEquality().equals(sdkVersionJson, json), + true, + ); + }); }); - test('copyWith takes new values', () { - final data = _generate(); - - final packages = [SentryPackage('name1', 'version1')]; - final integrations = ['test1']; - - final copy = data.copyWith( - name: 'name1', - version: 'version1', - integrations: integrations, - packages: packages, - ); - - expect( - ListEquality().equals(integrations, copy.integrations), - true, - ); - expect( - ListEquality().equals(packages, copy.packages), - true, - ); - expect('name1', copy.name); - expect('version1', copy.version); + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sdkVersion; + + final copy = data.copyWith(); + + expect(data.toJson(), copy.toJson()); + }); + + test('copyWith takes new values', () { + final data = sdkVersion; + + final packages = [SentryPackage('name1', 'version1')]; + final integrations = ['test1']; + + final copy = data.copyWith( + name: 'name1', + version: 'version1', + integrations: integrations, + packages: packages, + ); + + expect( + ListEquality().equals(integrations, copy.integrations), + true, + ); + expect( + ListEquality().equals(packages, copy.packages), + true, + ); + expect('name1', copy.name); + expect('version1', copy.version); + }); }); } - -SdkVersion _generate() => SdkVersion( - name: 'name', - version: 'version', - integrations: ['test'], - packages: [SentryPackage('name', 'version')], - ); diff --git a/dart/test/protocol/sentry_app_test.dart b/dart/test/protocol/sentry_app_test.dart new file mode 100644 index 0000000000..11defbcdb6 --- /dev/null +++ b/dart/test/protocol/sentry_app_test.dart @@ -0,0 +1,83 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final testStartTime = DateTime.fromMicrosecondsSinceEpoch(0); + + final sentryApp = SentryApp( + name: 'fixture-name', + version: 'fixture-version', + identifier: 'fixture-identifier', + build: 'fixture-build', + buildType: 'fixture-buildType', + startTime: testStartTime, + deviceAppHash: 'fixture-deviceAppHash'); + + final sentryAppJson = { + 'app_name': 'fixture-name', + 'app_version': 'fixture-version', + 'app_identifier': 'fixture-identifier', + 'app_build': 'fixture-build', + 'build_type': 'fixture-buildType', + 'app_start_time': testStartTime.toIso8601String(), + 'device_app_hash': 'fixture-deviceAppHash' + }; + + group('json', () { + test('toJson', () { + final json = sentryApp.toJson(); + + expect( + MapEquality().equals(sentryAppJson, json), + true, + ); + }); + test('fromJson', () { + final sentryApp = SentryApp.fromJson(sentryAppJson); + final json = sentryApp.toJson(); + + expect( + MapEquality().equals(sentryAppJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryApp; + + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = sentryApp; + + final startTime = DateTime.now(); + + final copy = data.copyWith( + name: 'name1', + version: 'version1', + identifier: 'identifier1', + build: 'build1', + buildType: 'buildType1', + startTime: startTime, + deviceAppHash: 'hash1', + ); + + expect('name1', copy.name); + expect('version1', copy.version); + expect('identifier1', copy.identifier); + expect('build1', copy.build); + expect('buildType1', copy.buildType); + expect(startTime, copy.startTime); + expect('hash1', copy.deviceAppHash); + }); + }); +} diff --git a/dart/test/protocol/sentry_browser_test.dart b/dart/test/protocol/sentry_browser_test.dart new file mode 100644 index 0000000000..a388361ecd --- /dev/null +++ b/dart/test/protocol/sentry_browser_test.dart @@ -0,0 +1,60 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final sentryBrowser = SentryBrowser( + name: 'fixture-name', + version: 'fixture-version', + ); + + final sentryBrowserJson = { + 'name': 'fixture-name', + 'version': 'fixture-version', + }; + + group('json', () { + test('toJson', () { + final json = sentryBrowser.toJson(); + + expect( + MapEquality().equals(sentryBrowserJson, json), + true, + ); + }); + test('fromJson', () { + final sentryBrowser = SentryBrowser.fromJson(sentryBrowserJson); + final json = sentryBrowser.toJson(); + + expect( + MapEquality().equals(sentryBrowserJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryBrowser; + + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = sentryBrowser; + + final copy = data.copyWith( + name: 'name1', + version: 'version1', + ); + + expect('name1', copy.name); + expect('version1', copy.version); + }); + }); +} diff --git a/dart/test/protocol/sentry_device_test.dart b/dart/test/protocol/sentry_device_test.dart new file mode 100644 index 0000000000..e704fbc1a5 --- /dev/null +++ b/dart/test/protocol/sentry_device_test.dart @@ -0,0 +1,156 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final testBootTime = DateTime.fromMicrosecondsSinceEpoch(0); + + final sentryDevice = SentryDevice( + name: 'testDevice', + family: 'testFamily', + model: 'testModel', + modelId: 'testModelId', + arch: 'testArch', + batteryLevel: 23.0, + orientation: SentryOrientation.landscape, + manufacturer: 'testOEM', + brand: 'testBrand', + screenResolution: '123x345', + screenDensity: 99.1, + screenDpi: 100, + online: false, + charging: true, + lowMemory: false, + simulator: true, + memorySize: 1234567, + freeMemory: 12345, + usableMemory: 9876, + storageSize: 1234567, + freeStorage: 1234567, + externalStorageSize: 98765, + externalFreeStorage: 98765, + bootTime: testBootTime, + timezone: 'Australia/Melbourne', + ); + + final sentryDeviceJson = { + 'name': 'testDevice', + 'family': 'testFamily', + 'model': 'testModel', + 'model_id': 'testModelId', + 'arch': 'testArch', + 'battery_level': 23.0, + 'orientation': 'landscape', + 'manufacturer': 'testOEM', + 'brand': 'testBrand', + 'screen_resolution': '123x345', + 'screen_density': 99.1, + 'screen_dpi': 100, + 'online': false, + 'charging': true, + 'low_memory': false, + 'simulator': true, + 'memory_size': 1234567, + 'free_memory': 12345, + 'usable_memory': 9876, + 'storage_size': 1234567, + 'free_storage': 1234567, + 'external_storage_size': 98765, + 'external_free_storage': 98765, + 'boot_time': testBootTime.toIso8601String(), + 'timezone': 'Australia/Melbourne', + }; + + group('json', () { + test('toJson', () { + final json = sentryDevice.toJson(); + + expect( + MapEquality().equals(sentryDeviceJson, json), + true, + ); + }); + test('fromJson', () { + final sentryDevice = SentryDevice.fromJson(sentryDeviceJson); + final json = sentryDevice.toJson(); + + expect( + MapEquality().equals(sentryDeviceJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryDevice; + + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = sentryDevice; + + final bootTime = DateTime.now(); + + final copy = data.copyWith( + name: 'name1', + family: 'family1', + model: 'model1', + modelId: 'modelId1', + arch: 'arch1', + batteryLevel: 2, + orientation: SentryOrientation.portrait, + manufacturer: 'manufacturer1', + brand: 'brand1', + screenResolution: '123x3451', + screenDensity: 99.2, + screenDpi: 99, + online: true, + charging: false, + lowMemory: true, + simulator: false, + memorySize: 12345678, + freeMemory: 123456, + usableMemory: 98765, + storageSize: 12345678, + freeStorage: 12345678, + externalStorageSize: 987654, + externalFreeStorage: 987654, + bootTime: bootTime, + timezone: 'Austria/Vienna', + ); + + expect('name1', copy.name); + expect('family1', copy.family); + expect('model1', copy.model); + expect('modelId1', copy.modelId); + expect('arch1', copy.arch); + expect(2, copy.batteryLevel); + expect(SentryOrientation.portrait, copy.orientation); + expect('manufacturer1', copy.manufacturer); + expect('brand1', copy.brand); + expect('123x3451', copy.screenResolution); + expect(99.2, copy.screenDensity); + expect(99, copy.screenDpi); + expect(true, copy.online); + expect(false, copy.charging); + expect(true, copy.lowMemory); + expect(false, copy.simulator); + expect(12345678, copy.memorySize); + expect(123456, copy.freeMemory); + expect(98765, copy.usableMemory); + expect(12345678, copy.storageSize); + expect(12345678, copy.freeStorage); + expect(987654, copy.externalStorageSize); + expect(987654, copy.externalFreeStorage); + expect(bootTime, copy.bootTime); + expect('Austria/Vienna', copy.timezone); + }); + }); +} diff --git a/dart/test/protocol/sentry_exception_test.dart b/dart/test/protocol/sentry_exception_test.dart index cad8cd4cba..15c75b0191 100644 --- a/dart/test/protocol/sentry_exception_test.dart +++ b/dart/test/protocol/sentry_exception_test.dart @@ -1,126 +1,154 @@ +import 'package:collection/collection.dart'; import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; void main() { - test('should serialize stacktrace', () { - final mechanism = Mechanism( - type: 'mechanism-example', - description: 'a mechanism', - handled: true, - synthetic: false, - helpLink: 'https://help.com', - data: {'polyfill': 'bluebird'}, - meta: { + final sentryException = SentryException( + type: 'type', + value: 'value', + module: 'module', + stackTrace: SentryStackTrace(frames: [SentryStackFrame(absPath: 'abs')]), + mechanism: Mechanism(type: 'type'), + threadId: 1, + ); + + final sentryExceptionJson = { + 'type': 'type', + 'value': 'value', + 'module': 'module', + 'stacktrace': { + 'frames': [ + {'abs_path': 'abs'} + ] + }, + 'mechanism': {'type': 'type'}, + 'thread_id': 1, + }; + + group('json', () { + test('fromJson', () { + final sentryException = SentryException.fromJson(sentryExceptionJson); + final json = sentryException.toJson(); + + expect( + DeepCollectionEquality().equals(sentryExceptionJson, json), + true, + ); + }); + + test('should serialize stacktrace', () { + final mechanism = Mechanism( + type: 'mechanism-example', + description: 'a mechanism', + handled: true, + synthetic: false, + helpLink: 'https://help.com', + data: {'polyfill': 'bluebird'}, + meta: { + 'signal': { + 'number': 10, + 'code': 0, + 'name': 'SIGBUS', + 'code_name': 'BUS_NOOP' + } + }, + ); + final stacktrace = SentryStackTrace(frames: [ + SentryStackFrame( + absPath: 'frame-path', + fileName: 'example.dart', + function: 'parse', + module: 'example-module', + lineNo: 1, + colNo: 2, + contextLine: 'context-line example', + inApp: true, + package: 'example-package', + native: false, + platform: 'dart', + rawFunction: 'example-rawFunction', + framesOmitted: [1, 2, 3], + ), + ]); + + final sentryException = SentryException( + type: 'StateError', + value: 'Bad state: error', + module: 'example.module', + stackTrace: stacktrace, + mechanism: mechanism, + threadId: 123456, + ); + + final serialized = sentryException.toJson(); + + expect(serialized['type'], 'StateError'); + expect(serialized['value'], 'Bad state: error'); + expect(serialized['module'], 'example.module'); + expect(serialized['thread_id'], 123456); + expect(serialized['mechanism']['type'], 'mechanism-example'); + expect(serialized['mechanism']['description'], 'a mechanism'); + expect(serialized['mechanism']['handled'], true); + expect(serialized['mechanism']['synthetic'], false); + expect(serialized['mechanism']['help_link'], 'https://help.com'); + expect(serialized['mechanism']['data'], {'polyfill': 'bluebird'}); + expect(serialized['mechanism']['meta'], { 'signal': { 'number': 10, 'code': 0, 'name': 'SIGBUS', 'code_name': 'BUS_NOOP' } - }, - ); - final stacktrace = SentryStackTrace(frames: [ - SentryStackFrame( - absPath: 'frame-path', - fileName: 'example.dart', - function: 'parse', - module: 'example-module', - lineNo: 1, - colNo: 2, - contextLine: 'context-line example', - inApp: true, - package: 'example-package', - native: false, - platform: 'dart', - rawFunction: 'example-rawFunction', - framesOmitted: [1, 2, 3], - ), - ]); - - final sentryException = SentryException( - type: 'StateError', - value: 'Bad state: error', - module: 'example.module', - stackTrace: stacktrace, - mechanism: mechanism, - threadId: 123456, - ); - - final serialized = sentryException.toJson(); - - expect(serialized['type'], 'StateError'); - expect(serialized['value'], 'Bad state: error'); - expect(serialized['module'], 'example.module'); - expect(serialized['thread_id'], 123456); - expect(serialized['mechanism']['type'], 'mechanism-example'); - expect(serialized['mechanism']['description'], 'a mechanism'); - expect(serialized['mechanism']['handled'], true); - expect(serialized['mechanism']['synthetic'], false); - expect(serialized['mechanism']['help_link'], 'https://help.com'); - expect(serialized['mechanism']['data'], {'polyfill': 'bluebird'}); - expect(serialized['mechanism']['meta'], { - 'signal': { - 'number': 10, - 'code': 0, - 'name': 'SIGBUS', - 'code_name': 'BUS_NOOP' - } - }); + }); - final serializedFrame = serialized['stacktrace']['frames'].first; - expect(serializedFrame['abs_path'], 'frame-path'); - expect(serializedFrame['filename'], 'example.dart'); - expect(serializedFrame['function'], 'parse'); - expect(serializedFrame['module'], 'example-module'); - expect(serializedFrame['lineno'], 1); - expect(serializedFrame['colno'], 2); - expect(serializedFrame['context_line'], 'context-line example'); - expect(serializedFrame['in_app'], true); - expect(serializedFrame['package'], 'example-package'); - expect(serializedFrame['native'], false); - expect(serializedFrame['platform'], 'dart'); - expect(serializedFrame['raw_function'], 'example-rawFunction'); - expect(serializedFrame['frames_omitted'], [1, 2, 3]); + final serializedFrame = serialized['stacktrace']['frames'].first; + expect(serializedFrame['abs_path'], 'frame-path'); + expect(serializedFrame['filename'], 'example.dart'); + expect(serializedFrame['function'], 'parse'); + expect(serializedFrame['module'], 'example-module'); + expect(serializedFrame['lineno'], 1); + expect(serializedFrame['colno'], 2); + expect(serializedFrame['context_line'], 'context-line example'); + expect(serializedFrame['in_app'], true); + expect(serializedFrame['package'], 'example-package'); + expect(serializedFrame['native'], false); + expect(serializedFrame['platform'], 'dart'); + expect(serializedFrame['raw_function'], 'example-rawFunction'); + expect(serializedFrame['frames_omitted'], [1, 2, 3]); + }); }); - test('copyWith keeps unchanged', () { - final data = _generate(); + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryException; - final copy = data.copyWith(); + final copy = data.copyWith(); - expect(data.toJson(), copy.toJson()); - }); + expect(data.toJson(), copy.toJson()); + }); + + test('copyWith takes new values', () { + final data = sentryException; - test('copyWith takes new values', () { - final data = _generate(); - - final stackTrace = - SentryStackTrace(frames: [SentryStackFrame(absPath: 'abs1')]); - final mechanism = Mechanism(type: 'type1'); - - final copy = data.copyWith( - type: 'type1', - value: 'value1', - module: 'module1', - stackTrace: stackTrace, - mechanism: mechanism, - threadId: 2, - ); - - expect('type1', copy.type); - expect('value1', copy.value); - expect('module1', copy.module); - expect(2, copy.threadId); - expect(mechanism.toJson(), copy.mechanism!.toJson()); - expect(stackTrace.toJson(), copy.stackTrace!.toJson()); + final stackTrace = + SentryStackTrace(frames: [SentryStackFrame(absPath: 'abs1')]); + final mechanism = Mechanism(type: 'type1'); + + final copy = data.copyWith( + type: 'type1', + value: 'value1', + module: 'module1', + stackTrace: stackTrace, + mechanism: mechanism, + threadId: 2, + ); + + expect('type1', copy.type); + expect('value1', copy.value); + expect('module1', copy.module); + expect(2, copy.threadId); + expect(mechanism.toJson(), copy.mechanism!.toJson()); + expect(stackTrace.toJson(), copy.stackTrace!.toJson()); + }); }); } - -SentryException _generate() => SentryException( - type: 'type', - value: 'value', - module: 'module', - stackTrace: SentryStackTrace(frames: [SentryStackFrame(absPath: 'abs')]), - mechanism: Mechanism(type: 'type'), - threadId: 1, - ); diff --git a/dart/test/protocol/sentry_gpu_test.dart b/dart/test/protocol/sentry_gpu_test.dart new file mode 100644 index 0000000000..c1e1c363b8 --- /dev/null +++ b/dart/test/protocol/sentry_gpu_test.dart @@ -0,0 +1,86 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final sentryGpu = SentryGpu( + name: 'fixture-name', + id: 1, + vendorId: 2, + vendorName: 'fixture-vendorName', + memorySize: 3, + apiType: 'fixture-apiType', + multiThreadedRendering: true, + version: '4', + npotSupport: 'fixture-npotSupport'); + + final sentryGpuJson = { + 'name': 'fixture-name', + 'id': 1, + 'vendor_id': 2, + 'vendor_name': 'fixture-vendorName', + 'memory_size': 3, + 'api_type': 'fixture-apiType', + 'multi_threaded_rendering': true, + 'version': '4', + 'npot_support': 'fixture-npotSupport' + }; + + group('json', () { + test('toJson', () { + final json = sentryGpu.toJson(); + + expect( + MapEquality().equals(sentryGpuJson, json), + true, + ); + }); + test('fromJson', () { + final sentryGpu = SentryGpu.fromJson(sentryGpuJson); + final json = sentryGpu.toJson(); + + expect( + MapEquality().equals(sentryGpuJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryGpu; + + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + test('copyWith takes new values', () { + final data = sentryGpu; + + final copy = data.copyWith( + name: 'name1', + id: 11, + vendorId: 22, + vendorName: 'vendorName1', + memorySize: 33, + apiType: 'apiType1', + multiThreadedRendering: false, + version: 'version1', + npotSupport: 'npotSupport1', + ); + + expect('name1', copy.name); + expect(11, copy.id); + expect(22, copy.vendorId); + expect('vendorName1', copy.vendorName); + expect(33, copy.memorySize); + expect('apiType1', copy.apiType); + expect(false, copy.multiThreadedRendering); + expect('version1', copy.version); + expect('npotSupport1', copy.npotSupport); + }); + }); +} diff --git a/dart/test/protocol/sentry_message_test.dart b/dart/test/protocol/sentry_message_test.dart new file mode 100644 index 0000000000..7112642ed2 --- /dev/null +++ b/dart/test/protocol/sentry_message_test.dart @@ -0,0 +1,64 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final sentryMessage = SentryMessage( + 'message 1', + template: 'message %d', + params: ['1'], + ); + + final sentryMessageJson = { + 'formatted': 'message 1', + 'message': 'message %d', + 'params': ['1'], + }; + + group('json', () { + test('toJson', () { + final json = sentryMessage.toJson(); + + expect( + DeepCollectionEquality().equals(sentryMessageJson, json), + true, + ); + }); + test('fromJson', () { + final sentryMessage = SentryMessage.fromJson(sentryMessageJson); + final json = sentryMessage.toJson(); + + expect( + DeepCollectionEquality().equals(sentryMessageJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryMessage; + + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = sentryMessage; + + final copy = data.copyWith( + formatted: 'message 21', + template: 'message 2 %d', + params: ['2'], + ); + + expect('message 21', copy.formatted); + expect('message 2 %d', copy.template); + expect(['2'], copy.params); + }); + }); +} diff --git a/dart/test/protocol/sentry_operating_system_test.dart b/dart/test/protocol/sentry_operating_system_test.dart new file mode 100644 index 0000000000..dee235712d --- /dev/null +++ b/dart/test/protocol/sentry_operating_system_test.dart @@ -0,0 +1,74 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final sentryOperatingSystem = SentryOperatingSystem( + name: 'fixture-name', + version: 'fixture-version', + build: 'fixture-build', + kernelVersion: 'fixture-kernelVersion', + rooted: true, + rawDescription: 'fixture-rawDescription'); + + final sentryOperatingSystemJson = { + 'name': 'fixture-name', + 'version': 'fixture-version', + 'build': 'fixture-build', + 'kernel_version': 'fixture-kernelVersion', + 'rooted': true, + 'raw_description': 'fixture-rawDescription' + }; + + group('json', () { + test('toJson', () { + final json = sentryOperatingSystem.toJson(); + + expect( + MapEquality().equals(sentryOperatingSystemJson, json), + true, + ); + }); + test('fromJson', () { + final sentryOperatingSystem = + SentryOperatingSystem.fromJson(sentryOperatingSystemJson); + final json = sentryOperatingSystem.toJson(); + + expect( + MapEquality().equals(sentryOperatingSystemJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryOperatingSystem; + + final copy = data.copyWith(); + + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = sentryOperatingSystem; + + final copy = data.copyWith( + name: 'name1', + version: 'version1', + build: 'build1', + kernelVersion: 'kernelVersion1', + rooted: true, + ); + + expect('name1', copy.name); + expect('version1', copy.version); + expect('build1', copy.build); + expect('kernelVersion1', copy.kernelVersion); + expect(true, copy.rooted); + }); + }); +} diff --git a/dart/test/protocol/sentry_package_test.dart b/dart/test/protocol/sentry_package_test.dart index 35e1ffd75e..b8c1d40e71 100644 --- a/dart/test/protocol/sentry_package_test.dart +++ b/dart/test/protocol/sentry_package_test.dart @@ -3,31 +3,57 @@ import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); + final sentryPackage = SentryPackage( + 'name', + 'version', + ); - final copy = data.copyWith(); + final sentryPackageJson = { + 'name': 'name', + 'version': 'version', + }; - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); + group('json', () { + test('toJson', () { + final json = sentryPackage.toJson(); + + expect( + MapEquality().equals(sentryPackageJson, json), + true, + ); + }); + test('fromJson', () { + final sentryPackage = SdkVersion.fromJson(sentryPackageJson); + final json = sentryPackage.toJson(); + + expect( + MapEquality().equals(sentryPackageJson, json), + true, + ); + }); }); - test('copyWith takes new values', () { - final data = _generate(); + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryPackage; + + final copy = data.copyWith(); - final copy = data.copyWith( - name: 'name1', - version: 'version1', - ); + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + test('copyWith takes new values', () { + final data = sentryPackage; - expect('name1', copy.name); - expect('version1', copy.version); + final copy = data.copyWith( + name: 'name1', + version: 'version1', + ); + + expect('name1', copy.name); + expect('version1', copy.version); + }); }); } - -SentryPackage _generate() => SentryPackage( - 'name', - 'version', - ); diff --git a/dart/test/protocol/sentry_request_test.dart b/dart/test/protocol/sentry_request_test.dart new file mode 100644 index 0000000000..ae78f75fdb --- /dev/null +++ b/dart/test/protocol/sentry_request_test.dart @@ -0,0 +1,78 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final sentryRequest = SentryRequest( + url: 'url', + method: 'method', + queryString: 'queryString', + cookies: 'cookies', + data: {'key': 'value'}, + headers: {'header_key': 'header_value'}, + env: {'env_key': 'env_value'}, + other: {'other_key': 'other_value'}, + ); + + final sentryRequestJson = { + 'url': 'url', + 'method': 'method', + 'query_string': 'queryString', + 'cookies': 'cookies', + 'data': {'key': 'value'}, + 'headers': {'header_key': 'header_value'}, + 'env': {'env_key': 'env_value'}, + 'other': {'other_key': 'other_value'}, + }; + + group('json', () { + test('toJson', () { + final json = sentryRequest.toJson(); + + expect( + DeepCollectionEquality().equals(sentryRequestJson, json), + true, + ); + }); + test('fromJson', () { + final sentryRequest = SentryRequest.fromJson(sentryRequestJson); + final json = sentryRequest.toJson(); + + expect( + DeepCollectionEquality().equals(sentryRequestJson, json), + true, + ); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryRequest; + + final copy = data.copyWith(); + + expect( + DeepCollectionEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); + + test('copyWith takes new values', () { + final data = sentryRequest; + + final copy = data.copyWith( + url: 'url1', + method: 'method1', + queryString: 'queryString1', + cookies: 'cookies1', + data: {'key1': 'value1'}, + ); + + expect('url1', copy.url); + expect('method1', copy.method); + expect('queryString1', copy.queryString); + expect('cookies1', copy.cookies); + expect({'key1': 'value1'}, copy.data); + }); + }); +} diff --git a/dart/test/protocol/sentry_runtime_test.dart b/dart/test/protocol/sentry_runtime_test.dart index 75878c6d2c..578eb8c9a3 100644 --- a/dart/test/protocol/sentry_runtime_test.dart +++ b/dart/test/protocol/sentry_runtime_test.dart @@ -3,37 +3,65 @@ import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); + final sentryRuntime = SentryRuntime( + key: 'key', + name: 'name', + version: 'version', + rawDescription: 'rawDescription', + ); - final copy = data.copyWith(); + final sentryRuntimeJson = { + 'name': 'name', + 'version': 'version', + 'raw_description': 'rawDescription', + }; - expect( - MapEquality().equals(data.toJson(), copy.toJson()), - true, - ); + group('json', () { + test('toJson', () { + final json = sentryRuntime.toJson(); + + expect( + MapEquality().equals(sentryRuntimeJson, json), + true, + ); + }); + test('fromJson', () { + final sentryRuntime = SentryRuntime.fromJson(sentryRuntimeJson); + final json = sentryRuntime.toJson(); + + expect( + MapEquality().equals(sentryRuntimeJson, json), + true, + ); + }); }); - test('copyWith takes new values', () { - final data = _generate(); + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryRuntime; + + final copy = data.copyWith(); - final copy = data.copyWith( - key: 'key1', - name: 'name1', - version: 'version1', - rawDescription: 'rawDescription1', - ); + expect( + MapEquality().equals(data.toJson(), copy.toJson()), + true, + ); + }); - expect('key1', copy.key); - expect('name1', copy.name); - expect('version1', copy.version); - expect('rawDescription1', copy.rawDescription); + test('copyWith takes new values', () { + final data = sentryRuntime; + + final copy = data.copyWith( + key: 'key1', + name: 'name1', + version: 'version1', + rawDescription: 'rawDescription1', + ); + + expect('key1', copy.key); + expect('name1', copy.name); + expect('version1', copy.version); + expect('rawDescription1', copy.rawDescription); + }); }); } - -SentryRuntime _generate() => SentryRuntime( - key: 'key', - name: 'name', - version: 'version', - rawDescription: 'rawDescription', - ); diff --git a/dart/test/protocol/sentry_stack_frame_test.dart b/dart/test/protocol/sentry_stack_frame_test.dart index 3690e77f53..69b5ad30fa 100644 --- a/dart/test/protocol/sentry_stack_frame_test.dart +++ b/dart/test/protocol/sentry_stack_frame_test.dart @@ -1,79 +1,123 @@ +import 'package:collection/collection.dart'; import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); + final sentryStackFrame = SentryStackFrame( + absPath: 'absPath', + fileName: 'fileName', + function: 'function', + module: 'module', + lineNo: 1, + colNo: 2, + contextLine: 'contextLine', + inApp: true, + package: 'package', + native: false, + platform: 'platform', + imageAddr: 'imageAddr', + symbolAddr: 'symbolAddr', + instructionAddr: 'instructionAddr', + rawFunction: 'rawFunction', + framesOmitted: [1], + preContext: ['a'], + postContext: ['b'], + vars: {'key': 'value'}, + ); - final copy = data.copyWith(); + final sentryStackFrameJson = { + 'pre_context': ['a'], + 'post_context': ['b'], + 'vars': {'key': 'value'}, + 'frames_omitted': [1], + 'filename': 'fileName', + 'package': 'package', + 'function': 'function', + 'module': 'module', + 'lineno': 1, + 'colno': 2, + 'abs_path': 'absPath', + 'context_line': 'contextLine', + 'in_app': true, + 'native': false, + 'platform': 'platform', + 'image_addr': 'imageAddr', + 'symbol_addr': 'symbolAddr', + 'instruction_addr': 'instructionAddr', + 'raw_function': 'rawFunction', + }; - expect(data.toJson(), copy.toJson()); + group('json', () { + test('toJson', () { + final json = sentryStackFrame.toJson(); + + expect( + DeepCollectionEquality().equals(sentryStackFrameJson, json), + true, + ); + }); + test('fromJson', () { + final sentryStackFrame = SentryStackFrame.fromJson(sentryStackFrameJson); + final json = sentryStackFrame.toJson(); + + expect( + DeepCollectionEquality().equals(sentryStackFrameJson, json), + true, + ); + }); }); - test('copyWith takes new values', () { - final data = _generate(); + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryStackFrame; + + final copy = data.copyWith(); - final copy = data.copyWith( - absPath: 'absPath1', - fileName: 'fileName1', - function: 'function1', - module: 'module1', - lineNo: 11, - colNo: 22, - contextLine: 'contextLine1', - inApp: false, - package: 'package1', - native: true, - platform: 'platform1', - imageAddr: 'imageAddr1', - symbolAddr: 'symbolAddr1', - instructionAddr: 'instructionAddr1', - rawFunction: 'rawFunction1', - framesOmitted: [11], - preContext: ['ab'], - postContext: ['bb'], - vars: {'key1': 'value1'}, - ); + expect(data.toJson(), copy.toJson()); + }); + test('copyWith takes new values', () { + final data = sentryStackFrame; - expect('absPath1', copy.absPath); - expect('fileName1', copy.fileName); - expect('function1', copy.function); - expect('module1', copy.module); - expect(11, copy.lineNo); - expect(22, copy.colNo); - expect(false, copy.inApp); - expect('package1', copy.package); - expect(true, copy.native); - expect('platform1', copy.platform); - expect('imageAddr1', copy.imageAddr); - expect('symbolAddr1', copy.symbolAddr); - expect('instructionAddr1', copy.instructionAddr); - expect('rawFunction1', copy.rawFunction); - expect([11], copy.framesOmitted); - expect(['ab'], copy.preContext); - expect(['bb'], copy.postContext); - expect({'key1': 'value1'}, copy.vars); + final copy = data.copyWith( + absPath: 'absPath1', + fileName: 'fileName1', + function: 'function1', + module: 'module1', + lineNo: 11, + colNo: 22, + contextLine: 'contextLine1', + inApp: false, + package: 'package1', + native: true, + platform: 'platform1', + imageAddr: 'imageAddr1', + symbolAddr: 'symbolAddr1', + instructionAddr: 'instructionAddr1', + rawFunction: 'rawFunction1', + framesOmitted: [11], + preContext: ['ab'], + postContext: ['bb'], + vars: {'key1': 'value1'}, + ); + + expect('absPath1', copy.absPath); + expect('fileName1', copy.fileName); + expect('function1', copy.function); + expect('module1', copy.module); + expect(11, copy.lineNo); + expect(22, copy.colNo); + expect(false, copy.inApp); + expect('package1', copy.package); + expect(true, copy.native); + expect('platform1', copy.platform); + expect('imageAddr1', copy.imageAddr); + expect('symbolAddr1', copy.symbolAddr); + expect('instructionAddr1', copy.instructionAddr); + expect('rawFunction1', copy.rawFunction); + expect([11], copy.framesOmitted); + expect(['ab'], copy.preContext); + expect(['bb'], copy.postContext); + expect({'key1': 'value1'}, copy.vars); + }); }); } - -SentryStackFrame _generate() => SentryStackFrame( - absPath: 'absPath', - fileName: 'fileName', - function: 'function', - module: 'module', - lineNo: 1, - colNo: 2, - contextLine: 'contextLine', - inApp: true, - package: 'package', - native: false, - platform: 'platform', - imageAddr: 'imageAddr', - symbolAddr: 'symbolAddr', - instructionAddr: 'instructionAddr', - rawFunction: 'rawFunction', - framesOmitted: [1], - preContext: ['a'], - postContext: ['b'], - vars: {'key': 'value'}, - ); diff --git a/dart/test/protocol/sentry_stack_trace_test.dart b/dart/test/protocol/sentry_stack_trace_test.dart index 8f74f3c59e..ce3f4817d6 100644 --- a/dart/test/protocol/sentry_stack_trace_test.dart +++ b/dart/test/protocol/sentry_stack_trace_test.dart @@ -3,37 +3,66 @@ import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); + final sentryStackTrace = SentryStackTrace( + frames: [SentryStackFrame(absPath: 'abs')], + registers: {'key': 'value'}, + ); - final copy = data.copyWith(); + final sentryStackTraceJson = { + 'frames': [ + {'abs_path': 'abs'} + ], + 'registers': {'key': 'value'}, + }; - expect(data.toJson(), copy.toJson()); + group('json', () { + test('toJson', () { + final json = sentryStackTrace.toJson(); + + expect( + DeepCollectionEquality().equals(sentryStackTraceJson, json), + true, + ); + }); + test('fromJson', () { + final sentryStackTrace = SentryStackTrace.fromJson(sentryStackTraceJson); + final json = sentryStackTrace.toJson(); + + expect( + DeepCollectionEquality().equals(sentryStackTraceJson, json), + true, + ); + }); }); - test('copyWith takes new values', () { - final data = _generate(); - - final frames = [SentryStackFrame(absPath: 'abs1')]; - final registers = {'key1': 'value1'}; - - final copy = data.copyWith( - frames: frames, - registers: registers, - ); - - expect( - ListEquality().equals(frames, copy.frames), - true, - ); - expect( - MapEquality().equals(registers, copy.registers), - true, - ); + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryStackTrace; + + final copy = data.copyWith(); + + expect(data.toJson(), copy.toJson()); + }); + + test('copyWith takes new values', () { + final data = sentryStackTrace; + + final frames = [SentryStackFrame(absPath: 'abs1')]; + final registers = {'key1': 'value1'}; + + final copy = data.copyWith( + frames: frames, + registers: registers, + ); + + expect( + ListEquality().equals(frames, copy.frames), + true, + ); + expect( + MapEquality().equals(registers, copy.registers), + true, + ); + }); }); } - -SentryStackTrace _generate() => SentryStackTrace( - frames: [SentryStackFrame(absPath: 'abs')], - registers: {'key': 'value'}, - ); diff --git a/dart/test/protocol/sentry_user_test.dart b/dart/test/protocol/sentry_user_test.dart new file mode 100644 index 0000000000..297a949099 --- /dev/null +++ b/dart/test/protocol/sentry_user_test.dart @@ -0,0 +1,95 @@ +import 'package:collection/collection.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +void main() { + final sentryUser = SentryUser( + id: 'id', + username: 'username', + email: 'email', + ipAddress: 'ipAddress', + extras: {'key': 'value'}, + ); + + final sentryUserJson = { + 'id': 'id', + 'username': 'username', + 'email': 'email', + 'ip_address': 'ipAddress', + 'extras': {'key': 'value'}, + }; + + group('json', () { + test('toJson', () { + final json = sentryUser.toJson(); + + expect( + DeepCollectionEquality().equals(sentryUserJson, json), + true, + ); + }); + test('fromJson', () { + final sentryUser = SentryUser.fromJson(sentryUserJson); + final json = sentryUser.toJson(); + + expect( + DeepCollectionEquality().equals(sentryUserJson, json), + true, + ); + }); + + test('toJson only serialises non-null values', () { + var data = SentryUser( + id: 'id', + ); + + var json = data.toJson(); + + expect(json.containsKey('id'), true); + expect(json.containsKey('username'), false); + expect(json.containsKey('email'), false); + expect(json.containsKey('ip_address'), false); + expect(json.containsKey('extras'), false); + + data = SentryUser( + ipAddress: 'ip', + ); + + json = data.toJson(); + + expect(json.containsKey('id'), false); + expect(json.containsKey('username'), false); + expect(json.containsKey('email'), false); + expect(json.containsKey('ip_address'), true); + expect(json.containsKey('extras'), false); + }); + }); + + group('copyWith', () { + test('copyWith keeps unchanged', () { + final data = sentryUser; + + final copy = data.copyWith(); + + expect(data.toJson(), copy.toJson()); + }); + + test('copyWith takes new values', () { + final data = sentryUser; + + final copy = data.copyWith( + id: 'id1', + username: 'username1', + email: 'email1', + ipAddress: 'ipAddress1', + extras: {'key1': 'value1'}, + ); + + expect('id1', copy.id); + expect('username1', copy.username); + expect('email1', copy.email); + expect('ipAddress1', copy.ipAddress); + expect({'key1': 'value1'}, copy.extras); + }); + }); +} diff --git a/dart/test/protocol/user_test.dart b/dart/test/protocol/user_test.dart deleted file mode 100644 index 8f0a080d52..0000000000 --- a/dart/test/protocol/user_test.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:sentry/sentry.dart'; -import 'package:test/test.dart'; - -void main() { - test('copyWith keeps unchanged', () { - final data = _generate(); - - final copy = data.copyWith(); - - expect(data.toJson(), copy.toJson()); - }); - - test('copyWith takes new values', () { - final data = _generate(); - - final copy = data.copyWith( - id: 'id1', - username: 'username1', - email: 'email1', - ipAddress: 'ipAddress1', - extras: {'key1': 'value1'}, - ); - - expect('id1', copy.id); - expect('username1', copy.username); - expect('email1', copy.email); - expect('ipAddress1', copy.ipAddress); - expect({'key1': 'value1'}, copy.extras); - }); - - test('toJson only serialises non-null values', () { - var data = SentryUser( - id: 'id', - ); - - var json = data.toJson(); - - expect(json.containsKey('id'), true); - expect(json.containsKey('username'), false); - expect(json.containsKey('email'), false); - expect(json.containsKey('ip_address'), false); - expect(json.containsKey('extras'), false); - - data = SentryUser( - ipAddress: 'ip', - ); - - json = data.toJson(); - - expect(json.containsKey('id'), false); - expect(json.containsKey('username'), false); - expect(json.containsKey('email'), false); - expect(json.containsKey('ip_address'), true); - expect(json.containsKey('extras'), false); - }); -} - -SentryUser _generate() => SentryUser( - id: 'id', - username: 'username', - email: 'email', - ipAddress: 'ipAddress', - extras: {'key': 'value'}, - ); diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart index c752af949d..929b897e14 100644 --- a/dart/test/sentry_client_test.dart +++ b/dart/test/sentry_client_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:sentry/sentry.dart'; import 'package:sentry/src/sentry_stack_trace_factory.dart'; import 'package:test/test.dart'; @@ -22,7 +24,9 @@ void main() { stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', ); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); expect(capturedEvent.stackTrace is SentryStackTrace, true); }); @@ -32,7 +36,9 @@ void main() { final event = SentryEvent(); await client.captureEvent(event); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); expect(capturedEvent.stackTrace is SentryStackTrace, true); }); @@ -42,7 +48,9 @@ void main() { final event = SentryEvent(); await client.captureEvent(event); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); expect(capturedEvent.stackTrace, isNull); }); @@ -62,7 +70,9 @@ void main() { stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', ); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); expect(capturedEvent.stackTrace, isNull); expect(capturedEvent.exception!.stackTrace, isNotNull); @@ -86,7 +96,9 @@ void main() { stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', ); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); expect(capturedEvent.stackTrace, isNull); expect(capturedEvent.exception!.stackTrace, isNotNull); @@ -101,7 +113,9 @@ void main() { level: SentryLevel.error, ); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); expect(capturedEvent.message!.formatted, 'simple message 1'); expect(capturedEvent.message!.template, 'simple message %d'); @@ -115,7 +129,10 @@ void main() { 'simple message 1', ); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + expect(capturedEvent.level, SentryLevel.info); }); @@ -123,7 +140,9 @@ void main() { final client = SentryClient(options..attachStacktrace = false); await client.captureMessage('message', level: SentryLevel.error); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); expect(capturedEvent.stackTrace, isNull); }); @@ -133,7 +152,11 @@ void main() { var options = SentryOptions(dsn: fakeDsn); Error error; - StackTrace stackTrace; + final stackTrace = ''' +#0 baz (file:///pathto/test.dart:50:3) + +#1 bar (file:///pathto/test.dart:46:9) + '''; setUp(() { options = SentryOptions(dsn: fakeDsn); @@ -143,19 +166,21 @@ void main() { test('should capture error', () async { try { throw StateError('Error'); - } on Error catch (err, stack) { + } on Error catch (err) { error = err; - stackTrace = stack; } final client = SentryClient(options); await client.captureException(error, stackTrace: stackTrace); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); - expect(capturedEvent.throwable, error); expect(capturedEvent.exception is SentryException, true); expect(capturedEvent.exception!.stackTrace, isNotNull); + expect(capturedEvent.exception!.stackTrace!.frames.first.lineNo, 46); + expect(capturedEvent.exception!.stackTrace!.frames.first.colNo, 9); }); }); @@ -185,9 +210,10 @@ void main() { final client = SentryClient(options); await client.captureException(error, stackTrace: stacktrace); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); - expect(capturedEvent.throwable, error); expect(capturedEvent.exception is SentryException, true); expect(capturedEvent.exception!.stackTrace, isNotNull); expect(capturedEvent.exception!.stackTrace!.frames.first.fileName, @@ -223,9 +249,10 @@ void main() { final client = SentryClient(options); await client.captureException(exception, stackTrace: stacktrace); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); - expect(capturedEvent.throwable, exception); expect(capturedEvent.exception is SentryException, true); expect(capturedEvent.exception!.stackTrace!.frames.first.fileName, 'test.dart'); @@ -243,7 +270,9 @@ void main() { final client = SentryClient(options); await client.captureException(exception); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); expect(capturedEvent.exception!.stackTrace, isNotNull); }); @@ -258,7 +287,9 @@ void main() { final client = SentryClient(options..attachStacktrace = false); await client.captureException(exception); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); expect(capturedEvent.exception!.stackTrace, isNull); }); @@ -280,7 +311,9 @@ void main() { final client = SentryClient(options); await client.captureException(exception, stackTrace: stacktrace); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); expect( capturedEvent.exception!.stackTrace!.frames @@ -333,13 +366,15 @@ void main() { final client = SentryClient(options); await client.captureEvent(event, scope: scope); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); expect(capturedEvent.user?.id, user.id); expect(capturedEvent.level!.name, SentryLevel.error.name); expect(capturedEvent.transaction, transaction); expect(capturedEvent.fingerprint, fingerprint); - expect(capturedEvent.breadcrumbs?.first, crumb); + expect(capturedEvent.breadcrumbs?.first.toJson(), crumb.toJson()); expect(capturedEvent.tags, { scopeTagKey: scopeTagValue, eventTagKey: eventTagValue, @@ -386,13 +421,16 @@ void main() { final client = SentryClient(options); await client.captureEvent(event, scope: scope); - final capturedEvent = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); expect(capturedEvent.user!.id, eventUser.id); expect(capturedEvent.level!.name, SentryLevel.warning.name); expect(capturedEvent.transaction, eventTransaction); expect(capturedEvent.fingerprint, eventFingerprint); - expect(capturedEvent.breadcrumbs, eventCrumbs); + expect(capturedEvent.breadcrumbs?.map((e) => e.toJson()), + eventCrumbs.map((e) => e.toJson())); }); }); @@ -409,7 +447,10 @@ void main() { await client.captureEvent(fakeEvent); - expect(transport.events.first.user, fakeEvent.user); + final capturedEnvelope = transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(capturedEvent.user?.toJson(), fakeEvent.user?.toJson()); }); test('sendDefaultPii is enabled and event has no user', () async { @@ -419,9 +460,12 @@ void main() { await client.captureEvent(fakeEvent); - expect(transport.events.length, 1); - expect(transport.events.first.user, isNotNull); - expect(transport.events.first.user?.ipAddress, '{{auto}}'); + final capturedEnvelope = transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(transport.envelopes.length, 1); + expect(capturedEvent.user, isNotNull); + expect(capturedEvent.user?.ipAddress, '{{auto}}'); }); test('sendDefaultPii is enabled and event has a user with IP address', @@ -431,12 +475,15 @@ void main() { await client.captureEvent(fakeEvent); - expect(transport.events.length, 1); - expect(transport.events.first.user, isNotNull); + final capturedEnvelope = transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(transport.envelopes.length, 1); + expect(capturedEvent.user, isNotNull); // fakeEvent has a user which is not null - expect(transport.events.first.user?.ipAddress, fakeEvent.user!.ipAddress); - expect(transport.events.first.user?.id, fakeEvent.user!.id); - expect(transport.events.first.user?.email, fakeEvent.user!.email); + expect(capturedEvent.user?.ipAddress, fakeEvent.user!.ipAddress); + expect(capturedEvent.user?.id, fakeEvent.user!.id); + expect(capturedEvent.user?.email, fakeEvent.user!.email); }); test('sendDefaultPii is enabled and event has a user without IP address', @@ -448,11 +495,14 @@ void main() { await client.captureEvent(event); - expect(transport.events.length, 1); - expect(transport.events.first.user, isNotNull); - expect(transport.events.first.user?.ipAddress, '{{auto}}'); - expect(transport.events.first.user?.id, fakeUser.id); - expect(transport.events.first.user?.email, fakeUser.email); + final capturedEnvelope = transport.envelopes.first; + final capturedEvent = await eventFromEnvelope(capturedEnvelope); + + expect(transport.envelopes.length, 1); + expect(capturedEvent.user, isNotNull); + expect(capturedEvent.user?.ipAddress, '{{auto}}'); + expect(capturedEvent.user?.id, fakeUser.id); + expect(capturedEvent.user?.email, fakeUser.email); }); }); @@ -510,7 +560,9 @@ void main() { final client = SentryClient(options); await client.captureEvent(fakeEvent); - final event = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final event = await eventFromEnvelope(capturedEnvelope); expect(event.tags!.containsKey('theme'), true); expect(event.extra!.containsKey('host'), true); @@ -551,7 +603,10 @@ void main() { final client = SentryClient(options); await client.captureEvent(fakeEvent); - final event = (options.transport as MockTransport).events.first; + final capturedEnvelope = + (options.transport as MockTransport).envelopes.first; + final event = await eventFromEnvelope(capturedEnvelope); + expect(event.tags!.containsKey('theme'), true); expect(event.extra!.containsKey('host'), true); expect(event.modules!.containsKey('core'), true); @@ -612,6 +667,16 @@ void main() { }); } +Future eventFromEnvelope(SentryEnvelope envelope) async { + final envelopeItemData = []; + await envelope.items.first + .envelopeItemStream() + .forEach(envelopeItemData.addAll); + final envelopeItem = utf8.decode(envelopeItemData); + final envelopeItemJson = jsonDecode(envelopeItem.split('\n').last); + return SentryEvent.fromJson(envelopeItemJson as Map); +} + SentryEvent? beforeSendCallbackDropEvent(SentryEvent event, {dynamic hint}) => null; diff --git a/dart/test/sentry_envelope_header_test.dart b/dart/test/sentry_envelope_header_test.dart index ae854489af..28f894e766 100644 --- a/dart/test/sentry_envelope_header_test.dart +++ b/dart/test/sentry_envelope_header_test.dart @@ -3,7 +3,7 @@ import 'package:sentry/src/sentry_envelope_header.dart'; import 'package:test/test.dart'; void main() { - group('SentryEnvelopeItemHeader', () { + group('SentryEnvelopeHeader', () { test('toJson empty', () { final sut = SentryEnvelopeHeader(null, null); final expected = {}; diff --git a/dart/test/sentry_envelope_test.dart b/dart/test/sentry_envelope_test.dart index d04e0ee482..624e4b0992 100644 --- a/dart/test/sentry_envelope_test.dart +++ b/dart/test/sentry_envelope_test.dart @@ -10,7 +10,7 @@ import 'package:sentry/src/protocol/sentry_id.dart'; import 'package:test/test.dart'; void main() { - group('SentryEnvelopeItem', () { + group('SentryEnvelope', () { test('serialize', () async { final eventId = SentryId.newId(); diff --git a/dart/test/sentry_envelope_vm_test.dart b/dart/test/sentry_envelope_vm_test.dart new file mode 100644 index 0000000000..ffbdd14e08 --- /dev/null +++ b/dart/test/sentry_envelope_vm_test.dart @@ -0,0 +1,46 @@ +@TestOn('vm') +import 'dart:io'; + +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/sentry_envelope.dart'; +import 'package:sentry/src/sentry_envelope_header.dart'; +import 'package:sentry/src/sentry_envelope_item_header.dart'; +import 'package:sentry/src/sentry_envelope_item.dart'; +import 'package:sentry/src/protocol/sentry_id.dart'; +import 'package:test/test.dart'; + +void main() { + group('SentryEnvelopeItem', () { + test('item with binary payload', () async { + // Attachment + + final length = () async { + return 3535; + }; + final dataFactory = () async { + final file = File('test_resources/sentry.png'); + final bytes = await file.readAsBytes(); + return bytes; + }; + final attachmentHeader = SentryEnvelopeItemHeader('attachment', length, + contentType: 'image/png', fileName: 'sentry.png'); + final attachmentItem = SentryEnvelopeItem(attachmentHeader, dataFactory); + + // Envelope + + final eventId = SentryId.fromId('3b382f22ee67491f80f7dee18016a7b1'); + final sdkVersion = SdkVersion(name: 'test', version: 'version'); + final header = SentryEnvelopeHeader(eventId, sdkVersion); + final envelope = SentryEnvelope(header, [attachmentItem]); + + final envelopeData = []; + await envelope.envelopeStream().forEach(envelopeData.addAll); + + final expectedEnvelopeFile = + File('test_resources/envelope-with-image.envelope'); + final expectedEnvelopeData = await expectedEnvelopeFile.readAsBytes(); + + expect(expectedEnvelopeData, envelopeData); + }); + }); +} diff --git a/dart/test/sentry_event_test.dart b/dart/test/sentry_event_test.dart index 51847556f7..6fd52cf673 100644 --- a/dart/test/sentry_event_test.dart +++ b/dart/test/sentry_event_test.dart @@ -2,15 +2,109 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:collection/collection.dart'; import 'package:sentry/sentry.dart'; import 'package:sentry/src/protocol/sentry_request.dart'; import 'package:sentry/src/sentry_stack_trace_factory.dart'; import 'package:sentry/src/version.dart'; +import 'package:sentry/src/utils.dart'; import 'package:test/test.dart'; import 'mocks.dart'; void main() { + group('deserialize', () { + final sentryId = SentryId.empty(); + final timestamp = DateTime.fromMillisecondsSinceEpoch(0); + final sentryEventJson = { + 'event_id': sentryId.toString(), + 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), + 'platform': 'platform', + 'logger': 'logger', + 'server_name': 'serverName', + 'release': 'release', + 'dist': 'dist', + 'environment': 'environment', + 'modules': {'key': 'value'}, + 'message': {'formatted': 'formatted'}, + 'transaction': 'transaction', + 'exception': { + 'values': [ + {'type': 'type', 'value': 'value'} + ] + }, + 'level': 'debug', + 'culprit': 'culprit', + 'tags': {'key': 'value'}, + 'extra': {'key': 'value'}, + 'contexts': { + 'device': {'name': 'name'} + }, + 'user': { + 'id': 'id', + 'username': 'username', + 'ip_address': '192.168.0.0.1' + }, + 'fingerprint': ['fingerprint'], + 'breadcrumbs': [ + { + 'message': 'message', + 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), + 'level': 'info' + } + ], + 'sdk': {'name': 'name', 'version': 'version'}, + 'request': {'url': 'url'}, + 'debug_meta': { + 'sdk_info': {'sdk_name': 'sdkName'} + }, + }; + + final emptyFieldsSentryEventJson = { + 'event_id': sentryId.toString(), + 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), + 'contexts': { + 'device': {'name': 'name'} + }, + }; + + test('fromJson', () { + final sentryEvent = SentryEvent.fromJson(sentryEventJson); + final json = sentryEvent.toJson(); + + expect( + DeepCollectionEquality().equals(sentryEventJson, json), + true, + ); + }); + + test('should not deserialize null or empty fields', () { + final sentryEvent = SentryEvent.fromJson(emptyFieldsSentryEventJson); + + expect(sentryEvent.platform, isNull); + expect(sentryEvent.logger, isNull); + expect(sentryEvent.serverName, isNull); + expect(sentryEvent.release, isNull); + expect(sentryEvent.dist, isNull); + expect(sentryEvent.environment, isNull); + expect(sentryEvent.modules, isNull); + expect(sentryEvent.message, isNull); + expect(sentryEvent.stackTrace, isNull); + expect(sentryEvent.exception, isNull); + expect(sentryEvent.transaction, isNull); + expect(sentryEvent.level, isNull); + expect(sentryEvent.culprit, isNull); + expect(sentryEvent.tags, isNull); + expect(sentryEvent.extra, isNull); + expect(sentryEvent.breadcrumbs, isNull); + expect(sentryEvent.user, isNull); + expect(sentryEvent.fingerprint, isNull); + expect(sentryEvent.sdk, isNull); + expect(sentryEvent.request, isNull); + expect(sentryEvent.debugMeta, isNull); + }); + }); + group(SentryEvent, () { test('$Breadcrumb serializes', () { expect( diff --git a/dart/test/transport/http_transport_test.dart b/dart/test/transport/http_transport_test.dart index 25d80a7100..89e5613814 100644 --- a/dart/test/transport/http_transport_test.dart +++ b/dart/test/transport/http_transport_test.dart @@ -45,7 +45,7 @@ void main() { final sut = fixture.getSut(httpMock, mockRateLimiter); final sentryEnvelope = givenEnvelope(); - await sut.sendSentryEnvelope(sentryEnvelope); + await sut.send(sentryEnvelope); expect(mockRateLimiter.envelopeToFilter, sentryEnvelope); }); @@ -66,7 +66,9 @@ void main() { final sut = fixture.getSut(httpMock, mockRateLimiter); final sentryEvent = SentryEvent(); - await sut.sendSentryEvent(sentryEvent); + final envelope = + SentryEnvelope.fromEvent(sentryEvent, fixture.options.sdk); + await sut.send(envelope); final envelopeData = []; await filteredEnvelope.envelopeStream().forEach(envelopeData.addAll); @@ -86,7 +88,9 @@ void main() { final sut = fixture.getSut(httpMock, mockRateLimiter); final sentryEvent = SentryEvent(); - final eventId = await sut.sendSentryEvent(sentryEvent); + final envelope = + SentryEnvelope.fromEvent(sentryEvent, fixture.options.sdk); + final eventId = await sut.send(envelope); expect(eventId, SentryId.empty()); expect(httpCalled, false); @@ -108,7 +112,10 @@ void main() { final sut = fixture.getSut(httpMock, mockRateLimiter); final sentryEvent = SentryEvent(); - await sut.sendSentryEvent(sentryEvent); + final envelope = + SentryEnvelope.fromEvent(sentryEvent, fixture.options.sdk); + await sut.send(envelope); + expect(mockRateLimiter.envelopeToFilter?.header.eventId, sentryEvent.eventId); @@ -126,7 +133,9 @@ void main() { final sut = fixture.getSut(httpMock, mockRateLimiter); final sentryEvent = SentryEvent(); - await sut.sendSentryEvent(sentryEvent); + final envelope = + SentryEnvelope.fromEvent(sentryEvent, fixture.options.sdk); + await sut.send(envelope); expect(mockRateLimiter.errorCode, 200); expect(mockRateLimiter.retryAfterHeader, isNull); diff --git a/dart/test_resources/envelope-with-image.envelope b/dart/test_resources/envelope-with-image.envelope new file mode 100644 index 0000000000000000000000000000000000000000..533f5e4f6fc9c60af5fbb0311fdb5fdfc2e074e9 GIT binary patch literal 3712 zcmV-`4uA1`B4u`EZggL1WFk5uGh#D1GG;O|Wo0%uG&wP5I51{6WMySBI506bVK-ti zA}k_vWNRWidm?UOZDk@lB6MYQbRsMwc4cyNX>V>KIwE#ua&u{KZX$hs3VR}BZ*FvD zZggLCd2nSSIwEOpVP|D8aBgQJEFxxUY-Mg?ZDk@lB6DSKbaHtvaBgQJEFyGyaAhJo zB4KoNVPj}*Wo~pLEFx@WZfA68B04iQGc|n*iBL{Q4GJ0x0000DNk~Le0002t0002t z2m=5B010g(NB{r;hfqvZMgRZ*0002#>FMU?=5B6oL_|cdudn?4{Q3F$?Ck9I_4Ufi z%J1**_xJaHetwaWkx5BOzrVlV-`~y6&DPe|TwGi@I5<8&K4D>DxVX4xW@gjV(C;AvQ<@8V#tQV z000dGNklX+z>l6NQb-un3BCU1ku`I9_y?|Nr~m0TF?Es=7K!C*i(1A0=R~ zb*fHPcaDxabm-8bLx&C>I&}EILw__F44ZC5G7PUS^7+fd!|d+n^1R=4YpTcjO}2yS zKTWr&T3p>`0=MT)x2ifE-^l(gaXMAeb@0E? zMP&{5x|zlCkE?@+ zY){XeJ-lnWp+)iC*}*LP(sWY`!rj3v>owihLb$}f%wOrR7fm;|2#_g$433U|(Fcd< z+5Dsqu0Ht}KKa&gYYX6-W@`NzeROz?F@auuUw?K5pUoR>Z2?dxCYw)l`1DtUtu2BD z^x=NKsu+UNpdo+pn#uu6fO^L1cet~aH%x^S` z6KHNovA=`wd=Gr5IsAdvhRS9RiZQ6M;mp|-zSY*GDO3ezC&6#g(sq2U36RmSnBoIu zo=%guwg85tOSryCli0?pH<}8cQKXuw7A8$8)#N2M$`$`k+S(!@C)(?rV7|X?5;!r1 z-@&As4Udtk6SUC$wUq}Tfs?U_3G^r<-l{U2J}^a`y!!%OY*W&drXWsmp~{q5qSh7w z35c;JhdKO4dy=N)JZF=*g1me9iA7r^aIJhTZVIS&qb*5ODoR-4CPq{m(l4}wF^uxH zMU1(j%y=u3rag)&Hi+;{dZa|QfibvzZG$_1FiUyI79>qk58Q!&rIOCmf~4sHxndh` z)dlnaZ8$k}PXf(0CSG2n8jh!gE%)Quv^IZtlQZ#&jM0vy>Bv0Xl(|J-%8=)2o9@On ziPu~3ZN}alzS^3k>2HdFKH(Dxi7{RhH{6dg$!(wbST3xs|m+IW(rnWw5 zY9HnD_&2RDvpG>CjU^oV;sKP|nA7O+q$yK=mcH3B_=<-{)^M19a*^7WG^M@dZ!4+G zFKv^+iPk<4&PWhrw64tN*f^ONoJBlP)V`CZhNl|bi!Df+4jmax$_YEWI3@?M-ZGn~ z>}S}|xYixj&;Zfs)S5J1xmIC)VW2y4A(@cAS!dETU;W3|Fn#M+Mg*1ssmw3qikctB zMj<^7E>$($cnw4vG&Z%B+4N_h|MW3*f>-t70`<?ovfp{1%y;(>sHbl93s zn(mM8G@$ok<=PUqo6@Ia8}*dg$Q=?mPOo0AVACRH&TF{{g{I`jo<=+#vxLnV!i=kK^xi5$~5R#b#XQ>$hSr~-LUcLhW5aU3tig*0Q6}YgT2_l$O;e=?+zU zquTW7dSEKdtgy_6hJ5E-nwp+a^-ZD-OAJlS7$mRlHb+fQXqj6&8*3I^Bu$!UsTCp&d*;I5lfpdLm#Hbl{65q@dB*_@VT}r1# zR~nVm$XSd*LsCi7l0Fqp6jq!xWxKJ&)KK$KbI`04ID2OBj)tLdVJ4nJTI1H>A&hU8(vlvqk?BuY${6+= zMhuI9%&&L}U>?l|_lPlyO5lOl;22(S#6@#-(#cHK)9@88%pBRXXsEcPYbfo8Nwg2C zQ`l8JX?m}!OiSo)FO_U)5jiY7)BfW&FTohPJv9fL9CpQmz$DsPiK;JjAE17_hE7Ew zhHEq+*~KnKz{1Q@61ZAim>CO|3Plu?2Nun)L9G66f}SyrY9s{?*8aWhYXI*CZr`br zG1f5hziNFsP=;difW_^=7GD8UGHH6Om))z>FWnWzcLP@}kO9r19i_``)Rnr>+(OvD zO2efB6DXCfnlav?@`k!TewpD=faLAS@uBE^nC?RL)i|L?zNP&c#vONBG+7)z-cut zY{vLVhfV_AiIIlF(K0q-pHnW=U=QPyrdwljW*Tdho|P>iy;HW5a_xr`XVJIW?C|r2 zHQjVLP&21OljLiW#*axm$R0#4>dVy;KaKyR`D&c*8D5 z)O^ZXbdKcYhIN#6MI}u^E-}Oe`f0^m7C$`0a9sMPo@2iex7nZ!X^dhHkMky%_5hdZ zSj@GjmISl7{3zYi2!%FU?8V@=D3>x<;QS|0~Cv}b1Lh# zkT2v$^z{%Aitnx_lI%TX1e=_VgD~m*>*Cj(rH4Vrz$=P9#~Hv_+RSxv3DVu;=>K;ww6XAmwYkGpsbbz8BkqY6hMBnOuy`n`kFDMGJCtRUR3 z!sJF-1!+|lboy?7brOStl-zvo3KF7-5wM3tY{_>}_}`9l`N#4=LiI%#f>e-t%B z9#o8#7f_Z2?l9+dZ~MR_VeTF-oe%4*K~eaeY9DaLyL-6uD$Ezd6a$L!1$&6$ledTO zK}3Q{#OS1n9XhjeAzG%PpE*5R*q2N(0#Vd;DdpLve=GZ>g9EHYv_^cd~bKQjO zLGm7+Pfr1NOTl7sc4^h6-ak(#^B2dZ);5y!VL9|igX5Awov>Hzx6;4vp+kob9XfRA e(4j+z^!OjB9jyEhAyhE{0000E`C4~jn{Grh46iQo`OCw@?C$3Byx(+ds>k_Fwu9+EO}D68T-{~@x93f_syZCs$o?&H zI%>LQ72!Ac?=K#%n{Hbrcv3s~7yp5k+WTg8@W0SSWea}dYtt<(iPKOAv)iVdSPsv@ z4rbY9(~T^HV@C(G?6~P>mOyxoE-`JonZ@ystAmGZPtTk^ylc9lMe*I)!7TgIbW;n$ z-N7vDHQm-ixWvB9U+J$GO*gg(kSTr)j*foO2Z!j{{G<-9KKU0u`POi23*ee&YW*2~ zba;$0fnI!He|80*%^PiP0Z=C*m__@4;&I>&=fuHJVAluO+&3M0NQ4Yy=NZanX5)xTLihI_2=-+TO+M40Ah+i z;wh4-o*QUw5xha$rSyx8aTs|iG9pj#+yy-M6P{}wkSSP}d7n%l5iLLg2fp1LAZ#0S%Xl_Wczk~054}7ON{DIbn%4QCV zF{rWO%-Iya)z+jbR0U-x!Ee#hc6_Y~kkPQ1;sa!!PLsE`0EVPXxV}k~*v6_inhKv$ zq?)M~CQT{Th_NMyIs8U@lBVQ5XOp;synFbG zMO!3rt$ZzR3aECYElE=;VjFJN1@r%HI5~7r0?jriUS6Xbj;Dkz z_v6{LHh*`MGx3Rx(T=3)$UNMXxkX;ekmqTe?#4BV*IV#y#@-yh+M1;4Z;F6E;S&gn zFbHIZJ+q#vNu#_^BDeGUAQ-w>fGa|wmxZUALa7+H?1$TIZ-2xB^>(V0hHO8 z)9CP|DN}xyzS%MOiibwlaF~8_k=m9trM={DE2+ybZIi%>);JG12@jhtr=0M-z0wePSU#xjF4{)i{ca`WwIh^lL^ag#(^Z-5>`y5 zxZzgFKs=MM%*K+?Hn3`7IG8d^^lD9-YP`LNZ#s?lc^_Nu0y0LbGMn7j!)&6P$74De zWjiW0CQbbpTg~+rGu=EH{}#(^k|a&Hhuy8N2r>MXkZNbjN^Y@m*6 zMnyS%Awii9sR~nFE}W@d?4bZ>aU5E>>dR~(m#`R)^Wo9Md*dRD?%Ut7Z(S0(nn(>5iP9)udusGb*ZqG3Z~c1qJA;zdQ zX-ZpXQx|zc?Oj1#dB}>^vZK&zR%OzZmenWe4pn=j+VtppU@FY4u*`;reCJ%6nx0Vg zO`;4-3{A`!B(Lo@M@>&?nOiy=YZhE2O`4Lq+}GDNkLOm~EQaku3=<|z@wbIp&{Rg1 znQ5I`u`rd{RCG6ibA4#Us2Oz<-^>#v$r!_3N~c9v8kN(?S&TtLQc2R3>@*Xd(nT%* z-iK#R+E0`;MJG}*(@Gb$R?xb(YzN*=P`)sevUn=hDtl3DHmEkHqMamZN-XfEyGwWr zvU*gKc9x}2V3|!x2^<%OMO);K>FX4{K~vGDHBz!L6B99}$IHcDian(PH0Okz{8C9% zj*RU0-{4D0v|tW)mRb6QCr!&Rh9<9BI#3T)Uxk|KT$rg8R-80tyRpR7Q1ei8(5w?U zduH&ChM{m_CZ0lCH%@)-dwFYJE9ShGO!7#qGcrUjb4w zX?m=e-K*3u-4(@m16M4N0nMQurORy8mAcT}LfF4b!=(ZfD3z_6G2Wr_hPpoFpeR)r zuxA6dF-_7`wW%7HL&{2-MCn)rLL#gJEq~JV8mnP4-9?pVT`{MU^&bLlA*Z>5!pwmd z5^Gyg4hd@xpq+-w@)NxRWUO>i8$HP~%7rs>UxoZIOqy0uW;0UG_-b;HOw92(OXD~X zrFc`_O)w&fI8)!fA-P%cP9@)l>;@)Oswm7Hu({Ffsf-l4<6$~8FD#^ zPxzelYl7!vP)7Q?`OR1BcIwDR6~!!AYCe9BsMj^yNqb(D2QB~3vtF~kJ= zX~kO>KRm;5T>7S-W4{r%*`N$*jA9Ot^Cp+}0GH`l%(bVM1hcsODBbXA2|{E~6_OSq zJWsZ0flCB+Crv4VY{y%ml_+AhaI3jGLl|hi9DWP#SAbBu$mV@AHoS9WBhV(v)$BSF z>%7pNG~ZAm2WTZq^dfc`FMnaoeRUSlbh*^)=zo1Be< zFzNj3;@6y|he5``D~dhG8NgCI<6Dj<^Zm!13TW`8DOJh(%$N+&*!>n`_=4V&SQFk; zc!4q-O2;|xNr5OEn=a31qM;U?<<7Q{1pc|#N$zmobpt0$d0`3t79BjX>fIO85T9@4 z5)zi&g;ZWSUU&yMJJP0m+i{dbFV>@2?zIcjP;+`oEay99MBzKSPyQHh!6qRfqM%Va zr^pv_cChWj?E8qm;zwngc&Q2aj8`4E}%mzyc!h!1YN{b z#}co9C8~%#LoD41^s0rPf^ojMhc4=P8u(_v$Y+f#X^{@T8D7Ig<5Fvcm= zdBm2bNMnqFrQEL=H{^Q{xulv6q+y?fKNp+r?~6g=l_i8tbPV@dO|7_Mh~6wfhm4^3B7Ww7|`nz>+fN5CLyK)cWe0K zxDQ;KH{cfruD^o+2^~{^53kI_O@;A9t48XS@RE(7uP?iMlFz0n|`@kb%?jA0k z59_Q!QTUu{A8^IHd${r{%ooEH1B&qldx+taw}2-mm2)C0h4uGv%eAW7v1vyScdT&e^@WjWMGyDBXutL@ z7H#OE;885aD~jfOID_!!*gn=Ag~ogMtQ?Ya-GuBx@*bW~PXTvJ!D4ZCY1O6PKTjv~ z7ssX6Hj?vUIrK+^I&|pJp+krC_#dbpto#olR51Vm002ov JPDHLkV1hwO&o}@8 literal 0 HcmV?d00001 diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 498ecfce22..8b6db22e6f 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -46,13 +46,13 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler { channel.setMethodCallHandler(null) } - private fun writeEnvelope(envelope: String): Boolean { + private fun writeEnvelope(envelope: ByteArray): Boolean { if (!this::options.isInitialized || options.outboxPath.isNullOrEmpty()) { return false } val file = File(options.outboxPath, UUID.randomUUID().toString()) - file.writeText(envelope, Charsets.UTF_8) + file.writeBytes(envelope) return true } @@ -124,9 +124,9 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler { private fun captureEnvelope(call: MethodCall, result: Result) { val args = call.arguments() as List if (args.isNotEmpty()) { - val event = args.first() as String? + val event = args.first() as ByteArray? - if (!event.isNullOrEmpty()) { + if (event != null && event.size > 0) { if (!writeEnvelope(event)) { result.error("3", "SentryOptions or outboxPath are null or empty", null) } diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index f2aff5dfec..0a152db5dc 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -258,65 +258,21 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { private func captureEnvelope(_ call: FlutterMethodCall, result: @escaping FlutterResult) { guard let arguments = call.arguments as? [Any], !arguments.isEmpty, - let event = arguments.first as? String else { + let data = (arguments.first as? FlutterStandardTypedData)?.data else { print("Envelope is null or empty !") result(FlutterError(code: "2", message: "Envelope is null or empty", details: nil)) return } - - do { - let envelope = try parseJsonEnvelope(event) - SentrySDK.capture(envelope) - result("") - } catch { - print("Cannot parse the envelope json !") - result(FlutterError(code: "3", message: "Cannot parse the envelope json", details: nil)) + // We need to replace this when we have the possibility to access envelope deserialization. + guard let sentrySerialization = NSClassFromString("SentrySerialization") as? NSObject.Type, + let unmanaged = sentrySerialization.perform(NSSelectorFromString("envelopeWithData:"), with: data), + let envelope = unmanaged.takeUnretainedValue() as? SentryEnvelope else { + print("Cannot parse the envelope data") + result(FlutterError(code: "3", message: "Cannot parse the envelope data", details: nil)) return } - } - - private func parseJsonEnvelope(_ data: String) throws -> SentryEnvelope { - let parts = data.split(separator: "\n") - - let envelopeParts: [[String: Any]] = try parts.map({ part in - guard let dict = parseJson(text: "\(part)") else { - throw NSError() - } - return dict - }) - - let rawEnvelopeHeader = envelopeParts[0] - guard let eventId = rawEnvelopeHeader["event_id"] as? String, - let itemType = envelopeParts[1]["type"] as? String else { - throw NSError() - } - - let sdkInfo = SentrySdkInfo(dict: rawEnvelopeHeader) - let sentryId = SentryId(uuidString: eventId) - let envelopeHeader = SentryEnvelopeHeader.init(id: sentryId, andSdkInfo: sdkInfo) - - let payload = envelopeParts[2] - - let data = try JSONSerialization.data(withJSONObject: payload, options: .init(rawValue: 0)) - - let itemHeader = SentryEnvelopeItemHeader(type: itemType, length: UInt(data.count)) - let sentryItem = SentryEnvelopeItem(header: itemHeader, data: data) - - return SentryEnvelope.init(header: envelopeHeader, singleItem: sentryItem) - } - - func parseJson(text: String) -> [String: Any]? { - guard let data = text.data(using: .utf8) else { - print("Invalid UTF8 String : \(text)") - return nil - } - - do { - let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] - return json - } catch { - print("json parsing error !") - } - return nil + SentrySDK.capture(envelope) + result("") + return } } diff --git a/flutter/lib/src/file_system_transport.dart b/flutter/lib/src/file_system_transport.dart index eb718d93f5..68e47d3cde 100644 --- a/flutter/lib/src/file_system_transport.dart +++ b/flutter/lib/src/file_system_transport.dart @@ -1,4 +1,4 @@ -import 'dart:convert'; +import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:sentry/sentry.dart'; @@ -10,19 +10,11 @@ class FileSystemTransport implements Transport { final SentryOptions _options; @override - Future sendSentryEvent(SentryEvent event) async { - final envelope = SentryEnvelope.fromEvent(event, _options.sdk); - return await sendSentryEnvelope(envelope); - } - - @override - Future sendSentryEnvelope(SentryEnvelope envelope) async { + Future send(SentryEnvelope envelope) async { final envelopeData = []; await envelope.envelopeStream().forEach(envelopeData.addAll); - - final envelopeString = utf8.decode(envelopeData); - - final args = [envelopeString]; + // https://flutter.dev/docs/development/platform-integration/platform-channels#codec + final args = [Uint8List.fromList(envelopeData)]; try { await _channel.invokeMethod('captureEnvelope', args); } catch (error) { diff --git a/flutter/test/file_system_transport_test.dart b/flutter/test/file_system_transport_test.dart index 6a0f7fe511..680deb073d 100644 --- a/flutter/test/file_system_transport_test.dart +++ b/flutter/test/file_system_transport_test.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -25,8 +26,11 @@ void main() { final transport = fixture.getSut(_channel); final event = SentryEvent(); + final sdkVersion = + SdkVersion(name: 'fixture-sdkName', version: 'fixture-sdkVersion'); - final sentryId = await transport.sendSentryEvent(event); + final envelope = SentryEnvelope.fromEvent(event, sdkVersion); + final sentryId = await transport.send(envelope); expect(sentryId, sentryId); }); @@ -37,8 +41,12 @@ void main() { }); final transport = fixture.getSut(_channel); + final event = SentryEvent(); + final sdkVersion = + SdkVersion(name: 'fixture-sdkName', version: 'fixture-sdkVersion'); - final sentryId = await transport.sendSentryEvent(SentryEvent()); + final envelope = SentryEnvelope.fromEvent(event, sdkVersion); + final sentryId = await transport.send(envelope); expect(SentryId.empty(), sentryId); }); @@ -53,10 +61,14 @@ void main() { final event = SentryEvent(message: SentryMessage('hi I am a special char ◤')); - await transport.sendSentryEvent(event); + final sdkVersion = + SdkVersion(name: 'fixture-sdkName', version: 'fixture-sdkVersion'); + final envelope = SentryEnvelope.fromEvent(event, sdkVersion); + await transport.send(envelope); final envelopeList = arguments as List; - final envelopeString = envelopeList.first as String; + final envelopeData = envelopeList.first as Uint8List; + final envelopeString = utf8.decode(envelopeData); final lines = envelopeString.split('\n'); final envelopeHeader = lines.first; final itemHeader = lines[1]; diff --git a/flutter/test/mocks.mocks.dart b/flutter/test/mocks.mocks.dart index 0b1ff36b40..2ee6b6f00e 100644 --- a/flutter/test/mocks.mocks.dart +++ b/flutter/test/mocks.mocks.dart @@ -1,5 +1,5 @@ -// Mocks generated by Mockito 5.0.3 from annotations -// in sentry_flutter/test/mocks.dart. +// Mocks generated by Mockito 5.0.7 from annotations +// in sentry_flutter/example/ios/.symlinks/plugins/sentry_flutter/test/mocks.dart. // Do not manually edit this file. import 'dart:async' as _i4; @@ -17,6 +17,10 @@ import 'package:sentry/src/transport/transport.dart' as _i9; // ignore_for_file: comment_references // ignore_for_file: unnecessary_parenthesis +// ignore_for_file: prefer_const_constructors + +// ignore_for_file: avoid_redundant_argument_values + class _FakeSentryId extends _i1.Fake implements _i2.SentryId {} class _FakeHub extends _i1.Fake implements _i3.Hub {} @@ -48,7 +52,7 @@ class MockHub extends _i1.Mock implements _i3.Hub { #hint: hint, #withScope: withScope }), - returnValue: Future.value(_FakeSentryId())) + returnValue: Future<_i2.SentryId>.value(_FakeSentryId())) as _i4.Future<_i2.SentryId>); @override _i4.Future<_i2.SentryId> captureException(dynamic throwable, @@ -61,7 +65,7 @@ class MockHub extends _i1.Mock implements _i3.Hub { #hint: hint, #withScope: withScope }), - returnValue: Future.value(_FakeSentryId())) + returnValue: Future<_i2.SentryId>.value(_FakeSentryId())) as _i4.Future<_i2.SentryId>); @override _i4.Future<_i2.SentryId> captureMessage(String? message, @@ -80,7 +84,7 @@ class MockHub extends _i1.Mock implements _i3.Hub { #hint: hint, #withScope: withScope }), - returnValue: Future.value(_FakeSentryId())) + returnValue: Future<_i2.SentryId>.value(_FakeSentryId())) as _i4.Future<_i2.SentryId>); @override void addBreadcrumb(_i7.Breadcrumb? crumb, {dynamic hint}) => super @@ -95,7 +99,7 @@ class MockHub extends _i1.Mock implements _i3.Hub { returnValue: _FakeHub()) as _i3.Hub); @override _i4.Future close() => (super.noSuchMethod(Invocation.method(#close, []), - returnValue: Future.value(null), + returnValue: Future.value(null), returnValueForMissingStub: Future.value()) as _i4.Future); @override void configureScope(_i3.ScopeCallback? callback) => @@ -112,13 +116,8 @@ class MockTransport extends _i1.Mock implements _i9.Transport { } @override - _i4.Future<_i2.SentryId> sendSentryEvent(_i5.SentryEvent? event) => - (super.noSuchMethod(Invocation.method(#sendSentryEvent, [event]), - returnValue: Future.value(_FakeSentryId())) - as _i4.Future<_i2.SentryId>); - @override - _i4.Future<_i2.SentryId> sendSentryEnvelope(_i10.SentryEnvelope? envelope) => - (super.noSuchMethod(Invocation.method(#sendSentryEnvelope, [envelope]), - returnValue: Future.value(_FakeSentryId())) + _i4.Future<_i2.SentryId> send(_i10.SentryEnvelope? envelope) => + (super.noSuchMethod(Invocation.method(#send, [envelope]), + returnValue: Future<_i2.SentryId>.value(_FakeSentryId())) as _i4.Future<_i2.SentryId>); } From 739fe04edd3601e9c776a68527523a6e30446d05 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 28 May 2021 14:16:40 +0200 Subject: [PATCH 79/83] Move new changelog items to unrealeased section --- CHANGELOG.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b6429d377..ffd1134e6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Unreleased +## Breaking Changes: + +* Feat: Support envelope based transport for events (#391) * Feature: Envelope Only Transport API #426 (#463) + * The method signature of `Transport` changed from `Future send(SentryEvent event)` to `Future send(SentryEnvelope envelope)` # 5.1.0-beta.1 @@ -24,11 +28,6 @@ * Feature: Add `withScope` callback to capture methods (#463) * Fix: Add missing properties `language`, `screenHeightPixels` and `screenWidthPixels` to `SentryDevice` (#465) -## Breaking Changes: - -* Feat: Support envelope based transport for events (#391) - * The method signature of `Transport` changed from `Future send(SentryEvent event)` to `Future send(SentryEnvelope envelope)` - ## Sentry Self Hosted Compatibility * This version of the `sentry` Dart package requires [Sentry server >= v20.6.0](https://github.com/getsentry/onpremise/releases). This only applies to on-premise Sentry, if you are using sentry.io no action is needed. From 60cde6dc13e00fdb771fc93f56d78897891e10e3 Mon Sep 17 00:00:00 2001 From: denrase Date: Fri, 28 May 2021 14:45:02 +0200 Subject: [PATCH 80/83] Bump sentry-cocoa version to 7.1.3, Use PrivateSentrySDKOnly.envelope(with: data) --- flutter/ios/Classes/SentryFlutterPluginApple.swift | 6 ++---- flutter/ios/sentry_flutter.podspec | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/flutter/ios/Classes/SentryFlutterPluginApple.swift b/flutter/ios/Classes/SentryFlutterPluginApple.swift index 0a152db5dc..b6837b60f3 100644 --- a/flutter/ios/Classes/SentryFlutterPluginApple.swift +++ b/flutter/ios/Classes/SentryFlutterPluginApple.swift @@ -263,10 +263,8 @@ public class SentryFlutterPluginApple: NSObject, FlutterPlugin { result(FlutterError(code: "2", message: "Envelope is null or empty", details: nil)) return } - // We need to replace this when we have the possibility to access envelope deserialization. - guard let sentrySerialization = NSClassFromString("SentrySerialization") as? NSObject.Type, - let unmanaged = sentrySerialization.perform(NSSelectorFromString("envelopeWithData:"), with: data), - let envelope = unmanaged.takeUnretainedValue() as? SentryEnvelope else { + + guard let envelope = PrivateSentrySDKOnly.envelope(with: data) else { print("Cannot parse the envelope data") result(FlutterError(code: "3", message: "Cannot parse the envelope data", details: nil)) return diff --git a/flutter/ios/sentry_flutter.podspec b/flutter/ios/sentry_flutter.podspec index 73464cb967..1b5092e8d5 100644 --- a/flutter/ios/sentry_flutter.podspec +++ b/flutter/ios/sentry_flutter.podspec @@ -12,7 +12,7 @@ Sentry SDK for Flutter with support to native through sentry-cocoa. :tag => s.version.to_s } s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' - s.dependency 'Sentry', '~> 7.0.3' + s.dependency 'Sentry', '~> 7.1.3' s.ios.dependency 'Flutter' s.osx.dependency 'FlutterMacOS' s.ios.deployment_target = '9.0' From 7650f82febaea1ac1f3e0a21b75d1a48687ada7b Mon Sep 17 00:00:00 2001 From: denrase Date: Mon, 31 May 2021 10:43:11 +0200 Subject: [PATCH 81/83] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e8bbb3d6e..434386b6ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,8 @@ ## Breaking Changes: * Feat: Support envelope based transport for events (#391) -* Feature: Envelope Only Transport API #426 (#463) * The method signature of `Transport` changed from `Future send(SentryEvent event)` to `Future send(SentryEnvelope envelope)` + * Bumped sentry-cocoa to v7.1.3 * Fix: Merge user from event and scope (#467) # 5.1.0-beta.1 From fbfc40985eede00eb169fa8275868a73d998cc5f Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 8 Jun 2021 09:51:42 +0200 Subject: [PATCH 82/83] Update changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e958296749..5bba2b7760 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,6 @@ * Feat: Support envelope based transport for events (#391) * The method signature of `Transport` changed from `Future send(SentryEvent event)` to `Future send(SentryEnvelope envelope)` - * Bumped sentry-cocoa to v7.1.3 * Remove `Sentry.currentHub` (#490) # 5.1.0 From 99af50ac2f334f07f7b06f4e3ea2397d402052da Mon Sep 17 00:00:00 2001 From: denrase Date: Tue, 8 Jun 2021 10:09:19 +0200 Subject: [PATCH 83/83] Fix klint issue --- .../src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt index 923b3af057..6004c2e75e 100644 --- a/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt @@ -45,7 +45,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler { channel.setMethodCallHandler(null) } - + private fun writeEnvelope(envelope: ByteArray): Boolean { val options = HubAdapter.getInstance().options if (options.outboxPath.isNullOrEmpty()) {