From f12d09db045001829805941d7d2f050867de7409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Andra=C5=A1ec?= Date: Mon, 8 Jan 2024 10:14:47 +0000 Subject: [PATCH] Add isar breadcrumbs (#1800) --- CHANGELOG.md | 1 + isar/lib/src/sentry_span_helper.dart | 18 + isar/pubspec.yaml | 4 +- isar/test/sentry_isar_collection_test.dart | 452 +++++++++++++++++++++ isar/test/sentry_isar_test.dart | 229 ++++++++++- 5 files changed, 687 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8154a73e70..a099b054d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Add `ConnectivityIntegration` for web ([#1765](https://github.com/getsentry/sentry-dart/pull/1765)) - We only get the info if online/offline on web platform. The added breadcrumb is set to either `wifi` or `none`. - APM for isar ([#1726](https://github.com/getsentry/sentry-dart/pull/1726)) +- Add isar breadcrumbs ([#1800](https://github.com/getsentry/sentry-dart/pull/1800)) - Starting with Flutter 3.16, Sentry adds the [`appFlavor`](https://api.flutter.dev/flutter/services/appFlavor-constant.html) to the `flutter_context` ([#1799](https://github.com/getsentry/sentry-dart/pull/1799)) ### Dependencies diff --git a/isar/lib/src/sentry_span_helper.dart b/isar/lib/src/sentry_span_helper.dart index 69fc6e5c39..ec1823c7ca 100644 --- a/isar/lib/src/sentry_span_helper.dart +++ b/isar/lib/src/sentry_span_helper.dart @@ -38,28 +38,46 @@ class SentrySpanHelper { // ignore: invalid_use_of_internal_member span?.origin = _origin; + var breadcrumb = Breadcrumb( + message: description, + data: {}, + type: 'query', + ); + span?.setData(SentryIsar.dbSystemKey, SentryIsar.dbSystem); if (dbName != null) { span?.setData(SentryIsar.dbNameKey, dbName); + breadcrumb.data?[SentryIsar.dbNameKey] = dbName; } if (collectionName != null) { span?.setData(SentryIsar.dbCollectionKey, collectionName); + breadcrumb.data?[SentryIsar.dbCollectionKey] = collectionName; } try { final result = await execute(); + span?.status = SpanStatus.ok(); + breadcrumb.data?['status'] = 'ok'; return result; } catch (exception) { span?.throwable = exception; span?.status = SpanStatus.internalError(); + breadcrumb.data?['status'] = 'internal_error'; + breadcrumb = breadcrumb.copyWith( + level: SentryLevel.warning, + ); + rethrow; } finally { await span?.finish(); + + // ignore: invalid_use_of_internal_member + await _hub.scope.addBreadcrumb(breadcrumb); } } } diff --git a/isar/pubspec.yaml b/isar/pubspec.yaml index 43c0907472..df066786f8 100644 --- a/isar/pubspec.yaml +++ b/isar/pubspec.yaml @@ -12,13 +12,13 @@ environment: dependencies: isar: ^3.1.0 isar_flutter_libs: ^3.1.0 # contains Isar Core - sentry: 7.12.0 + sentry: 7.14.0 meta: ^1.3.0 path: ^1.8.3 dev_dependencies: isar_generator: ^3.1.0 - build_runner: ^2.4.2 + build_runner: ^2.4.6 lints: ^3.0.0 flutter_test: sdk: flutter diff --git a/isar/test/sentry_isar_collection_test.dart b/isar/test/sentry_isar_collection_test.dart index c3286a2971..de55ac5989 100644 --- a/isar/test/sentry_isar_collection_test.dart +++ b/isar/test/sentry_isar_collection_test.dart @@ -36,6 +36,22 @@ void main() { expect(span?.throwable, error); } + void verifyBreadcrumb( + String message, + Breadcrumb? crumb, { + String status = 'ok', + }) { + expect( + crumb?.message, + message, + ); + expect(crumb?.type, 'query'); + expect(crumb?.data?['status'], status); + if (status != 'ok') { + expect(crumb?.level, SentryLevel.warning); + } + } + group('add spans', () { late Fixture fixture; @@ -44,6 +60,7 @@ void main() { when(fixture.hub.options).thenReturn(fixture.options); when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.hub.scope).thenReturn(fixture.scope); await fixture.setUp(); }); @@ -204,6 +221,7 @@ void main() { when(fixture.hub.options).thenReturn(fixture.options); when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.hub.scope).thenReturn(fixture.scope); when(fixture.isarCollection.name).thenReturn(Fixture.dbCollection); await fixture.setUp(); @@ -426,6 +444,435 @@ void main() { ); }); }); + + group('add breadcrumbs', () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + + when(fixture.hub.options).thenReturn(fixture.options); + when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.hub.scope).thenReturn(fixture.scope); + + await fixture.setUp(); + }); + + tearDown(() async { + await fixture.tearDown(); + }); + + test('clear adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().clear(); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('clear', breadcrumb); + }); + + test('count adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().count(); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('count', breadcrumb); + }); + + test('delete adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().delete(0); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('delete', breadcrumb); + }); + + test('deleteAll adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().deleteAll([0]); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('deleteAll', breadcrumb); + }); + + test('deleteAllByIndex adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().putByIndex('name', Person()..name = 'Joe'); + await fixture.getSut().deleteAllByIndex('name', []); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[2]; + verifyBreadcrumb('deleteAllByIndex', breadcrumb); + }); + + test('deleteByIndex adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().putByIndex('name', Person()..name = 'Joe'); + await fixture.getSut().deleteByIndex('name', []); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[2]; + verifyBreadcrumb('deleteByIndex', breadcrumb); + }); + + test('get adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().get(1); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('get', breadcrumb); + }); + + test('getAll adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().getAll([1]); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('getAll', breadcrumb); + }); + + test('getAllByIndex adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().getAllByIndex('name', []); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('getAllByIndex', breadcrumb); + }); + + test('getByIndex adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().getByIndex('name', []); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('getByIndex', breadcrumb); + }); + + test('getSize adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().getSize(); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('getSize', breadcrumb); + }); + + test('importJson adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().importJson([]); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('importJson', breadcrumb); + }); + + test('importJsonRaw adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + final query = fixture.getSut().buildQuery(); + Uint8List jsonRaw = Uint8List.fromList([]); + await query.exportJsonRaw((raw) { + jsonRaw = Uint8List.fromList(raw); + }); + await fixture.getSut().importJsonRaw(jsonRaw); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('importJsonRaw', breadcrumb); + }); + + test('put adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().put(Person()); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('put', breadcrumb); + }); + + test('putAll adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().putAll([Person()]); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('putAll', breadcrumb); + }); + + test('putAllByIndex adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().putAllByIndex('name', [Person()]); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('putAllByIndex', breadcrumb); + }); + + test('putByIndex adds breadcrumb', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().putByIndex('name', Person()); + }); + final breadcrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('putByIndex', breadcrumb); + }); + }); + + group('add error breadcrumbs', () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + + when(fixture.hub.options).thenReturn(fixture.options); + when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.hub.scope).thenReturn(fixture.scope); + when(fixture.isarCollection.name).thenReturn(Fixture.dbCollection); + + await fixture.setUp(); + }); + + tearDown(() async { + await fixture.tearDown(); + }); + + test('throwing clear adds error breadcrumb', () async { + when(fixture.isarCollection.clear()).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).clear(); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'clear', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing count adds error breadcrumb', () async { + when(fixture.isarCollection.count()).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).count(); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'count', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing delete adds error breadcrumb', () async { + when(fixture.isarCollection.delete(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).delete(0); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'delete', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing deleteAll adds error breadcrumb', () async { + when(fixture.isarCollection.deleteAll(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).deleteAll([0]); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'deleteAll', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing deleteAllByIndex adds error breadcrumb', () async { + when(fixture.isarCollection.deleteAllByIndex(any, any)) + .thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).deleteAllByIndex('name', []); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'deleteAllByIndex', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing deleteByIndex adds error breadcrumb', () async { + when(fixture.isarCollection.deleteByIndex(any, any)) + .thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).deleteByIndex('name', []); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'deleteByIndex', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing get adds error breadcrumb', () async { + when(fixture.isarCollection.get(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).get(1); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'get', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing getAll adds error breadcrumb', () async { + when(fixture.isarCollection.getAll(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).getAll([1]); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'getAll', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing getAllByIndex adds error breadcrumb', () async { + when(fixture.isarCollection.getAllByIndex(any, any)) + .thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).getAllByIndex('name', []); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'getAllByIndex', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing getByIndex adds error breadcrumb', () async { + when(fixture.isarCollection.getByIndex(any, any)) + .thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).getByIndex('name', []); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'getByIndex', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing getSize adds error breadcrumb', () async { + when(fixture.isarCollection.getSize()).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).getSize(); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'getSize', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing importJson adds error breadcrumb', () async { + when(fixture.isarCollection.importJson(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).importJson([]); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'importJson', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing importJsonRaw adds error breadcrumb', () async { + when(fixture.isarCollection.importJsonRaw(any)) + .thenThrow(fixture.exception); + try { + await fixture + .getSut(injectMock: true) + .importJsonRaw(Uint8List.fromList([])); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'importJsonRaw', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing put adds error breadcrumb', () async { + when(fixture.isarCollection.put(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).put(Person()); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'put', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing putAll adds error breadcrumb', () async { + when(fixture.isarCollection.putAll(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).putAll([Person()]); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'putAll', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing putAllByIndex adds error breadcrumb', () async { + when(fixture.isarCollection.putAllByIndex(any, any)) + .thenThrow(fixture.exception); + try { + await fixture + .getSut(injectMock: true) + .putAllByIndex('name', [Person()]); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'putAllByIndex', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing putByIndex adds error breadcrumb', () async { + when(fixture.isarCollection.putByIndex(any, any)) + .thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).putByIndex('name', Person()); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'putByIndex', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + }); } class Fixture { @@ -440,6 +887,7 @@ class Fixture { final _context = SentryTransactionContext('name', 'operation'); late final tracer = SentryTracer(_context, hub); late Isar sentryIsar; + late final scope = Scope(options); Future setUp() async { // Make sure to use flutter test -j 1 to avoid tests running in parallel. This would break the automatic download. @@ -473,4 +921,8 @@ class Fixture { SentrySpan? getCreatedSpan() { return tracer.children.last; } + + Breadcrumb? getCreatedBreadcrumb() { + return hub.scope.breadcrumbs.last; + } } diff --git a/isar/test/sentry_isar_test.dart b/isar/test/sentry_isar_test.dart index 70cc23c832..222226fa53 100644 --- a/isar/test/sentry_isar_test.dart +++ b/isar/test/sentry_isar_test.dart @@ -15,19 +15,13 @@ import 'mocks/mocks.mocks.dart'; import 'person.dart'; void main() { - void verifySpan( - String description, - SentrySpan? span, { - bool checkName = false, - }) { + void verifySpan(String description, SentrySpan? span) { expect(span?.context.operation, SentryIsar.dbOp); expect(span?.context.description, description); expect(span?.status, SpanStatus.ok()); // ignore: invalid_use_of_internal_member expect(span?.origin, SentryTraceOrigins.autoDbIsar); - if (checkName) { - expect(span?.data[SentryIsar.dbNameKey], Fixture.dbName); - } + expect(span?.data[SentryIsar.dbNameKey], Fixture.dbName); } void verifyErrorSpan(String description, SentrySpan? span, Exception error) { @@ -39,6 +33,23 @@ void main() { expect(span?.throwable, error); } + void verifyBreadcrumb( + String message, + Breadcrumb? crumb, { + String status = 'ok', + }) { + expect( + crumb?.message, + message, + ); + expect(crumb?.type, 'query'); + expect(crumb?.data?[SentryIsar.dbNameKey], Fixture.dbName); + expect(crumb?.data?['status'], status); + if (status != 'ok') { + expect(crumb?.level, SentryLevel.warning); + } + } + group('add spans', () { late Fixture fixture; @@ -47,6 +58,7 @@ void main() { when(fixture.hub.options).thenReturn(fixture.options); when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.hub.scope).thenReturn(fixture.scope); await fixture.setUp(); }); @@ -57,7 +69,7 @@ void main() { test('open adds span', () async { final span = fixture.getCreatedSpan(); - verifySpan('open', span, checkName: true); + verifySpan('open', span); }); test('clear adds span', () async { @@ -65,37 +77,37 @@ void main() { await fixture.sut.clear(); }); final span = fixture.getCreatedSpan(); - verifySpan('clear', span, checkName: true); + verifySpan('clear', span); }); test('close adds span', () async { await fixture.sut.close(); final span = fixture.getCreatedSpan(); - verifySpan('close', span, checkName: true); + verifySpan('close', span); }); test('copyToFile adds span', () async { await fixture.sut.copyToFile(fixture.copyPath); final span = fixture.getCreatedSpan(); - verifySpan('copyToFile', span, checkName: true); + verifySpan('copyToFile', span); }); test('getSize adds span', () async { await fixture.sut.getSize(); final span = fixture.getCreatedSpan(); - verifySpan('getSize', span, checkName: true); + verifySpan('getSize', span); }); test('txn adds span', () async { await fixture.sut.txn(() async {}); final span = fixture.getCreatedSpan(); - verifySpan('txn', span, checkName: true); + verifySpan('txn', span); }); test('writeTxn adds span', () async { await fixture.sut.writeTxn(() async {}); final span = fixture.getCreatedSpan(); - verifySpan('writeTxn', span, checkName: true); + verifySpan('writeTxn', span); }); }); @@ -107,6 +119,8 @@ void main() { when(fixture.hub.options).thenReturn(fixture.options); when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.hub.scope).thenReturn(fixture.scope); + when(fixture.isar.close()).thenAnswer((_) async { return true; }); @@ -186,6 +200,185 @@ void main() { }); }); + group('adds breadcrumbs', () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + + when(fixture.hub.options).thenReturn(fixture.options); + when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.hub.scope).thenReturn(fixture.scope); + + await fixture.setUp(); + }); + + tearDown(() async { + await fixture.tearDown(); + }); + + test('open adds breadcrumb', () async { + final breadcrumb = fixture.getCreatedBreadcrumb(); + verifyBreadcrumb('open', breadcrumb); + }); + + test('clear adds breadcrumb', () async { + await fixture.sut.writeTxn(() async { + await fixture.sut.clear(); + }); + + // order: open, clear, writeTxn + + final openCrumb = fixture.hub.scope.breadcrumbs[0]; + verifyBreadcrumb('open', openCrumb); + + final clearCrumb = fixture.hub.scope.breadcrumbs[1]; + verifyBreadcrumb('clear', clearCrumb); + + final writeTxnCrumb = fixture.hub.scope.breadcrumbs[2]; + verifyBreadcrumb('writeTxn', writeTxnCrumb); + }); + + test('close adds breadcrumb', () async { + await fixture.sut.close(); + final breadcrumb = fixture.getCreatedBreadcrumb(); + verifyBreadcrumb('close', breadcrumb); + }); + + test('copyToFile adds breadcrumb', () async { + await fixture.sut.copyToFile(fixture.copyPath); + final breadcrumb = fixture.getCreatedBreadcrumb(); + verifyBreadcrumb('copyToFile', breadcrumb); + }); + + test('getSize adds breadcrumb', () async { + await fixture.sut.getSize(); + final breadcrumb = fixture.getCreatedBreadcrumb(); + verifyBreadcrumb('getSize', breadcrumb); + }); + + test('txn adds breadcrumb', () async { + await fixture.sut.txn(() async {}); + final breadcrumb = fixture.getCreatedBreadcrumb(); + verifyBreadcrumb('txn', breadcrumb); + }); + + test('writeTxn adds breadcrumb', () async { + await fixture.sut.writeTxn(() async {}); + final breadcrumb = fixture.getCreatedBreadcrumb(); + verifyBreadcrumb('writeTxn', breadcrumb); + }); + }); + + group('add error breadcrumbs', () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + + when(fixture.hub.options).thenReturn(fixture.options); + when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.hub.scope).thenReturn(fixture.scope); + + when(fixture.isar.close()).thenAnswer((_) async { + return true; + }); + when(fixture.isar.name).thenReturn(Fixture.dbName); + + await fixture.setUp(injectMock: true); + }); + + tearDown(() async { + await fixture.tearDown(); + }); + + test('throwing close adds error breadcrumb', () async { + when(fixture.isar.close()).thenThrow(fixture.exception); + try { + await fixture.sut.close(); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'close', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing clear adds error breadcrumb', () async { + when(fixture.isar.clear()).thenThrow(fixture.exception); + try { + await fixture.sut.clear(); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'clear', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing copyToFile adds error breadcrumb', () async { + when(fixture.isar.copyToFile(any)).thenThrow(fixture.exception); + try { + await fixture.sut.copyToFile(fixture.copyPath); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'copyToFile', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing getSize adds error breadcrumb', () async { + when(fixture.isar.getSize()).thenThrow(fixture.exception); + try { + await fixture.sut.getSize(); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'getSize', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing txn adds error breadcrumb', () async { + param() async {} + when(fixture.isar.txn(param)).thenThrow(fixture.exception); + try { + await fixture.sut.txn(param); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'txn', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + + test('throwing writeTxn adds error breadcrumb', () async { + param() async {} + when(fixture.isar.writeTxn(param)).thenThrow(fixture.exception); + try { + await fixture.sut.writeTxn(param); + } catch (error) { + expect(error, fixture.exception); + } + verifyBreadcrumb( + 'writeTxn', + fixture.getCreatedBreadcrumb(), + status: 'internal_error', + ); + }); + }); + group('integrations', () { late Fixture fixture; @@ -194,6 +387,7 @@ void main() { when(fixture.hub.options).thenReturn(fixture.options); when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.hub.scope).thenReturn(fixture.scope); await fixture.setUp(); }); @@ -233,6 +427,7 @@ class Fixture { final _context = SentryTransactionContext('name', 'operation'); late final tracer = SentryTracer(_context, hub); late Isar sut; + late final scope = Scope(options); Future setUp({bool injectMock = false}) async { if (injectMock) { @@ -268,6 +463,10 @@ class Fixture { return tracer.children.last; } + Breadcrumb? getCreatedBreadcrumb() { + return hub.scope.breadcrumbs.last; + } + Future deleteCopyPath() async { final file = File(copyPath); if (await file.exists()) {