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 0000000000..533f5e4f6f Binary files /dev/null and b/dart/test_resources/envelope-with-image.envelope differ diff --git a/dart/test_resources/sentry.png b/dart/test_resources/sentry.png new file mode 100644 index 0000000000..2225be472d Binary files /dev/null and b/dart/test_resources/sentry.png differ 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>); }