Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(database, web): fix broken exception handling on streams #12647

Merged
merged 8 commits into from
Apr 16, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -389,9 +389,7 @@ class Query<T extends database_interop.QueryJsImpl> extends JsObjectWrapper<T> {
});

final void Function(JSObject) cancelCallbackWrap = ((JSObject error) {
final dartified = error.dartify();
streamController
.addError(convertFirebaseDatabaseException(dartified ?? {}));
streamController.addError(error);
streamController.close();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,25 @@ FirebaseException convertFirebaseDatabaseException(Object exception,
[StackTrace? stackTrace]) {
final castedJSObject = exception as core_interop.JSError;
String code = 'unknown';
String message = castedJSObject.message?.toDart ?? '';
String message = castedJSObject.message?.toDart.toLowerCase() ?? '';

// FirebaseWeb SDK for Database has no error codes, so we manually map known
// messages to known error codes for cross platform consistency.
if (message.toLowerCase().contains('index not defined')) {
if (message.contains('index not defined')) {
code = 'index-not-defined';
} else if (message.toLowerCase().contains('permission denied')) {
} else if (message.contains('permission denied') ||
message.contains('permission_denied')) {
code = 'permission-denied';
} else if (message
.toLowerCase()
.contains('transaction needs to be run again with current data')) {
code = 'data-stale';
} else if (message
.toLowerCase()
.contains('transaction had too many retries')) {
} else if (message.contains('transaction had too many retries')) {
code = 'max-retries';
} else if (message.toLowerCase().contains('service is unavailable')) {
} else if (message.contains('service is unavailable')) {
code = 'unavailable';
} else if (message.toLowerCase().contains('network error')) {
} else if (message.contains('network error')) {
code = 'network-error';
} else if (message.toLowerCase().contains('write was canceled')) {
} else if (message.contains('write was canceled')) {
code = 'write-cancelled';
}

Expand Down
41 changes: 41 additions & 0 deletions tests/integration_test/firebase_database/query_e2e.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'package:collection/collection.dart';
import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_test/flutter_test.dart';

Expand Down Expand Up @@ -499,5 +501,44 @@ void setupQueryTests() {
retry: 2,
);
});

group('onValue', () {
test('emits an event when the data changes', () async {
await ref.set({
'a': 2,
'b': 3,
'c': 1,
});
expect(
ref.onValue,
emitsInOrder([
isA<DatabaseEvent>().having((s) => s.snapshot.value, 'value', {
'a': 2,
'b': 3,
'c': 1,
}).having((e) => e.type, 'type', DatabaseEventType.value),
]),
);
});

test(
'throw a `permission-denied` exception when accessing restricted data',
() async {
final Completer<FirebaseException> errorReceived =
Completer<FirebaseException>();
FirebaseDatabase.instance.ref().child('restricted').onValue.listen(
(event) {
// Do nothing
},
onError: (error) {
errorReceived.complete(error);
},
);

final streamError = await errorReceived.future;
expect(streamError, isA<FirebaseException>());
expect(streamError.code, 'permission-denied');
});
});
});
}