diff --git a/packages/dart_firebase_admin/lib/src/app/firebase_admin.dart b/packages/dart_firebase_admin/lib/src/app/firebase_admin.dart index 5913eda3..9238723d 100644 --- a/packages/dart_firebase_admin/lib/src/app/firebase_admin.dart +++ b/packages/dart_firebase_admin/lib/src/app/firebase_admin.dart @@ -24,8 +24,17 @@ class FirebaseAdminApp { /// Use the Firebase Emulator Suite to run the app locally. void useEmulator() { _isUsingEmulator = true; - authApiHost = Uri.http('127.0.0.1:9099', 'identitytoolkit.googleapis.com/'); - firestoreApiHost = Uri.http('127.0.0.1:8080', '/'); + final env = + Zone.current[envSymbol] as Map? ?? Platform.environment; + + authApiHost = Uri.http( + env['FIREBASE_AUTH_EMULATOR_HOST'] ?? '127.0.0.1:9099', + 'identitytoolkit.googleapis.com/', + ); + firestoreApiHost = Uri.http( + env['FIRESTORE_EMULATOR_HOST'] ?? '127.0.0.1:8080', + '/', + ); } @internal diff --git a/packages/dart_firebase_admin/lib/src/google_cloud_firestore/document_reader.dart b/packages/dart_firebase_admin/lib/src/google_cloud_firestore/document_reader.dart index 1972f30e..07bffc18 100644 --- a/packages/dart_firebase_admin/lib/src/google_cloud_firestore/document_reader.dart +++ b/packages/dart_firebase_admin/lib/src/google_cloud_firestore/document_reader.dart @@ -122,7 +122,8 @@ class _DocumentReader { // Only retry if we made progress. resultCount > 0 && // Don't retry permanent errors. - StatusCode.batchGetRetryCodes.contains(firestoreError.errorCode.statusCode); + StatusCode.batchGetRetryCodes + .contains(firestoreError.errorCode.statusCode); if (shoulRetry) { return _fetchDocuments(); } else { diff --git a/packages/dart_firebase_admin/lib/src/google_cloud_firestore/transaction.dart b/packages/dart_firebase_admin/lib/src/google_cloud_firestore/transaction.dart index 13eeb822..3efef57b 100644 --- a/packages/dart_firebase_admin/lib/src/google_cloud_firestore/transaction.dart +++ b/packages/dart_firebase_admin/lib/src/google_cloud_firestore/transaction.dart @@ -35,7 +35,8 @@ class Transaction { ) { _firestore = firestore; - _maxAttempts = transactionOptions?.maxAttempts ?? defaultMaxTransactionsAttempts; + _maxAttempts = + transactionOptions?.maxAttempts ?? defaultMaxTransactionsAttempts; switch (transactionOptions) { case ReadOnlyTransactionOptions(): @@ -50,9 +51,11 @@ class Transaction { static const int defaultMaxTransactionsAttempts = 5; //Error message for transactional reads that were executed after performing writes. - static const String readAfterWriteErrorMsg = 'Firestore transactions require all reads to be executed before all writes.'; + static const String readAfterWriteErrorMsg = + 'Firestore transactions require all reads to be executed before all writes.'; - static const String readOnlyWriteErrorMsg = 'Firestore read-only transactions cannot execute writes.'; + static const String readOnlyWriteErrorMsg = + 'Firestore read-only transactions cannot execute writes.'; late final Firestore _firestore; @@ -91,7 +94,8 @@ class Transaction { if (_writeBatch != null && _writeBatch._operations.isNotEmpty) { throw Exception(readAfterWriteErrorMsg); } - return _withLazyStartedTransaction, DocumentSnapshot>( + return _withLazyStartedTransaction, + DocumentSnapshot>( docRef, fieldMask: null, resultFn: _getSingleFn, @@ -115,7 +119,8 @@ class Transaction { if (_writeBatch != null && _writeBatch._operations.isNotEmpty) { throw Exception(readAfterWriteErrorMsg); } - return _withLazyStartedTransaction>, List>>( + return _withLazyStartedTransaction>, + List>>( documentsRefs, fieldMask: fieldMasks, resultFn: _getBatchFn, @@ -162,7 +167,9 @@ class Transaction { /// /// A [Precondition] restricting this update. /// - void update(DocumentReference documentRef, Map data, {Precondition? precondition}) { + void update( + DocumentReference documentRef, Map data, + {Precondition? precondition}) { if (_writeBatch == null) { throw Exception(readOnlyWriteErrorMsg); } @@ -170,7 +177,8 @@ class Transaction { _writeBatch.update( documentRef, { - for (final entry in data.entries) FieldPath.from(entry.key): entry.value, + for (final entry in data.entries) + FieldPath.from(entry.key): entry.value, }, precondition: precondition, ); @@ -180,7 +188,8 @@ class Transaction { /// /// A delete for a non-existing document is treated as a success (unless /// [precondition] is specified, in which case it throws a [FirebaseFirestoreAdminException] with [FirestoreClientErrorCode.notFound]). - void delete(DocumentReference> documentRef, {Precondition? precondition}) { + void delete(DocumentReference> documentRef, + {Precondition? precondition}) { if (_writeBatch == null) { throw Exception(readOnlyWriteErrorMsg); } @@ -233,7 +242,8 @@ class Transaction { // If there are any locks held, then rollback will eventually release them. // Rollback can be done concurrently thereby reducing latency caused by // otherwise blocking. - final rollBackRequest = firestore1.RollbackRequest(transaction: transactionId); + final rollBackRequest = + firestore1.RollbackRequest(transaction: transactionId); return _firestore._client.v1((client) { return client.projects.databases.documents .rollback( @@ -264,25 +274,31 @@ class Transaction { // response because we are not starting a new transaction return _transactionIdPromise! .then( - (transactionId) => resultFn(docRef, transactionId: transactionId, fieldMask: fieldMask), + (transactionId) => resultFn(docRef, + transactionId: transactionId, fieldMask: fieldMask), ) .then((r) => r.result); } else { if (_readOnlyReadTime != null) { // We do not start a transaction for read-only transactions // do not set _prevTransactionId - return resultFn(docRef, readTime: _readOnlyReadTime, fieldMask: fieldMask).then((r) => r.result); + return resultFn(docRef, + readTime: _readOnlyReadTime, fieldMask: fieldMask) + .then((r) => r.result); } else { // This is the first read of the transaction so we create the appropriate // options for lazily starting the transaction inside this first read op final opts = firestore1.TransactionOptions(); if (_writeBatch != null) { - opts.readWrite = _prevTransactionId == null ? firestore1.ReadWrite() : firestore1.ReadWrite(retryTransaction: _prevTransactionId); + opts.readWrite = _prevTransactionId == null + ? firestore1.ReadWrite() + : firestore1.ReadWrite(retryTransaction: _prevTransactionId); } else { opts.readOnly = firestore1.ReadOnly(); } - final resultPromise = resultFn(docRef, transactionOptions: opts, fieldMask: fieldMask); + final resultPromise = + resultFn(docRef, transactionOptions: opts, fieldMask: fieldMask); // Ensure the _transactionIdPromise is set synchronously so that // subsequent operations will not race to start another transaction @@ -324,7 +340,8 @@ class Transaction { transactionOptions: transactionOptions, ); final result = await reader._get(); - return _TransactionResult(transaction: result.transaction, result: result.result.single); + return _TransactionResult( + transaction: result.transaction, result: result.result.single); } Future<_TransactionResult>>> _getBatchFn( @@ -344,7 +361,8 @@ class Transaction { ); final result = await reader._get(); - return _TransactionResult(transaction: result.transaction, result: result.result); + return _TransactionResult( + transaction: result.transaction, result: result.result); } Future _runTransaction( diff --git a/packages/dart_firebase_admin/lib/src/google_cloud_firestore/write_batch.dart b/packages/dart_firebase_admin/lib/src/google_cloud_firestore/write_batch.dart index 0390a134..1aebf89a 100644 --- a/packages/dart_firebase_admin/lib/src/google_cloud_firestore/write_batch.dart +++ b/packages/dart_firebase_admin/lib/src/google_cloud_firestore/write_batch.dart @@ -11,7 +11,8 @@ class WriteResult { @override bool operator ==(Object other) { - return identical(this, other) || other is WriteResult && writeTime == other.writeTime; + return identical(this, other) || + other is WriteResult && writeTime == other.writeTime; } @override @@ -91,7 +92,8 @@ class WriteBatch { final response = await _commit(transactionId: transactionId); return [ - for (final writeResult in response.writeResults ?? []) + for (final writeResult + in response.writeResults ?? []) WriteResult._( Timestamp._fromString( writeResult.updateTime ?? response.commitTime!, @@ -161,11 +163,13 @@ class WriteBatch { _verifyNotCommited(); - final transform = _DocumentTransform.fromObject(documentReference, firestoreData); + final transform = + _DocumentTransform.fromObject(documentReference, firestoreData); transform.validate(); firestore1.Write op() { - final document = DocumentSnapshot._fromObject(documentReference, firestoreData); + final document = + DocumentSnapshot._fromObject(documentReference, firestoreData); final write = document._toWriteProto(); if (transform.transforms.isNotEmpty) { diff --git a/packages/dart_firebase_admin/test/firebase_admin_app_test.dart b/packages/dart_firebase_admin/test/firebase_admin_app_test.dart index 132cfc2e..72caa93c 100644 --- a/packages/dart_firebase_admin/test/firebase_admin_app_test.dart +++ b/packages/dart_firebase_admin/test/firebase_admin_app_test.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:dart_firebase_admin/src/app.dart'; import 'package:test/test.dart'; @@ -34,5 +36,40 @@ void main() { Uri.http('127.0.0.1:8080', '/'), ); }); + + test( + 'useEmulator() uses environment variables to set apiHost to the emulator', + () async { + const firebaseAuthEmulatorHost = '127.0.0.1:9000'; + const firestoreEmulatorHost = '127.0.0.1:8000'; + final testEnv = { + 'FIREBASE_AUTH_EMULATOR_HOST': firebaseAuthEmulatorHost, + 'FIRESTORE_EMULATOR_HOST': firestoreEmulatorHost, + }; + + await runZoned( + zoneValues: {envSymbol: testEnv}, + () async { + final app = FirebaseAdminApp.initializeApp( + 'dart-firebase-admin', + Credential.fromApplicationDefaultCredentials(), + ); + + app.useEmulator(); + + expect( + app.authApiHost, + Uri.http( + firebaseAuthEmulatorHost, + 'identitytoolkit.googleapis.com/', + ), + ); + expect( + app.firestoreApiHost, + Uri.http(firestoreEmulatorHost, '/'), + ); + }, + ); + }); }); } diff --git a/packages/dart_firebase_admin/test/google_cloud_firestore/transaction_test.dart b/packages/dart_firebase_admin/test/google_cloud_firestore/transaction_test.dart index 6714e21b..6c7b809a 100644 --- a/packages/dart_firebase_admin/test/google_cloud_firestore/transaction_test.dart +++ b/packages/dart_firebase_admin/test/google_cloud_firestore/transaction_test.dart @@ -30,7 +30,8 @@ void main() { test( 'get a document in a transaction', () async { - final DocumentReference> docRef = await initializeTest('simpleDocument'); + final DocumentReference> docRef = + await initializeTest('simpleDocument'); await docRef.set({'value': 42}); @@ -49,9 +50,12 @@ void main() { test( 'getAll documents in a transaction', () async { - final DocumentReference> docRef1 = await initializeTest('simpleDocument'); - final DocumentReference> docRef2 = await initializeTest('simpleDocument2'); - final DocumentReference> docRef3 = await initializeTest('simpleDocument3'); + final DocumentReference> docRef1 = + await initializeTest('simpleDocument'); + final DocumentReference> docRef2 = + await initializeTest('simpleDocument2'); + final DocumentReference> docRef3 = + await initializeTest('simpleDocument3'); await docRef1.set({'value': 42}); await docRef2.set({'value': 44}); @@ -60,8 +64,10 @@ void main() { expect( await firestore.runTransaction( (transaction) async { - final snapshot = await transaction.getAll([docRef1, docRef2, docRef3]); - return Future.value(snapshot).then((v) => v.map((e) => e.data()!['value']).toList()); + final snapshot = + await transaction.getAll([docRef1, docRef2, docRef3]); + return Future.value(snapshot) + .then((v) => v.map((e) => e.data()!['value']).toList()); }, ), [42, 44, 'foo'], @@ -72,9 +78,12 @@ void main() { test( 'getAll documents with FieldMask in a transaction', () async { - final DocumentReference> docRef1 = await initializeTest('simpleDocument'); - final DocumentReference> docRef2 = await initializeTest('simpleDocument2'); - final DocumentReference> docRef3 = await initializeTest('simpleDocument3'); + final DocumentReference> docRef1 = + await initializeTest('simpleDocument'); + final DocumentReference> docRef2 = + await initializeTest('simpleDocument2'); + final DocumentReference> docRef3 = + await initializeTest('simpleDocument3'); await docRef1.set({'value': 42, 'otherValue': 'bar'}); await docRef2.set({'value': 44, 'otherValue': 'bar'}); @@ -93,7 +102,8 @@ void main() { FieldPath(['value']), ], ); - return Future.value(snapshot).then((v) => v.map((e) => e.data()!).toList()); + return Future.value(snapshot) + .then((v) => v.map((e) => e.data()!).toList()); }, ), [ @@ -108,7 +118,8 @@ void main() { test( 'set a document in a transaction', () async { - final DocumentReference> docRef = await initializeTest('simpleDocument'); + final DocumentReference> docRef = + await initializeTest('simpleDocument'); await firestore.runTransaction( (transaction) async { @@ -126,7 +137,8 @@ void main() { test( 'update a document in a transaction', () async { - final DocumentReference> docRef = await initializeTest('simpleDocument'); + final DocumentReference> docRef = + await initializeTest('simpleDocument'); await firestore.runTransaction( (transaction) async { @@ -145,7 +157,8 @@ void main() { test( 'update a non existingdocument in a transaction', () async { - final DocumentReference> docRef = await initializeTest('simpleDocument'); + final DocumentReference> docRef = + await initializeTest('simpleDocument'); final nonExistingDocRef = await initializeTest('simpleDocument2'); @@ -158,7 +171,10 @@ void main() { }, ); }, - throwsA(isA().having((e) => e.errorCode.statusCode, 'statusCode', StatusCode.notFound)), + throwsA(isA().having( + (e) => e.errorCode.statusCode, + 'statusCode', + StatusCode.notFound)), ); }, ); @@ -166,7 +182,8 @@ void main() { test( 'update a document with precondition in a transaction', () async { - final DocumentReference> docRef = await initializeTest('simpleDocument'); + final DocumentReference> docRef = + await initializeTest('simpleDocument'); final setReult = await docRef.set({'value': 42}); @@ -174,7 +191,8 @@ void main() { await firestore.runTransaction( (transaction) async { - transaction.update(docRef, {'value': 44}, precondition: precondition); + transaction.update(docRef, {'value': 44}, + precondition: precondition); }, ); @@ -184,11 +202,15 @@ void main() { () async { await firestore.runTransaction( (transaction) async { - transaction.update(docRef, {'value': 46}, precondition: precondition); + transaction.update(docRef, {'value': 46}, + precondition: precondition); }, ); }, - throwsA(isA().having((e) => e.errorCode.statusCode, 'statusCode', StatusCode.failedPrecondition)), + throwsA(isA().having( + (e) => e.errorCode.statusCode, + 'statusCode', + StatusCode.failedPrecondition)), ); }, ); @@ -196,7 +218,8 @@ void main() { test( 'get and set a document in a transaction', () async { - final DocumentReference> docRef = await initializeTest('simpleDocument'); + final DocumentReference> docRef = + await initializeTest('simpleDocument'); await docRef.set({'value': 42}); DocumentSnapshot> getData; DocumentSnapshot> setData; @@ -226,7 +249,8 @@ void main() { test( 'delete a existing document in a transaction', () async { - final DocumentReference> docRef = await initializeTest('simpleDocument'); + final DocumentReference> docRef = + await initializeTest('simpleDocument'); await docRef.set({'value': 42}); @@ -238,7 +262,8 @@ void main() { expect( await docRef.get(), - isA>>().having((e) => e.exists, 'exists', false), + isA>>() + .having((e) => e.exists, 'exists', false), ); }, ); @@ -246,7 +271,8 @@ void main() { test( 'delete a non existing document in a transaction', () async { - final DocumentReference> docRef = await initializeTest('simpleDocument'); + final DocumentReference> docRef = + await initializeTest('simpleDocument'); expect( await firestore.runTransaction( @@ -262,7 +288,8 @@ void main() { test( 'delete a non existing document with existing precondition in a transaction', () async { - final DocumentReference> docRef = await initializeTest('simpleDocument'); + final DocumentReference> docRef = + await initializeTest('simpleDocument'); final precondition = Precondition.exists(true); expect( () async { @@ -272,7 +299,10 @@ void main() { }, ); }, - throwsA(isA().having((e) => e.errorCode.statusCode, 'statusCode', StatusCode.notFound)), + throwsA(isA().having( + (e) => e.errorCode.statusCode, + 'statusCode', + StatusCode.notFound)), ); }, ); @@ -280,10 +310,12 @@ void main() { test( 'delete a document with precondition in a transaction', () async { - final DocumentReference> docRef = await initializeTest('simpleDocument'); + final DocumentReference> docRef = + await initializeTest('simpleDocument'); final writeResult = await docRef.set({'value': 42}); - var precondition = Precondition.timestamp(Timestamp.fromDate(DateTime.now().subtract(const Duration(days: 1)))); + var precondition = Precondition.timestamp(Timestamp.fromDate( + DateTime.now().subtract(const Duration(days: 1)))); expect( () async { @@ -293,12 +325,16 @@ void main() { }, ); }, - throwsA(isA().having((e) => e.errorCode.statusCode, 'statusCode', StatusCode.failedPrecondition)), + throwsA(isA().having( + (e) => e.errorCode.statusCode, + 'statusCode', + StatusCode.failedPrecondition)), ); expect( await docRef.get(), - isA>>().having((e) => e.exists, 'exists', true), + isA>>() + .having((e) => e.exists, 'exists', true), ); precondition = Precondition.timestamp(writeResult.writeTime); @@ -310,7 +346,8 @@ void main() { expect( await docRef.get(), - isA>>().having((e) => e.exists, 'exists', false), + isA>>() + .having((e) => e.exists, 'exists', false), ); }, ); @@ -318,7 +355,8 @@ void main() { test( 'prevent get after set in a transaction', () async { - final DocumentReference> docRef = await initializeTest('simpleDocument'); + final DocumentReference> docRef = + await initializeTest('simpleDocument'); expect( () async { @@ -344,7 +382,8 @@ void main() { test( 'prevent set in a readOnly transaction', () async { - final DocumentReference> docRef = await initializeTest('simpleDocument'); + final DocumentReference> docRef = + await initializeTest('simpleDocument'); expect( () async { @@ -370,7 +409,8 @@ void main() { test( 'detects document change during transaction', () async { - final DocumentReference> docRef = await initializeTest('simpleDocument'); + final DocumentReference> docRef = + await initializeTest('simpleDocument'); expect( () async { @@ -402,8 +442,10 @@ void main() { test( 'runs multiple transactions in parallel', () async { - final DocumentReference> doc1 = await initializeTest('transaction-multi-1'); - final DocumentReference> doc2 = await initializeTest('transaction-multi-2'); + final DocumentReference> doc1 = + await initializeTest('transaction-multi-1'); + final DocumentReference> doc2 = + await initializeTest('transaction-multi-2'); await Future.wait([ firestore.runTransaction((transaction) async { @@ -418,9 +460,11 @@ void main() { }), ]); - final DocumentSnapshot> snapshot1 = await doc1.get(); + final DocumentSnapshot> snapshot1 = + await doc1.get(); expect(snapshot1.data()!['test'], equals('value3')); - final DocumentSnapshot> snapshot2 = await doc2.get(); + final DocumentSnapshot> snapshot2 = + await doc2.get(); expect(snapshot2.data()!['test'], equals('value4')); }, ); @@ -428,7 +472,8 @@ void main() { test( 'should not collide transaction if number of maxAttempts is enough', () async { - final DocumentReference> doc1 = await initializeTest('transaction-maxAttempts-1'); + final DocumentReference> doc1 = + await initializeTest('transaction-maxAttempts-1'); await doc1.set({'test': 0}); @@ -451,7 +496,8 @@ void main() { ), ]); - final DocumentSnapshot> snapshot1 = await doc1.get(); + final DocumentSnapshot> snapshot1 = + await doc1.get(); expect(snapshot1.data()!['test'], equals(2)); }, ); @@ -459,7 +505,8 @@ void main() { test( 'should collide transaction if number of maxAttempts is not enough', () async { - final DocumentReference> doc1 = await initializeTest('transaction-maxAttempts-1'); + final DocumentReference> doc1 = + await initializeTest('transaction-maxAttempts-1'); await doc1.set({'test': 0}); expect( @@ -495,7 +542,8 @@ void main() { ); test('works with withConverter', () async { - final DocumentReference> rawDoc = await initializeTest('with-converter-batch'); + final DocumentReference> rawDoc = + await initializeTest('with-converter-batch'); final DocumentReference doc = rawDoc.withConverter( fromFirestore: (snapshot) { @@ -529,7 +577,8 @@ void main() { test('should resolve with user value', () async { final int randomValue = Random().nextInt(9999); - final int response = await firestore.runTransaction((transaction) async { + final int response = + await firestore.runTransaction((transaction) async { return randomValue; }); expect(response, equals(randomValue));