From fdd8df6966b55216617680e5eb1664fb78dfa174 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Thu, 1 Mar 2018 17:28:04 +0000 Subject: [PATCH 1/2] Add missing firestore tests --- tests/ios/Podfile.lock | 4 +- .../firestore/collectionReferenceTests.js | 152 ++++++++++++++++-- .../tests/firestore/documentReferenceTests.js | 135 ++++++++++++++++ tests/src/tests/firestore/fieldPathTests.js | 7 + tests/src/tests/firestore/firestoreTests.js | 62 +++++++ 5 files changed, 344 insertions(+), 16 deletions(-) diff --git a/tests/ios/Podfile.lock b/tests/ios/Podfile.lock index 22babbd1..fe1e3160 100644 --- a/tests/ios/Podfile.lock +++ b/tests/ios/Podfile.lock @@ -164,7 +164,7 @@ PODS: - React/Core - React/fishhook - React/RCTBlob - - RNFirebase (3.2.4): + - RNFirebase (3.2.7): - React - yoga (0.52.0.React) @@ -228,7 +228,7 @@ SPEC CHECKSUMS: nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3 Protobuf: 8a9838fba8dae3389230e1b7f8c104aa32389c03 React: 61a6bdf17a9ff16875c230e6ff278d9de274e16c - RNFirebase: 011e47909cf54070f72d50b8d61eb7b347774d29 + RNFirebase: 3a141a97041ea0757e2036c2bb18acbe9f0e105d yoga: 646606bf554d54a16711f35596178522fbc00480 PODFILE CHECKSUM: 67c98bcb203cb992da590bcab6f690f727653ca5 diff --git a/tests/src/tests/firestore/collectionReferenceTests.js b/tests/src/tests/firestore/collectionReferenceTests.js index 6b46fd07..780b595e 100644 --- a/tests/src/tests/firestore/collectionReferenceTests.js +++ b/tests/src/tests/firestore/collectionReferenceTests.js @@ -26,6 +26,15 @@ function collectionReferenceTests({ })); }); + context('parent', () => { + it('should return parent document', () => { + const collection = firebase.native + .firestore() + .collection('collection/document/subcollection'); + collection.parent.path.should.equal('collection/document'); + }); + }); + context('add()', () => { it('should create Document', () => firebase.native @@ -51,6 +60,15 @@ function collectionReferenceTests({ should.equal(docRef.path, 'collection-tests/doc'); resolve(); })); + + it('should error when supplied an incorrect path', () => { + (() => { + firebase.native + .firestore() + .collection('collection') + .doc('invalid/doc'); + }).should.throw('Argument "documentPath" must point to a document.'); + }); }); context('get()', () => { @@ -68,6 +86,47 @@ function collectionReferenceTests({ }); context('onSnapshot()', () => { + it('QuerySnapshot has correct properties', async () => { + const snapshot = await firebase.native + .firestore() + .collection('collection-tests') + .get(); + + snapshot.docChanges.should.be.an.Array(); + snapshot.empty.should.equal(false); + snapshot.metadata.should.be.an.Object(); + snapshot.query.should.be.an.Object(); + }); + + it('DocumentChange has correct properties', async () => { + const collectionRef = firebase.native + .firestore() + .collection('collection-tests'); + + // Test + + let unsubscribe; + let changes; + await new Promise(resolve2 => { + unsubscribe = collectionRef.onSnapshot(snapshot => { + changes = snapshot.docChanges; + resolve2(); + }); + }); + + // Assertions + + changes.should.be.a.Array(); + changes[0].doc.should.be.an.Object(); + changes[0].newIndex.should.be.a.Number(); + changes[0].oldIndex.should.be.a.Number(); + changes[0].type.should.be.a.String(); + + // Tear down + + unsubscribe(); + }); + it('calls callback with the initial data and then when document changes', async () => { const collectionRef = firebase.native .firestore() @@ -104,9 +163,7 @@ function collectionReferenceTests({ unsubscribe(); }); - }); - context('onSnapshot()', () => { it('calls callback with the initial data and then when document is added', async () => { const collectionRef = firebase.native .firestore() @@ -144,9 +201,7 @@ function collectionReferenceTests({ unsubscribe(); }); - }); - context('onSnapshot()', () => { it("doesn't call callback when the ref is updated with the same value", async () => { const collectionRef = firebase.native .firestore() @@ -181,9 +236,7 @@ function collectionReferenceTests({ unsubscribe(); }); - }); - context('onSnapshot()', () => { it('allows binding multiple callbacks to the same ref', async () => { // Setup const collectionRef = firebase.native @@ -234,9 +287,7 @@ function collectionReferenceTests({ unsubscribeA(); unsubscribeB(); }); - }); - context('onSnapshot()', () => { it('listener stops listening when unsubscribed', async () => { // Setup const collectionRef = firebase.native @@ -310,9 +361,7 @@ function collectionReferenceTests({ callbackA.should.be.calledTwice(); callbackB.should.be.calledThrice(); }); - }); - context('onSnapshot()', () => { it('supports options and callback', async () => { const collectionRef = firebase.native .firestore() @@ -354,9 +403,7 @@ function collectionReferenceTests({ unsubscribe(); }); - }); - context('onSnapshot()', () => { it('supports observer', async () => { const collectionRef = firebase.native .firestore() @@ -396,9 +443,7 @@ function collectionReferenceTests({ unsubscribe(); }); - }); - context('onSnapshot()', () => { it('supports options and observer', async () => { const collectionRef = firebase.native .firestore() @@ -416,6 +461,7 @@ function collectionReferenceTests({ snapshot.forEach(doc => callback(doc.data())); resolve2(); }, + error: () => {}, }; unsubscribe = collectionRef.onSnapshot( { @@ -443,6 +489,84 @@ function collectionReferenceTests({ unsubscribe(); }); + + it('errors when invalid parameters supplied', async () => { + const colRef = firebase.native + .firestore() + .collection('collection-tests'); + + (() => { + colRef.onSnapshot(() => {}, 'error'); + }).should.throw( + 'Query.onSnapshot failed: Second argument must be a valid function.' + ); + (() => { + colRef.onSnapshot({ + next: () => {}, + error: 'error', + }); + }).should.throw( + 'Query.onSnapshot failed: Observer.error must be a valid function.' + ); + (() => { + colRef.onSnapshot( + { + includeQueryMetadataChanges: true, + }, + () => {}, + 'error' + ); + }).should.throw( + 'Query.onSnapshot failed: Third argument must be a valid function.' + ); + (() => { + colRef.onSnapshot( + { + includeQueryMetadataChanges: true, + }, + { + next: () => {}, + error: 'error', + } + ); + }).should.throw( + 'Query.onSnapshot failed: Observer.error must be a valid function.' + ); + (() => { + colRef.onSnapshot( + { + includeQueryMetadataChanges: true, + }, + { + next: 'error', + } + ); + }).should.throw( + 'Query.onSnapshot failed: Observer.next must be a valid function.' + ); + (() => { + colRef.onSnapshot( + { + includeQueryMetadataChanges: true, + }, + 'error' + ); + }).should.throw( + 'Query.onSnapshot failed: Second argument must be a function or observer.' + ); + (() => { + colRef.onSnapshot({ + error: 'error', + }); + }).should.throw( + 'Query.onSnapshot failed: First argument must be a function, observer or options.' + ); + (() => { + colRef.onSnapshot(); + }).should.throw( + 'Query.onSnapshot failed: Called with invalid arguments.' + ); + }); }); // Where diff --git a/tests/src/tests/firestore/documentReferenceTests.js b/tests/src/tests/firestore/documentReferenceTests.js index 4686c888..bd14e3d8 100644 --- a/tests/src/tests/firestore/documentReferenceTests.js +++ b/tests/src/tests/firestore/documentReferenceTests.js @@ -17,6 +17,28 @@ function documentReferenceTests({ describe, it, context, firebase }) { })); }); + context('id', () => { + it('should return document id', () => { + const document = firebase.native.firestore().doc('documents/doc1'); + document.id.should.equal('doc1'); + }); + }); + + context('parent', () => { + it('should return parent collection', () => { + const document = firebase.native.firestore().doc('documents/doc1'); + document.parent.id.should.equal('documents'); + }); + }); + + context('collection()', () => { + it('should return a child collection', () => { + const document = firebase.native.firestore().doc('documents/doc1'); + const collection = document.collection('pages'); + collection.id.should.equal('pages'); + }); + }); + context('delete()', () => { it('should delete Document', () => firebase.native @@ -32,6 +54,17 @@ function documentReferenceTests({ describe, it, context, firebase }) { })); }); + context('get()', () => { + it('DocumentSnapshot should have correct properties', async () => { + const snapshot = await firebase.native + .firestore() + .doc('document-tests/doc1') + .get(); + snapshot.id.should.equal('doc1'); + snapshot.metadata.should.be.an.Object(); + }); + }); + context('onSnapshot()', () => { it('calls callback with the initial data and then when value changes', async () => { const docRef = firebase.native.firestore().doc('document-tests/doc1'); @@ -321,6 +354,7 @@ function documentReferenceTests({ describe, it, context, firebase }) { callback(snapshot.data()); resolve2(); }, + error: () => {}, }; unsubscribe = docRef.onSnapshot( { includeMetadataChanges: true }, @@ -346,6 +380,88 @@ function documentReferenceTests({ describe, it, context, firebase }) { unsubscribe(); }); + + it('errors when invalid parameters supplied', async () => { + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + (() => { + docRef.onSnapshot(() => {}, 'error'); + }).should.throw( + 'DocumentReference.onSnapshot failed: Second argument must be a valid function.' + ); + (() => { + docRef.onSnapshot({ + next: () => {}, + error: 'error', + }); + }).should.throw( + 'DocumentReference.onSnapshot failed: Observer.error must be a valid function.' + ); + (() => { + docRef.onSnapshot({ + next: 'error', + }); + }).should.throw( + 'DocumentReference.onSnapshot failed: Observer.next must be a valid function.' + ); + (() => { + docRef.onSnapshot( + { + includeMetadataChanges: true, + }, + () => {}, + 'error' + ); + }).should.throw( + 'DocumentReference.onSnapshot failed: Third argument must be a valid function.' + ); + (() => { + docRef.onSnapshot( + { + includeMetadataChanges: true, + }, + { + next: () => {}, + error: 'error', + } + ); + }).should.throw( + 'DocumentReference.onSnapshot failed: Observer.error must be a valid function.' + ); + (() => { + docRef.onSnapshot( + { + includeMetadataChanges: true, + }, + { + next: 'error', + } + ); + }).should.throw( + 'DocumentReference.onSnapshot failed: Observer.next must be a valid function.' + ); + (() => { + docRef.onSnapshot( + { + includeMetadataChanges: true, + }, + 'error' + ); + }).should.throw( + 'DocumentReference.onSnapshot failed: Second argument must be a function or observer.' + ); + (() => { + docRef.onSnapshot({ + error: 'error', + }); + }).should.throw( + 'DocumentReference.onSnapshot failed: First argument must be a function, observer or options.' + ); + (() => { + docRef.onSnapshot(); + }).should.throw( + 'DocumentReference.onSnapshot failed: Called with invalid arguments.' + ); + }); }); context('set()', () => { @@ -464,6 +580,25 @@ function documentReferenceTests({ describe, it, context, firebase }) { doc.data().nested.firstname.should.equal('First Name'); doc.data().nested.lastname.should.equal('Last Name'); })); + + it('errors when invalid parameters supplied', async () => { + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + (() => { + docRef.update('error'); + }).should.throw( + 'DocumentReference.update failed: If using a single argument, it must be an object.' + ); + (() => { + docRef.update('error1', 'error2', 'error3'); + }).should.throw( + 'DocumentReference.update failed: Must have either a single object argument, or equal numbers of key/value pairs.' + ); + (() => { + docRef.update(0, 'error'); + }).should.throw( + 'DocumentReference.update failed: Argument at index 0 must be a string or FieldPath' + ); + }); }); context('types', () => { diff --git a/tests/src/tests/firestore/fieldPathTests.js b/tests/src/tests/firestore/fieldPathTests.js index 99d49eb6..aba24123 100644 --- a/tests/src/tests/firestore/fieldPathTests.js +++ b/tests/src/tests/firestore/fieldPathTests.js @@ -2,6 +2,13 @@ import should from 'should'; function fieldPathTests({ describe, it, context, firebase }) { describe('FieldPath', () => { + context('documentId', () => { + it('should be a FieldPath', () => { + const documentId = firebase.native.firestore.FieldPath.documentId(); + documentId.should.be.instanceof(firebase.native.firestore.FieldPath); + }); + }); + context('DocumentSnapshot.get()', () => { it('should get the correct values', () => firebase.native diff --git a/tests/src/tests/firestore/firestoreTests.js b/tests/src/tests/firestore/firestoreTests.js index b88cd477..bde6a43d 100644 --- a/tests/src/tests/firestore/firestoreTests.js +++ b/tests/src/tests/firestore/firestoreTests.js @@ -11,6 +11,14 @@ function firestoreTests({ describe, it, context, firebase }) { should.equal(collectionRef.id, 'collection2'); resolve(); })); + + it('should error if invalid collection path supplied', () => { + (() => { + firebase.native.firestore().collection('collection1/doc1'); + }).should.throw( + 'Argument "collectionPath" must point to a collection.' + ); + }); }); context('doc()', () => { @@ -22,6 +30,12 @@ function firestoreTests({ describe, it, context, firebase }) { should.equal(docRef.path, 'collection1/doc1/collection2/doc2'); resolve(); })); + + it('should error if invalid document path supplied', () => { + (() => { + firebase.native.firestore().doc('collection1'); + }).should.throw('Argument "documentPath" must point to a document.'); + }); }); context('batch()', () => { @@ -90,6 +104,54 @@ function firestoreTests({ describe, it, context, firebase }) { sfDoc.data().nested.lastname.should.equal('Last Name'); }); }); + + it('errors when invalid parameters supplied', async () => { + const ref = firebase.native.firestore().doc('collection/doc'); + const batch = firebase.native.firestore().batch(); + (() => { + batch.update(ref, 'error'); + }).should.throw( + 'WriteBatch.update failed: If using two arguments, the second must be an object.' + ); + (() => { + batch.update(ref, 'error1', 'error2', 'error3'); + }).should.throw( + 'WriteBatch.update failed: Must have a document reference, followed by either a single object argument, or equal numbers of key/value pairs.' + ); + (() => { + batch.update(ref, 0, 'error'); + }).should.throw( + 'WriteBatch.update failed: Argument at index 0 must be a string or FieldPath' + ); + }); + }); + + context('enablePersistence()', () => { + it('should throw an unsupported error', () => { + (() => { + firebase.native.firestore().enablePersistence(); + }).should.throw( + 'Persistence is enabled by default on the Firestore SDKs' + ); + }); + }); + + context('setLogLevel()', () => { + it('should throw an unsupported error', () => { + (() => { + firebase.native.firestore().setLogLevel(); + }).should.throw( + 'firebase.firestore().setLogLevel() is unsupported by the native Firebase SDKs.' + ); + }); + }); + + context('settings()', () => { + it('should throw an unsupported error', () => { + (() => { + firebase.native.firestore().settings(); + }).should.throw('firebase.firestore().settings() coming soon'); + }); }); }); } From 81604318114abc21e2a23aa3da73366e5527adec Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Thu, 1 Mar 2018 18:01:11 +0000 Subject: [PATCH 2/2] Add some additional authTests --- tests/src/tests/auth/authTests.js | 2028 +++++++++++++---------------- tests/src/tests/auth/index.js | 2 + tests/src/tests/auth/userTests.js | 399 ++++++ 3 files changed, 1315 insertions(+), 1114 deletions(-) create mode 100644 tests/src/tests/auth/userTests.js diff --git a/tests/src/tests/auth/authTests.js b/tests/src/tests/auth/authTests.js index 53f5f826..5d259394 100644 --- a/tests/src/tests/auth/authTests.js +++ b/tests/src/tests/auth/authTests.js @@ -15,1203 +15,1003 @@ const randomString = (length, chars) => { return result; }; -export default (authTests = ({ tryCatch, describe, it, firebase }) => { - describe('onAuthStateChanged', () => { - it('calls callback with the current user and when auth state changes', async () => { - await firebase.native.auth().signInAnonymously(); +export default (authTests = ({ tryCatch, context, describe, it, firebase }) => { + describe('auth()', () => { + context('onAuthStateChanged', () => { + it('calls callback with the current user and when auth state changes', async () => { + await firebase.native.auth().signInAnonymously(); - // Test - const callback = sinon.spy(); + // Test + const callback = sinon.spy(); - let unsubscribe; - await new Promise(resolve => { - unsubscribe = firebase.native.auth().onAuthStateChanged(user => { - callback(user); - resolve(); + let unsubscribe; + await new Promise(resolve => { + unsubscribe = firebase.native.auth().onAuthStateChanged(user => { + callback(user); + resolve(); + }); }); - }); - callback.should.be.calledWith(firebase.native.auth().currentUser); - callback.should.be.calledOnce(); + callback.should.be.calledWith(firebase.native.auth().currentUser); + callback.should.be.calledOnce(); - // Sign out + // Sign out - await firebase.native.auth().signOut(); + await firebase.native.auth().signOut(); - await new Promise(resolve => { - setTimeout(() => resolve(), 5); - }); - - // Assertions - - callback.should.be.calledWith(null); - callback.should.be.calledTwice(); - - // Tear down - - unsubscribe(); - }); - - it('stops listening when unsubscribed', async () => { - await firebase.native.auth().signInAnonymously(); - - // Test - const callback = sinon.spy(); - - let unsubscribe; - await new Promise(resolve => { - unsubscribe = firebase.native.auth().onAuthStateChanged(user => { - callback(user); - resolve(); + await new Promise(resolve => { + setTimeout(() => resolve(), 5); }); + + // Assertions + + callback.should.be.calledWith(null); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); }); - callback.should.be.calledWith(firebase.native.auth().currentUser); - callback.should.be.calledOnce(); + it('stops listening when unsubscribed', async () => { + await firebase.native.auth().signInAnonymously(); - // Sign out + // Test + const callback = sinon.spy(); - await firebase.native.auth().signOut(); - - await new Promise(resolve => { - setTimeout(() => resolve(), 5); - }); - - // Assertions - - callback.should.be.calledWith(null); - callback.should.be.calledTwice(); - - // Unsubscribe - - unsubscribe(); - - // Sign back in - - await firebase.native.auth().signInAnonymously(); - - // Assertions - - callback.should.be.calledTwice(); - - // Tear down - - await firebase.native.auth().signOut(); - }); - }); - - describe('onIdTokenChanged', () => { - it('calls callback with the current user and when auth state changes', async () => { - await firebase.native.auth().signInAnonymously(); - - // Test - const callback = sinon.spy(); - - let unsubscribe; - await new Promise(resolve => { - unsubscribe = firebase.native.auth().onIdTokenChanged(user => { - callback(user); - resolve(); + let unsubscribe; + await new Promise(resolve => { + unsubscribe = firebase.native.auth().onAuthStateChanged(user => { + callback(user); + resolve(); + }); }); - }); - callback.should.be.calledWith(firebase.native.auth().currentUser); - callback.should.be.calledOnce(); + callback.should.be.calledWith(firebase.native.auth().currentUser); + callback.should.be.calledOnce(); - // Sign out + // Sign out - await firebase.native.auth().signOut(); + await firebase.native.auth().signOut(); - await new Promise(resolve => { - setTimeout(() => resolve(), 5); - }); - - // Assertions - - callback.should.be.calledWith(null); - callback.should.be.calledTwice(); - - // Tear down - - unsubscribe(); - }); - - it('stops listening when unsubscribed', async () => { - await firebase.native.auth().signInAnonymously(); - - // Test - const callback = sinon.spy(); - - let unsubscribe; - await new Promise(resolve => { - unsubscribe = firebase.native.auth().onIdTokenChanged(user => { - callback(user); - resolve(); + await new Promise(resolve => { + setTimeout(() => resolve(), 5); }); + + // Assertions + + callback.should.be.calledWith(null); + callback.should.be.calledTwice(); + + // Unsubscribe + + unsubscribe(); + + // Sign back in + + await firebase.native.auth().signInAnonymously(); + + // Assertions + + callback.should.be.calledTwice(); + + // Tear down + + await firebase.native.auth().signOut(); }); - - callback.should.be.calledWith(firebase.native.auth().currentUser); - callback.should.be.calledOnce(); - - // Sign out - - await firebase.native.auth().signOut(); - - await new Promise(resolve => { - setTimeout(() => resolve(), 5); - }); - - // Assertions - - callback.should.be.calledWith(null); - callback.should.be.calledTwice(); - - // Unsubscribe - - unsubscribe(); - - // Sign back in - - await firebase.native.auth().signInAnonymously(); - - // Assertions - - callback.should.be.calledTwice(); - - // Tear down - - await firebase.native.auth().signOut(); }); - }); - describe('onUserChanged', () => { - it('calls callback with the current user and when auth state changes', async () => { - await firebase.native.auth().signInAnonymously(); + context('onIdTokenChanged', () => { + it('calls callback with the current user and when auth state changes', async () => { + await firebase.native.auth().signInAnonymously(); - // Test - const callback = sinon.spy(); + // Test + const callback = sinon.spy(); - let unsubscribe; - await new Promise(resolve => { - unsubscribe = firebase.native.auth().onUserChanged(user => { - callback(user); - resolve(); + let unsubscribe; + await new Promise(resolve => { + unsubscribe = firebase.native.auth().onIdTokenChanged(user => { + callback(user); + resolve(); + }); }); - }); - callback.should.be.calledWith(firebase.native.auth().currentUser); - callback.should.be.calledOnce(); + callback.should.be.calledWith(firebase.native.auth().currentUser); + callback.should.be.calledOnce(); - // Sign out + // Sign out - await firebase.native.auth().signOut(); + await firebase.native.auth().signOut(); - await new Promise(resolve => { - setTimeout(() => resolve(), 5); - }); - - // Assertions - - callback.should.be.calledWith(null); - // Because of the way onUserChanged works, it will be called double - // - once for onAuthStateChanged - // - once for onIdTokenChanged - callback.should.have.callCount(4); - - // Tear down - - unsubscribe(); - }); - - it('stops listening when unsubscribed', async () => { - await firebase.native.auth().signInAnonymously(); - - // Test - const callback = sinon.spy(); - - let unsubscribe; - await new Promise(resolve => { - unsubscribe = firebase.native.auth().onUserChanged(user => { - callback(user); - resolve(); + await new Promise(resolve => { + setTimeout(() => resolve(), 5); }); + + // Assertions + + callback.should.be.calledWith(null); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); }); - callback.should.be.calledWith(firebase.native.auth().currentUser); - callback.should.be.calledOnce(); + it('stops listening when unsubscribed', async () => { + await firebase.native.auth().signInAnonymously(); - // Sign out + // Test + const callback = sinon.spy(); - await firebase.native.auth().signOut(); + let unsubscribe; + await new Promise(resolve => { + unsubscribe = firebase.native.auth().onIdTokenChanged(user => { + callback(user); + resolve(); + }); + }); - await new Promise(resolve => { - setTimeout(() => resolve(), 5); + callback.should.be.calledWith(firebase.native.auth().currentUser); + callback.should.be.calledOnce(); + + // Sign out + + await firebase.native.auth().signOut(); + + await new Promise(resolve => { + setTimeout(() => resolve(), 5); + }); + + // Assertions + + callback.should.be.calledWith(null); + callback.should.be.calledTwice(); + + // Unsubscribe + + unsubscribe(); + + // Sign back in + + await firebase.native.auth().signInAnonymously(); + + // Assertions + + callback.should.be.calledTwice(); + + // Tear down + + await firebase.native.auth().signOut(); + }); + }); + + context('onUserChanged', () => { + it('calls callback with the current user and when auth state changes', async () => { + await firebase.native.auth().signInAnonymously(); + + // Test + const callback = sinon.spy(); + + let unsubscribe; + await new Promise(resolve => { + unsubscribe = firebase.native.auth().onUserChanged(user => { + callback(user); + resolve(); + }); + }); + + callback.should.be.calledWith(firebase.native.auth().currentUser); + callback.should.be.calledOnce(); + + // Sign out + + await firebase.native.auth().signOut(); + + await new Promise(resolve => { + setTimeout(() => resolve(), 5); + }); + + // Assertions + + callback.should.be.calledWith(null); + // Because of the way onUserChanged works, it will be called double + // - once for onAuthStateChanged + // - once for onIdTokenChanged + callback.should.have.callCount(4); + + // Tear down + + unsubscribe(); }); - // Assertions + it('stops listening when unsubscribed', async () => { + await firebase.native.auth().signInAnonymously(); - callback.should.be.calledWith(null); - // Because of the way onUserChanged works, it will be called double - // - once for onAuthStateChanged - // - once for onIdTokenChanged - callback.should.have.callCount(4); + // Test + const callback = sinon.spy(); - // Unsubscribe + let unsubscribe; + await new Promise(resolve => { + unsubscribe = firebase.native.auth().onUserChanged(user => { + callback(user); + resolve(); + }); + }); - unsubscribe(); + callback.should.be.calledWith(firebase.native.auth().currentUser); + callback.should.be.calledOnce(); - // Sign back in + // Sign out - await firebase.native.auth().signInAnonymously(); + await firebase.native.auth().signOut(); - // Assertions + await new Promise(resolve => { + setTimeout(() => resolve(), 5); + }); - callback.should.have.callCount(4); + // Assertions - // Tear down + callback.should.be.calledWith(null); + // Because of the way onUserChanged works, it will be called double + // - once for onAuthStateChanged + // - once for onIdTokenChanged + callback.should.have.callCount(4); - await firebase.native.auth().signOut(); - }); - }); + // Unsubscribe - describe('signInAnonymously', () => { - it('it should sign in anonymously', () => { - const successCb = currentUser => { - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - should.equal(currentUser.toJSON().email, null); - currentUser.isAnonymous.should.equal(true); - currentUser.providerId.should.equal('firebase'); + unsubscribe(); - currentUser.should.equal(firebase.native.auth().currentUser); + // Sign back in - return firebase.native.auth().signOut(); - }; + await firebase.native.auth().signInAnonymously(); - return firebase.native - .auth() - .signInAnonymously() - .then(successCb); - }); - }); + // Assertions - describe('signInAnonymouslyAndRetrieveData', () => { - it('it should sign in anonymously', () => { - const successCb = currentUserCredential => { - const currentUser = currentUserCredential.user; - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - should.equal(currentUser.toJSON().email, null); - currentUser.isAnonymous.should.equal(true); - currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(firebase.native.auth().currentUser); + callback.should.have.callCount(4); - const { additionalUserInfo } = currentUserCredential; - additionalUserInfo.should.be.an.Object(); + // Tear down - return firebase.native.auth().signOut(); - }; - - return firebase.native - .auth() - .signInAnonymouslyAndRetrieveData() - .then(successCb); - }); - }); - - describe('linkWithCredential', () => { - it('it should link anonymous account <-> email account', () => { - const random = randomString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; - - const successCb = currentUser => { - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - should.equal(currentUser.toJSON().email, null); - currentUser.isAnonymous.should.equal(true); - currentUser.providerId.should.equal('firebase'); - firebase.native.auth().currentUser.uid.should.be.a.String(); - - const credential = firebase.native.auth.EmailAuthProvider.credential( - email, - pass - ); - - return currentUser - .linkWithCredential(credential) - .then(linkedUser => { - linkedUser.should.be.an.Object(); - linkedUser.should.equal(firebase.native.auth().currentUser); - linkedUser.uid.should.be.a.String(); - linkedUser.toJSON().should.be.an.Object(); - // iOS and Android are inconsistent in returning lowercase / mixed case - linkedUser - .toJSON() - .email.toLowerCase() - .should.eql(email.toLowerCase()); - linkedUser.isAnonymous.should.equal(false); - linkedUser.providerId.should.equal('firebase'); - return firebase.native.auth().signOut(); - }) - .catch(error => - firebase.native - .auth() - .signOut() - .then(() => Promise.reject(error)) - ); - }; - - return firebase.native - .auth() - .signInAnonymously() - .then(successCb); + await firebase.native.auth().signOut(); + }); }); - it('it should error on link anon <-> email if email already exists', () => { - const email = 'test@test.com'; - const pass = 'test1234'; - - const successCb = currentUser => { - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - should.equal(currentUser.toJSON().email, null); - currentUser.isAnonymous.should.equal(true); - currentUser.providerId.should.equal('firebase'); - firebase.native.auth().currentUser.uid.should.be.a.String(); - - const credential = firebase.native.auth.EmailAuthProvider.credential( - email, - pass - ); - - return currentUser - .linkWithCredential(credential) - .then(() => - firebase.native - .auth() - .signOut() - .then(() => Promise.reject(new Error('Did not error on link'))) - ) - .catch(error => - firebase.native - .auth() - .signOut() - .then(() => { - error.code.should.equal('auth/email-already-in-use'); - error.message.should.equal( - 'The email address is already in use by another account.' - ); - return Promise.resolve(); - }) - ); - }; - - return firebase.native - .auth() - .signInAnonymously() - .then(successCb); - }); - }); - - describe('linkAndRetrieveDataWithCredential', () => { - it('it should link anonymous account <-> email account', () => { - const random = randomString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; - - const successCb = currentUser => { - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - should.equal(currentUser.toJSON().email, null); - currentUser.isAnonymous.should.equal(true); - currentUser.providerId.should.equal('firebase'); - firebase.native.auth().currentUser.uid.should.be.a.String(); - - const credential = firebase.native.auth.EmailAuthProvider.credential( - email, - pass - ); - - return currentUser - .linkAndRetrieveDataWithCredential(credential) - .then(linkedUserCredential => { - linkedUserCredential.should.be.an.Object(); - const linkedUser = linkedUserCredential.user; - linkedUser.should.be.an.Object(); - linkedUser.should.equal(firebase.native.auth().currentUser); - linkedUser.uid.should.be.a.String(); - linkedUser.toJSON().should.be.an.Object(); - // iOS and Android are inconsistent in returning lowercase / mixed case - linkedUser - .toJSON() - .email.toLowerCase() - .should.eql(email.toLowerCase()); - linkedUser.isAnonymous.should.equal(false); - linkedUser.providerId.should.equal('firebase'); - // TODO: iOS is incorrect, passes on Android - // const additionalUserInfo = linkedUserCredential.additionalUserInfo; - // additionalUserInfo.should.be.an.Object(); - // additionalUserInfo.isNewUser.should.equal(false); - return firebase.native.auth().signOut(); - }) - .catch(error => - firebase.native - .auth() - .signOut() - .then(() => Promise.reject(error)) - ); - }; - - return firebase.native - .auth() - .signInAnonymously() - .then(successCb); - }); - - it('it should error on link anon <-> email if email already exists', () => { - const email = 'test@test.com'; - const pass = 'test1234'; - - const successCb = currentUser => { - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - should.equal(currentUser.toJSON().email, null); - currentUser.isAnonymous.should.equal(true); - currentUser.providerId.should.equal('firebase'); - firebase.native.auth().currentUser.uid.should.be.a.String(); - - const credential = firebase.native.auth.EmailAuthProvider.credential( - email, - pass - ); - - return currentUser - .linkAndRetrieveDataWithCredential(credential) - .then(() => - firebase.native - .auth() - .signOut() - .then(() => Promise.reject(new Error('Did not error on link'))) - ) - .catch(error => - firebase.native - .auth() - .signOut() - .then(() => { - error.code.should.equal('auth/email-already-in-use'); - error.message.should.equal( - 'The email address is already in use by another account.' - ); - return Promise.resolve(); - }) - ); - }; - - return firebase.native - .auth() - .signInAnonymously() - .then(successCb); - }); - }); - - describe('signInWithEmailAndPassword', () => { - it('it should login with email and password', () => { - const email = 'test@test.com'; - const pass = 'test1234'; - - const successCb = currentUser => { - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - currentUser.toJSON().email.should.eql('test@test.com'); - currentUser.isAnonymous.should.equal(false); - currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(firebase.native.auth().currentUser); - - return firebase.native.auth().signOut(); - }; - - return firebase.native - .auth() - .signInWithEmailAndPassword(email, pass) - .then(successCb); - }); - - it('it should error on login if user is disabled', () => { - const email = 'disabled@account.com'; - const pass = 'test1234'; - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/user-disabled'); - error.message.should.equal( - 'The user account has been disabled by an administrator.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .signInWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - - it('it should error on login if password incorrect', () => { - const email = 'test@test.com'; - const pass = 'test1234666'; - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/wrong-password'); - error.message.should.equal( - 'The password is invalid or the user does not have a password.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .signInWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - - it('it should error on login if user not found', () => { - const email = 'randomSomeone@fourOhFour.com'; - const pass = 'test1234'; - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/user-not-found'); - error.message.should.equal( - 'There is no user record corresponding to this identifier. The user may have been deleted.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .signInWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - }); - - describe('signInAndRetrieveDataWithEmailAndPassword', () => { - it('it should login with email and password', () => { - const email = 'test@test.com'; - const pass = 'test1234'; - - const successCb = currentUserCredential => { - const currentUser = currentUserCredential.user; - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - currentUser.toJSON().email.should.eql('test@test.com'); - currentUser.isAnonymous.should.equal(false); - currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(firebase.native.auth().currentUser); - - const { additionalUserInfo } = currentUserCredential; - additionalUserInfo.should.be.an.Object(); - additionalUserInfo.isNewUser.should.equal(false); - - return firebase.native.auth().signOut(); - }; - - return firebase.native - .auth() - .signInAndRetrieveDataWithEmailAndPassword(email, pass) - .then(successCb); - }); - - it('it should error on login if user is disabled', () => { - const email = 'disabled@account.com'; - const pass = 'test1234'; - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/user-disabled'); - error.message.should.equal( - 'The user account has been disabled by an administrator.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .signInAndRetrieveDataWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - - it('it should error on login if password incorrect', () => { - const email = 'test@test.com'; - const pass = 'test1234666'; - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/wrong-password'); - error.message.should.equal( - 'The password is invalid or the user does not have a password.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .signInAndRetrieveDataWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - - it('it should error on login if user not found', () => { - const email = 'randomSomeone@fourOhFour.com'; - const pass = 'test1234'; - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/user-not-found'); - error.message.should.equal( - 'There is no user record corresponding to this identifier. The user may have been deleted.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .signInAndRetrieveDataWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - }); - - describe('signInWithCredential', () => { - it('it should login with email and password', () => { - const credential = firebase.native.auth.EmailAuthProvider.credential( - 'test@test.com', - 'test1234' - ); - - const successCb = currentUser => { - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - currentUser.toJSON().email.should.eql('test@test.com'); - currentUser.isAnonymous.should.equal(false); - currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(firebase.native.auth().currentUser); - - return firebase.native.auth().signOut(); - }; - - return firebase.native - .auth() - .signInWithCredential(credential) - .then(successCb); - }); - - it('it should error on login if user is disabled', () => { - const credential = firebase.native.auth.EmailAuthProvider.credential( - 'disabled@account.com', - 'test1234' - ); - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/user-disabled'); - error.message.should.equal( - 'The user account has been disabled by an administrator.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .signInWithCredential(credential) - .then(successCb) - .catch(failureCb); - }); - - it('it should error on login if password incorrect', () => { - const credential = firebase.native.auth.EmailAuthProvider.credential( - 'test@test.com', - 'test1234666' - ); - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/wrong-password'); - error.message.should.equal( - 'The password is invalid or the user does not have a password.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .signInWithCredential(credential) - .then(successCb) - .catch(failureCb); - }); - - it('it should error on login if user not found', () => { - const credential = firebase.native.auth.EmailAuthProvider.credential( - 'randomSomeone@fourOhFour.com', - 'test1234' - ); - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/user-not-found'); - error.message.should.equal( - 'There is no user record corresponding to this identifier. The user may have been deleted.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .signInWithCredential(credential) - .then(successCb) - .catch(failureCb); - }); - }); - - describe('signInAndRetrieveDataWithCredential', () => { - it('it should login with email and password', () => { - const credential = firebase.native.auth.EmailAuthProvider.credential( - 'test@test.com', - 'test1234' - ); - - const successCb = currentUserCredential => { - const currentUser = currentUserCredential.user; - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - currentUser.toJSON().email.should.eql('test@test.com'); - currentUser.isAnonymous.should.equal(false); - currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(firebase.native.auth().currentUser); - - const { additionalUserInfo } = currentUserCredential; - additionalUserInfo.should.be.an.Object(); - additionalUserInfo.isNewUser.should.equal(false); - - return firebase.native.auth().signOut(); - }; - - return firebase.native - .auth() - .signInAndRetrieveDataWithCredential(credential) - .then(successCb); - }); - - it('it should error on login if user is disabled', () => { - const credential = firebase.native.auth.EmailAuthProvider.credential( - 'disabled@account.com', - 'test1234' - ); - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/user-disabled'); - error.message.should.equal( - 'The user account has been disabled by an administrator.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .signInAndRetrieveDataWithCredential(credential) - .then(successCb) - .catch(failureCb); - }); - - it('it should error on login if password incorrect', () => { - const credential = firebase.native.auth.EmailAuthProvider.credential( - 'test@test.com', - 'test1234666' - ); - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/wrong-password'); - error.message.should.equal( - 'The password is invalid or the user does not have a password.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .signInAndRetrieveDataWithCredential(credential) - .then(successCb) - .catch(failureCb); - }); - - it('it should error on login if user not found', () => { - const credential = firebase.native.auth.EmailAuthProvider.credential( - 'randomSomeone@fourOhFour.com', - 'test1234' - ); - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/user-not-found'); - error.message.should.equal( - 'There is no user record corresponding to this identifier. The user may have been deleted.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .signInAndRetrieveDataWithCredential(credential) - .then(successCb) - .catch(failureCb); - }); - }); - - describe('createUserWithEmailAndPassword', () => { - it('it should create a user with an email and password', () => { - const random = randomString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; - - const successCb = newUser => { - newUser.uid.should.be.a.String(); - newUser.email.should.equal(email.toLowerCase()); - newUser.emailVerified.should.equal(false); - newUser.isAnonymous.should.equal(false); - newUser.providerId.should.equal('firebase'); - newUser.should.equal(firebase.native.auth().currentUser); - }; - - return firebase.native - .auth() - .createUserWithEmailAndPassword(email, pass) - .then(successCb); - }); - - it('it should error on create with invalid email', () => { - const random = randomString(12, '#aA'); - const email = `${random}${random}.com.boop.shoop`; - const pass = random; - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/invalid-email'); - error.message.should.equal('The email address is badly formatted.'); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .createUserWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - - it('it should error on create if email in use', () => { - const email = 'test@test.com'; - const pass = 'test123456789'; - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/email-already-in-use'); - error.message.should.equal( - 'The email address is already in use by another account.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .createUserWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - - it('it should error on create if password weak', () => { - const email = 'testy@testy.com'; - const pass = '123'; - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/weak-password'); - // cannot test this message - it's different on the web client than ios/android return - // error.message.should.equal('The given password is invalid.'); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .createUserWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - }); - - describe('createUserAndRetrieveDataWithEmailAndPassword', () => { - it('it should create a user with an email and password', () => { - const random = randomString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; - - const successCb = newUserCredential => { - const newUser = newUserCredential.user; - newUser.uid.should.be.a.String(); - newUser.email.should.equal(email.toLowerCase()); - newUser.emailVerified.should.equal(false); - newUser.isAnonymous.should.equal(false); - newUser.providerId.should.equal('firebase'); - newUser.should.equal(firebase.native.auth().currentUser); - const { additionalUserInfo } = newUserCredential; - additionalUserInfo.should.be.an.Object(); - additionalUserInfo.isNewUser.should.equal(true); - }; - - return firebase.native - .auth() - .createUserAndRetrieveDataWithEmailAndPassword(email, pass) - .then(successCb); - }); - - it('it should error on create with invalid email', () => { - const random = randomString(12, '#aA'); - const email = `${random}${random}.com.boop.shoop`; - const pass = random; - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/invalid-email'); - error.message.should.equal('The email address is badly formatted.'); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .createUserAndRetrieveDataWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - - it('it should error on create if email in use', () => { - const email = 'test@test.com'; - const pass = 'test123456789'; - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/email-already-in-use'); - error.message.should.equal( - 'The email address is already in use by another account.' - ); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .createUserAndRetrieveDataWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - - it('it should error on create if password weak', () => { - const email = 'testy@testy.com'; - const pass = '123'; - - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/weak-password'); - // cannot test this message - it's different on the web client than ios/android return - // error.message.should.equal('The given password is invalid.'); - return Promise.resolve(); - }; - - return firebase.native - .auth() - .createUserAndRetrieveDataWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - }); - - describe('fetchProvidersForEmail', () => { - it('it should return password provider for an email address', () => - new Promise((resolve, reject) => { - const successCb = tryCatch(providers => { - providers.should.be.a.Array(); - providers.should.containEql('password'); - resolve(); - }, reject); - - const failureCb = tryCatch(() => { - reject(new Error('Should not have an error.')); - }, reject); + context('signInAnonymously', () => { + it('it should sign in anonymously', () => { + const successCb = currentUser => { + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + + currentUser.should.equal(firebase.native.auth().currentUser); + + return firebase.native.auth().signOut(); + }; return firebase.native .auth() - .fetchProvidersForEmail('test@test.com') - .then(successCb) - .catch(failureCb); - })); + .signInAnonymously() + .then(successCb); + }); + }); - it('it should return an empty array for a not found email', () => - new Promise((resolve, reject) => { - const successCb = tryCatch(providers => { - providers.should.be.a.Array(); - providers.should.be.empty(); - resolve(); - }, reject); + context('signInAnonymouslyAndRetrieveData', () => { + it('it should sign in anonymously', () => { + const successCb = currentUserCredential => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.native.auth().currentUser); - const failureCb = tryCatch(() => { - reject(new Error('Should not have an error.')); - }, reject); + const { additionalUserInfo } = currentUserCredential; + additionalUserInfo.should.be.an.Object(); + + return firebase.native.auth().signOut(); + }; return firebase.native .auth() - .fetchProvidersForEmail('test@i-do-not-exist.com') + .signInAnonymouslyAndRetrieveData() + .then(successCb); + }); + }); + + context('signInWithEmailAndPassword', () => { + it('it should login with email and password', () => { + const email = 'test@test.com'; + const pass = 'test1234'; + + const successCb = currentUser => { + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql('test@test.com'); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.native.auth().currentUser); + + return firebase.native.auth().signOut(); + }; + + return firebase.native + .auth() + .signInWithEmailAndPassword(email, pass) + .then(successCb); + }); + + it('it should error on login if user is disabled', () => { + const email = 'disabled@account.com'; + const pass = 'test1234'; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-disabled'); + error.message.should.equal( + 'The user account has been disabled by an administrator.' + ); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .signInWithEmailAndPassword(email, pass) .then(successCb) .catch(failureCb); - })); + }); - it('it should return an error for a bad email address', () => - new Promise((resolve, reject) => { - const successCb = tryCatch(() => { - reject(new Error('Should not have successfully resolved.')); - }, reject); + it('it should error on login if password incorrect', () => { + const email = 'test@test.com'; + const pass = 'test1234666'; - const failureCb = tryCatch(error => { + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/wrong-password'); + error.message.should.equal( + 'The password is invalid or the user does not have a password.' + ); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .signInWithEmailAndPassword(email, pass) + .then(successCb) + .catch(failureCb); + }); + + it('it should error on login if user not found', () => { + const email = 'randomSomeone@fourOhFour.com'; + const pass = 'test1234'; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-not-found'); + error.message.should.equal( + 'There is no user record corresponding to this identifier. The user may have been deleted.' + ); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .signInWithEmailAndPassword(email, pass) + .then(successCb) + .catch(failureCb); + }); + }); + + context('signInAndRetrieveDataWithEmailAndPassword', () => { + it('it should login with email and password', () => { + const email = 'test@test.com'; + const pass = 'test1234'; + + const successCb = currentUserCredential => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql('test@test.com'); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.native.auth().currentUser); + + const { additionalUserInfo } = currentUserCredential; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(false); + + return firebase.native.auth().signOut(); + }; + + return firebase.native + .auth() + .signInAndRetrieveDataWithEmailAndPassword(email, pass) + .then(successCb); + }); + + it('it should error on login if user is disabled', () => { + const email = 'disabled@account.com'; + const pass = 'test1234'; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-disabled'); + error.message.should.equal( + 'The user account has been disabled by an administrator.' + ); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .signInAndRetrieveDataWithEmailAndPassword(email, pass) + .then(successCb) + .catch(failureCb); + }); + + it('it should error on login if password incorrect', () => { + const email = 'test@test.com'; + const pass = 'test1234666'; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/wrong-password'); + error.message.should.equal( + 'The password is invalid or the user does not have a password.' + ); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .signInAndRetrieveDataWithEmailAndPassword(email, pass) + .then(successCb) + .catch(failureCb); + }); + + it('it should error on login if user not found', () => { + const email = 'randomSomeone@fourOhFour.com'; + const pass = 'test1234'; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-not-found'); + error.message.should.equal( + 'There is no user record corresponding to this identifier. The user may have been deleted.' + ); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .signInAndRetrieveDataWithEmailAndPassword(email, pass) + .then(successCb) + .catch(failureCb); + }); + }); + + context('signInWithCredential', () => { + it('it should login with email and password', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential( + 'test@test.com', + 'test1234' + ); + + const successCb = currentUser => { + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql('test@test.com'); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.native.auth().currentUser); + + return firebase.native.auth().signOut(); + }; + + return firebase.native + .auth() + .signInWithCredential(credential) + .then(successCb); + }); + + it('it should error on login if user is disabled', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential( + 'disabled@account.com', + 'test1234' + ); + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-disabled'); + error.message.should.equal( + 'The user account has been disabled by an administrator.' + ); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .signInWithCredential(credential) + .then(successCb) + .catch(failureCb); + }); + + it('it should error on login if password incorrect', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential( + 'test@test.com', + 'test1234666' + ); + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/wrong-password'); + error.message.should.equal( + 'The password is invalid or the user does not have a password.' + ); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .signInWithCredential(credential) + .then(successCb) + .catch(failureCb); + }); + + it('it should error on login if user not found', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential( + 'randomSomeone@fourOhFour.com', + 'test1234' + ); + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-not-found'); + error.message.should.equal( + 'There is no user record corresponding to this identifier. The user may have been deleted.' + ); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .signInWithCredential(credential) + .then(successCb) + .catch(failureCb); + }); + }); + + context('signInAndRetrieveDataWithCredential', () => { + it('it should login with email and password', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential( + 'test@test.com', + 'test1234' + ); + + const successCb = currentUserCredential => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql('test@test.com'); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.native.auth().currentUser); + + const { additionalUserInfo } = currentUserCredential; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(false); + + return firebase.native.auth().signOut(); + }; + + return firebase.native + .auth() + .signInAndRetrieveDataWithCredential(credential) + .then(successCb); + }); + + it('it should error on login if user is disabled', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential( + 'disabled@account.com', + 'test1234' + ); + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-disabled'); + error.message.should.equal( + 'The user account has been disabled by an administrator.' + ); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .signInAndRetrieveDataWithCredential(credential) + .then(successCb) + .catch(failureCb); + }); + + it('it should error on login if password incorrect', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential( + 'test@test.com', + 'test1234666' + ); + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/wrong-password'); + error.message.should.equal( + 'The password is invalid or the user does not have a password.' + ); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .signInAndRetrieveDataWithCredential(credential) + .then(successCb) + .catch(failureCb); + }); + + it('it should error on login if user not found', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential( + 'randomSomeone@fourOhFour.com', + 'test1234' + ); + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-not-found'); + error.message.should.equal( + 'There is no user record corresponding to this identifier. The user may have been deleted.' + ); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .signInAndRetrieveDataWithCredential(credential) + .then(successCb) + .catch(failureCb); + }); + }); + + context('createUserWithEmailAndPassword', () => { + it('it should create a user with an email and password', () => { + const random = randomString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const successCb = newUser => { + newUser.uid.should.be.a.String(); + newUser.email.should.equal(email.toLowerCase()); + newUser.emailVerified.should.equal(false); + newUser.isAnonymous.should.equal(false); + newUser.providerId.should.equal('firebase'); + newUser.should.equal(firebase.native.auth().currentUser); + }; + + return firebase.native + .auth() + .createUserWithEmailAndPassword(email, pass) + .then(successCb); + }); + + it('it should error on create with invalid email', () => { + const random = randomString(12, '#aA'); + const email = `${random}${random}.com.boop.shoop`; + const pass = random; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { error.code.should.equal('auth/invalid-email'); error.message.should.equal('The email address is badly formatted.'); - resolve(); - }, reject); + return Promise.resolve(); + }; return firebase.native .auth() - .fetchProvidersForEmail('foobar') + .createUserWithEmailAndPassword(email, pass) .then(successCb) .catch(failureCb); - })); - }); + }); - describe('Misc', () => { - it('it should delete a user', () => { - const random = randomString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; + it('it should error on create if email in use', () => { + const email = 'test@test.com'; + const pass = 'test123456789'; - const successCb = newUser => { - newUser.uid.should.be.a.String(); - newUser.email.should.equal(email.toLowerCase()); - newUser.emailVerified.should.equal(false); - newUser.isAnonymous.should.equal(false); - newUser.providerId.should.equal('firebase'); - return firebase.native.auth().currentUser.delete(); - }; + const successCb = () => Promise.reject(new Error('Did not error.')); - return firebase.native - .auth() - .createUserWithEmailAndPassword(email, pass) - .then(successCb); - }); - - it('it should return a token via getIdToken', () => { - const random = randomString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; - - const successCb = newUser => { - newUser.uid.should.be.a.String(); - newUser.email.should.equal(email.toLowerCase()); - newUser.emailVerified.should.equal(false); - newUser.isAnonymous.should.equal(false); - newUser.providerId.should.equal('firebase'); - - return newUser.getIdToken().then(token => { - token.should.be.a.String(); - token.length.should.be.greaterThan(24); - return firebase.native.auth().currentUser.delete(); - }); - }; - - return firebase.native - .auth() - .createUserWithEmailAndPassword(email, pass) - .then(successCb); - }); - - it('it should reject signOut if no currentUser', () => - new Promise((resolve, reject) => { - if (firebase.native.auth().currentUser) { - return reject( - new Error( - `A user is currently signed in. ${ - firebase.native.auth().currentUser.uid - }` - ) + const failureCb = error => { + error.code.should.equal('auth/email-already-in-use'); + error.message.should.equal( + 'The email address is already in use by another account.' ); - } - - const successCb = tryCatch(() => { - reject(new Error('No signOut error returned')); - }, reject); - - const failureCb = tryCatch(error => { - error.code.should.equal('auth/no-current-user'); - error.message.should.equal('No user currently signed in.'); - resolve(); - }, reject); + return Promise.resolve(); + }; return firebase.native .auth() - .signOut() + .createUserWithEmailAndPassword(email, pass) .then(successCb) .catch(failureCb); - })); + }); - it('it should change the language code', () => { - // eslint-disable-next-line no-param-reassign - firebase.native.auth().languageCode = 'en'; - if (firebase.native.auth().languageCode !== 'en') { - throw new Error('Expected language code to be "en".'); - } - // eslint-disable-next-line no-param-reassign - firebase.native.auth().languageCode = 'fr'; - if (firebase.native.auth().languageCode !== 'fr') { - throw new Error('Expected language code to be "fr".'); - } - // eslint-disable-next-line no-param-reassign - firebase.native.auth().languageCode = 'en'; + it('it should error on create if password weak', () => { + const email = 'testy@testy.com'; + const pass = '123'; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/weak-password'); + // cannot test this message - it's different on the web client than ios/android return + // error.message.should.equal('The given password is invalid.'); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .createUserWithEmailAndPassword(email, pass) + .then(successCb) + .catch(failureCb); + }); + }); + + context('createUserAndRetrieveDataWithEmailAndPassword', () => { + it('it should create a user with an email and password', () => { + const random = randomString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const successCb = newUserCredential => { + const newUser = newUserCredential.user; + newUser.uid.should.be.a.String(); + newUser.email.should.equal(email.toLowerCase()); + newUser.emailVerified.should.equal(false); + newUser.isAnonymous.should.equal(false); + newUser.providerId.should.equal('firebase'); + newUser.should.equal(firebase.native.auth().currentUser); + const { additionalUserInfo } = newUserCredential; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(true); + }; + + return firebase.native + .auth() + .createUserAndRetrieveDataWithEmailAndPassword(email, pass) + .then(successCb); + }); + + it('it should error on create with invalid email', () => { + const random = randomString(12, '#aA'); + const email = `${random}${random}.com.boop.shoop`; + const pass = random; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/invalid-email'); + error.message.should.equal('The email address is badly formatted.'); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .createUserAndRetrieveDataWithEmailAndPassword(email, pass) + .then(successCb) + .catch(failureCb); + }); + + it('it should error on create if email in use', () => { + const email = 'test@test.com'; + const pass = 'test123456789'; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/email-already-in-use'); + error.message.should.equal( + 'The email address is already in use by another account.' + ); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .createUserAndRetrieveDataWithEmailAndPassword(email, pass) + .then(successCb) + .catch(failureCb); + }); + + it('it should error on create if password weak', () => { + const email = 'testy@testy.com'; + const pass = '123'; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/weak-password'); + // cannot test this message - it's different on the web client than ios/android return + // error.message.should.equal('The given password is invalid.'); + return Promise.resolve(); + }; + + return firebase.native + .auth() + .createUserAndRetrieveDataWithEmailAndPassword(email, pass) + .then(successCb) + .catch(failureCb); + }); + }); + + context('fetchProvidersForEmail', () => { + it('it should return password provider for an email address', () => + new Promise((resolve, reject) => { + const successCb = tryCatch(providers => { + providers.should.be.a.Array(); + providers.should.containEql('password'); + resolve(); + }, reject); + + const failureCb = tryCatch(() => { + reject(new Error('Should not have an error.')); + }, reject); + + return firebase.native + .auth() + .fetchProvidersForEmail('test@test.com') + .then(successCb) + .catch(failureCb); + })); + + it('it should return an empty array for a not found email', () => + new Promise((resolve, reject) => { + const successCb = tryCatch(providers => { + providers.should.be.a.Array(); + providers.should.be.empty(); + resolve(); + }, reject); + + const failureCb = tryCatch(() => { + reject(new Error('Should not have an error.')); + }, reject); + + return firebase.native + .auth() + .fetchProvidersForEmail('test@i-do-not-exist.com') + .then(successCb) + .catch(failureCb); + })); + + it('it should return an error for a bad email address', () => + new Promise((resolve, reject) => { + const successCb = tryCatch(() => { + reject(new Error('Should not have successfully resolved.')); + }, reject); + + const failureCb = tryCatch(error => { + error.code.should.equal('auth/invalid-email'); + error.message.should.equal('The email address is badly formatted.'); + resolve(); + }, reject); + + return firebase.native + .auth() + .fetchProvidersForEmail('foobar') + .then(successCb) + .catch(failureCb); + })); + }); + + context('delete()', () => { + it('should delete a user', () => { + const random = randomString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const successCb = newUser => { + newUser.uid.should.be.a.String(); + newUser.email.should.equal(email.toLowerCase()); + newUser.emailVerified.should.equal(false); + newUser.isAnonymous.should.equal(false); + newUser.providerId.should.equal('firebase'); + return firebase.native.auth().currentUser.delete(); + }; + + return firebase.native + .auth() + .createUserWithEmailAndPassword(email, pass) + .then(successCb); + }); + }); + + context('languageCode', () => { + it('it should change the language code', () => { + // eslint-disable-next-line no-param-reassign + firebase.native.auth().languageCode = 'en'; + if (firebase.native.auth().languageCode !== 'en') { + throw new Error('Expected language code to be "en".'); + } + // eslint-disable-next-line no-param-reassign + firebase.native.auth().languageCode = 'fr'; + if (firebase.native.auth().languageCode !== 'fr') { + throw new Error('Expected language code to be "fr".'); + } + // eslint-disable-next-line no-param-reassign + firebase.native.auth().languageCode = 'en'; + }); + }); + + context('getRedirectResult', () => { + it('should throw an unsupported error', () => { + (() => { + firebase.native.auth().getRedirectResult(); + }).should.throw( + 'firebase.auth().getRedirectResult() is unsupported by the native Firebase SDKs.' + ); + }); + }); + + context('setPersistence', () => { + it('should throw an unsupported error', () => { + (() => { + firebase.native.auth().setPersistence(); + }).should.throw( + 'firebase.auth().setPersistence() is unsupported by the native Firebase SDKs.' + ); + }); + }); + + context('signInWithPopup', () => { + it('should throw an unsupported error', () => { + (() => { + firebase.native.auth().signInWithPopup(); + }).should.throw( + 'firebase.auth().signInWithPopup() is unsupported by the native Firebase SDKs.' + ); + }); + }); + + context('signInWithRedirect', () => { + it('should throw an unsupported error', () => { + (() => { + firebase.native.auth().signInWithRedirect(); + }).should.throw( + 'firebase.auth().signInWithRedirect() is unsupported by the native Firebase SDKs.' + ); + }); + }); + + context('useDeviceLanguage', () => { + it('should throw an unsupported error', () => { + (() => { + firebase.native.auth().useDeviceLanguage(); + }).should.throw( + 'firebase.auth().useDeviceLanguage() is unsupported by the native Firebase SDKs.' + ); + }); }); }); }); diff --git a/tests/src/tests/auth/index.js b/tests/src/tests/auth/index.js index fff18020..fa10795e 100644 --- a/tests/src/tests/auth/index.js +++ b/tests/src/tests/auth/index.js @@ -2,9 +2,11 @@ import firebase from '../../firebase'; import TestSuite from '../../../lib/TestSuite'; import authTests from './authTests'; +import userTests from './userTests'; const suite = new TestSuite('Auth', 'firebase.auth()', firebase); suite.addTests(authTests); +suite.addTests(userTests); export default suite; diff --git a/tests/src/tests/auth/userTests.js b/tests/src/tests/auth/userTests.js new file mode 100644 index 00000000..6b337d12 --- /dev/null +++ b/tests/src/tests/auth/userTests.js @@ -0,0 +1,399 @@ +import should from 'should'; + +const randomString = (length, chars) => { + let mask = ''; + if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz'; + if (chars.indexOf('A') > -1) mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + if (chars.indexOf('#') > -1) mask += '0123456789'; + if (chars.indexOf('!') > -1) mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\'; + let result = ''; + for (let i = length; i > 0; --i) { + result += mask[Math.round(Math.random() * (mask.length - 1))]; + } + return result; +}; + +export default (userTests = ({ tryCatch, context, describe, it, firebase }) => { + describe('User', () => { + context('getIdToken()', () => { + it('should return a token', () => { + const random = randomString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const successCb = newUser => { + newUser.uid.should.be.a.String(); + newUser.email.should.equal(email.toLowerCase()); + newUser.emailVerified.should.equal(false); + newUser.isAnonymous.should.equal(false); + newUser.providerId.should.equal('firebase'); + + return newUser.getIdToken().then(token => { + token.should.be.a.String(); + token.length.should.be.greaterThan(24); + return firebase.native.auth().currentUser.delete(); + }); + }; + + return firebase.native + .auth() + .createUserWithEmailAndPassword(email, pass) + .then(successCb); + }); + }); + + context('getToken()', () => { + it('should return a token', () => { + const random = randomString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const successCb = newUser => { + newUser.uid.should.be.a.String(); + newUser.email.should.equal(email.toLowerCase()); + newUser.emailVerified.should.equal(false); + newUser.isAnonymous.should.equal(false); + newUser.providerId.should.equal('firebase'); + + return newUser.getToken().then(token => { + token.should.be.a.String(); + token.length.should.be.greaterThan(24); + return firebase.native.auth().currentUser.delete(); + }); + }; + + return firebase.native + .auth() + .createUserWithEmailAndPassword(email, pass) + .then(successCb); + }); + }); + + context('linkWithCredential()', () => { + it('it should link anonymous account <-> email account', () => { + const random = randomString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const successCb = currentUser => { + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + firebase.native.auth().currentUser.uid.should.be.a.String(); + + const credential = firebase.native.auth.EmailAuthProvider.credential( + email, + pass + ); + + return currentUser + .linkWithCredential(credential) + .then(linkedUser => { + linkedUser.should.be.an.Object(); + linkedUser.should.equal(firebase.native.auth().currentUser); + linkedUser.uid.should.be.a.String(); + linkedUser.toJSON().should.be.an.Object(); + // iOS and Android are inconsistent in returning lowercase / mixed case + linkedUser + .toJSON() + .email.toLowerCase() + .should.eql(email.toLowerCase()); + linkedUser.isAnonymous.should.equal(false); + linkedUser.providerId.should.equal('firebase'); + return firebase.native.auth().signOut(); + }) + .catch(error => + firebase.native + .auth() + .signOut() + .then(() => Promise.reject(error)) + ); + }; + + return firebase.native + .auth() + .signInAnonymously() + .then(successCb); + }); + + it('it should error on link anon <-> email if email already exists', () => { + const email = 'test@test.com'; + const pass = 'test1234'; + + const successCb = currentUser => { + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + firebase.native.auth().currentUser.uid.should.be.a.String(); + + const credential = firebase.native.auth.EmailAuthProvider.credential( + email, + pass + ); + + return currentUser + .linkWithCredential(credential) + .then(() => + firebase.native + .auth() + .signOut() + .then(() => Promise.reject(new Error('Did not error on link'))) + ) + .catch(error => + firebase.native + .auth() + .signOut() + .then(() => { + error.code.should.equal('auth/email-already-in-use'); + error.message.should.equal( + 'The email address is already in use by another account.' + ); + return Promise.resolve(); + }) + ); + }; + + return firebase.native + .auth() + .signInAnonymously() + .then(successCb); + }); + }); + + context('linkAndRetrieveDataWithCredential()', () => { + it('it should link anonymous account <-> email account', () => { + const random = randomString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const successCb = currentUser => { + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + firebase.native.auth().currentUser.uid.should.be.a.String(); + + const credential = firebase.native.auth.EmailAuthProvider.credential( + email, + pass + ); + + return currentUser + .linkAndRetrieveDataWithCredential(credential) + .then(linkedUserCredential => { + linkedUserCredential.should.be.an.Object(); + const linkedUser = linkedUserCredential.user; + linkedUser.should.be.an.Object(); + linkedUser.should.equal(firebase.native.auth().currentUser); + linkedUser.uid.should.be.a.String(); + linkedUser.toJSON().should.be.an.Object(); + // iOS and Android are inconsistent in returning lowercase / mixed case + linkedUser + .toJSON() + .email.toLowerCase() + .should.eql(email.toLowerCase()); + linkedUser.isAnonymous.should.equal(false); + linkedUser.providerId.should.equal('firebase'); + // TODO: iOS is incorrect, passes on Android + // const additionalUserInfo = linkedUserCredential.additionalUserInfo; + // additionalUserInfo.should.be.an.Object(); + // additionalUserInfo.isNewUser.should.equal(false); + return firebase.native.auth().signOut(); + }) + .catch(error => + firebase.native + .auth() + .signOut() + .then(() => Promise.reject(error)) + ); + }; + + return firebase.native + .auth() + .signInAnonymously() + .then(successCb); + }); + + it('it should error on link anon <-> email if email already exists', () => { + const email = 'test@test.com'; + const pass = 'test1234'; + + const successCb = currentUser => { + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + firebase.native.auth().currentUser.uid.should.be.a.String(); + + const credential = firebase.native.auth.EmailAuthProvider.credential( + email, + pass + ); + + return currentUser + .linkAndRetrieveDataWithCredential(credential) + .then(() => + firebase.native + .auth() + .signOut() + .then(() => Promise.reject(new Error('Did not error on link'))) + ) + .catch(error => + firebase.native + .auth() + .signOut() + .then(() => { + error.code.should.equal('auth/email-already-in-use'); + error.message.should.equal( + 'The email address is already in use by another account.' + ); + return Promise.resolve(); + }) + ); + }; + + return firebase.native + .auth() + .signInAnonymously() + .then(successCb); + }); + }); + + context('signOut()', () => { + it('it should reject signOut if no currentUser', () => + new Promise((resolve, reject) => { + if (firebase.native.auth().currentUser) { + return reject( + new Error( + `A user is currently signed in. ${ + firebase.native.auth().currentUser.uid + }` + ) + ); + } + + const successCb = tryCatch(() => { + reject(new Error('No signOut error returned')); + }, reject); + + const failureCb = tryCatch(error => { + error.code.should.equal('auth/no-current-user'); + error.message.should.equal('No user currently signed in.'); + resolve(); + }, reject); + + return firebase.native + .auth() + .signOut() + .then(successCb) + .catch(failureCb); + })); + }); + + context('linkWithPhoneNumber()', () => { + it('should throw an unsupported error', async () => { + await firebase.native.auth().signInAnonymouslyAndRetrieveData(); + (() => { + firebase.native.auth().currentUser.linkWithPhoneNumber(); + }).should.throw( + 'User.linkWithPhoneNumber() is unsupported by the native Firebase SDKs.' + ); + await firebase.native.auth().signOut(); + }); + }); + + context('linkWithPopup()', () => { + it('should throw an unsupported error', async () => { + await firebase.native.auth().signInAnonymouslyAndRetrieveData(); + (() => { + firebase.native.auth().currentUser.linkWithPopup(); + }).should.throw( + 'User.linkWithPopup() is unsupported by the native Firebase SDKs.' + ); + await firebase.native.auth().signOut(); + }); + }); + + context('linkWithRedirect()', () => { + it('should throw an unsupported error', async () => { + await firebase.native.auth().signInAnonymouslyAndRetrieveData(); + (() => { + firebase.native.auth().currentUser.linkWithRedirect(); + }).should.throw( + 'User.linkWithRedirect() is unsupported by the native Firebase SDKs.' + ); + await firebase.native.auth().signOut(); + }); + }); + + context('reauthenticateWithPhoneNumber()', () => { + it('should throw an unsupported error', async () => { + await firebase.native.auth().signInAnonymouslyAndRetrieveData(); + (() => { + firebase.native.auth().currentUser.reauthenticateWithPhoneNumber(); + }).should.throw( + 'User.reauthenticateWithPhoneNumber() is unsupported by the native Firebase SDKs.' + ); + await firebase.native.auth().signOut(); + }); + }); + + context('reauthenticateWithPopup()', () => { + it('should throw an unsupported error', async () => { + await firebase.native.auth().signInAnonymouslyAndRetrieveData(); + (() => { + firebase.native.auth().currentUser.reauthenticateWithPopup(); + }).should.throw( + 'User.reauthenticateWithPopup() is unsupported by the native Firebase SDKs.' + ); + await firebase.native.auth().signOut(); + }); + }); + + context('reauthenticateWithRedirect()', () => { + it('should throw an unsupported error', async () => { + await firebase.native.auth().signInAnonymouslyAndRetrieveData(); + (() => { + firebase.native.auth().currentUser.reauthenticateWithRedirect(); + }).should.throw( + 'User.reauthenticateWithRedirect() is unsupported by the native Firebase SDKs.' + ); + await firebase.native.auth().signOut(); + }); + }); + + context('updatePhoneNumber()', () => { + it('should throw an unsupported error', async () => { + await firebase.native.auth().signInAnonymouslyAndRetrieveData(); + (() => { + firebase.native.auth().currentUser.updatePhoneNumber(); + }).should.throw( + 'User.updatePhoneNumber() is unsupported by the native Firebase SDKs.' + ); + await firebase.native.auth().signOut(); + }); + }); + + context('refreshToken', () => { + it('should throw an unsupported error', async () => { + await firebase.native.auth().signInAnonymouslyAndRetrieveData(); + (() => { + firebase.native.auth().currentUser.refreshToken; + }).should.throw( + 'User.refreshToken is unsupported by the native Firebase SDKs.' + ); + await firebase.native.auth().signOut(); + }); + }); + }); +});