Skip to content

Commit 92d6e50

Browse files
authored
Implement useAuthEmulator and remove auth.useEmulator. (#4414)
* Implement useAuthEmulator and remove auth.useEmulator. * Remove unused imports. * Create nasty-lemons-bow.md * Refactor impl into emulator.ts. * Add options to example. * Update .changeset/nasty-lemons-bow.md * Delete nasty-lemons-bow.md
1 parent 0eeefe2 commit 92d6e50

File tree

9 files changed

+270
-196
lines changed

9 files changed

+270
-196
lines changed

common/api-review/auth-exp.api.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,11 @@ export function updatePhoneNumber(user: externs.User, credential: externs.PhoneA
524524
// @public
525525
export function updateProfile(user: externs.User, { displayName, photoURL: photoUrl }: Profile): Promise<void>;
526526

527+
// @public
528+
export function useAuthEmulator(auth: externs.Auth, url: string, options?: {
529+
disableWarnings: boolean;
530+
}): void;
531+
527532
// @public
528533
export function useDeviceLanguage(auth: externs.Auth): void;
529534

packages-exp/auth-compat-exp/src/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export class Auth
9898
return this.auth.signOut();
9999
}
100100
useEmulator(url: string, options?: { disableWarnings: boolean }): void {
101-
this.auth.useEmulator(url, options);
101+
impl.useAuthEmulator(this.auth, url, options);
102102
}
103103
applyActionCode(code: string): Promise<void> {
104104
return impl.applyActionCode(this.auth, code);

packages-exp/auth-exp/src/core/auth/auth_impl.test.ts

Lines changed: 2 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,15 @@ import * as sinonChai from 'sinon-chai';
2323
import { FirebaseApp } from '@firebase/app-types-exp';
2424
import { FirebaseError } from '@firebase/util';
2525

26-
import { endpointUrl, mockEndpoint } from '../../../test/helpers/api/helper';
27-
import { testAuth, TestAuth, testUser } from '../../../test/helpers/mock_auth';
28-
import * as fetch from '../../../test/helpers/mock_fetch';
29-
import { Endpoint } from '../../api';
26+
import { testAuth, testUser } from '../../../test/helpers/mock_auth';
3027
import { Auth } from '../../model/auth';
3128
import { User } from '../../model/user';
3229
import { Persistence } from '../persistence';
3330
import { inMemoryPersistence } from '../persistence/in_memory';
3431
import { _getInstance } from '../util/instantiator';
3532
import * as navigator from '../util/navigator';
3633
import * as reload from '../user/reload';
37-
import { _castAuth, AuthImpl, DefaultConfig } from './auth_impl';
34+
import { AuthImpl, DefaultConfig } from './auth_impl';
3835
import { _initializeAuthInstance } from './initialize';
3936

4037
use(sinonChai);
@@ -461,126 +458,3 @@ describe('core/auth/auth_impl', () => {
461458
});
462459
});
463460
});
464-
465-
// These tests are separate because they are using a different auth with
466-
// separate setup and config
467-
describe('core/auth/auth_impl useEmulator', () => {
468-
let auth: TestAuth;
469-
let user: User;
470-
let normalEndpoint: fetch.Route;
471-
let emulatorEndpoint: fetch.Route;
472-
473-
beforeEach(async () => {
474-
auth = await testAuth();
475-
user = testUser(_castAuth(auth), 'uid', 'email', true);
476-
fetch.setUp();
477-
normalEndpoint = mockEndpoint(Endpoint.DELETE_ACCOUNT, {});
478-
emulatorEndpoint = fetch.mock(
479-
`http://localhost:2020/${endpointUrl(Endpoint.DELETE_ACCOUNT).replace(
480-
/^.*:\/\//,
481-
''
482-
)}`,
483-
{}
484-
);
485-
});
486-
487-
afterEach(() => {
488-
fetch.tearDown();
489-
sinon.restore();
490-
491-
// The DOM persists through tests; remove the banner if it is attached
492-
const banner =
493-
typeof document !== 'undefined'
494-
? document.querySelector('.firebase-emulator-warning')
495-
: null;
496-
if (banner) {
497-
banner.parentElement?.removeChild(banner);
498-
}
499-
});
500-
501-
context('useEmulator', () => {
502-
it('fails if a network request has already been made', async () => {
503-
await user.delete();
504-
expect(() => auth.useEmulator('http://localhost:2020')).to.throw(
505-
FirebaseError,
506-
'auth/emulator-config-failed'
507-
);
508-
});
509-
510-
it('updates the endpoint appropriately', async () => {
511-
auth.useEmulator('http://localhost:2020');
512-
await user.delete();
513-
expect(normalEndpoint.calls.length).to.eq(0);
514-
expect(emulatorEndpoint.calls.length).to.eq(1);
515-
});
516-
517-
it('checks the scheme properly', () => {
518-
expect(() => auth.useEmulator('http://localhost:2020')).not.to.throw;
519-
delete auth.config.emulator;
520-
expect(() => auth.useEmulator('https://localhost:2020')).not.to.throw;
521-
delete auth.config.emulator;
522-
expect(() => auth.useEmulator('ssh://localhost:2020')).to.throw(
523-
FirebaseError,
524-
'auth/invalid-emulator-scheme'
525-
);
526-
delete auth.config.emulator;
527-
expect(() => auth.useEmulator('localhost:2020')).to.throw(
528-
FirebaseError,
529-
'auth/invalid-emulator-scheme'
530-
);
531-
});
532-
533-
it('attaches a banner to the DOM', () => {
534-
auth.useEmulator('http://localhost:2020');
535-
if (typeof document !== 'undefined') {
536-
const el = document.querySelector('.firebase-emulator-warning')!;
537-
expect(el).not.to.be.null;
538-
expect(el.textContent).to.eq(
539-
'Running in emulator mode. ' +
540-
'Do not use with production credentials.'
541-
);
542-
}
543-
});
544-
545-
it('logs out a warning to the console', () => {
546-
sinon.stub(console, 'info');
547-
auth.useEmulator('http://localhost:2020');
548-
expect(console.info).to.have.been.calledWith(
549-
'WARNING: You are using the Auth Emulator,' +
550-
' which is intended for local testing only. Do not use with' +
551-
' production credentials.'
552-
);
553-
});
554-
555-
it('logs out the warning but has no banner if disableBanner true', () => {
556-
sinon.stub(console, 'info');
557-
auth.useEmulator('http://localhost:2020', { disableWarnings: true });
558-
expect(console.info).to.have.been.calledWith(
559-
'WARNING: You are using the Auth Emulator,' +
560-
' which is intended for local testing only. Do not use with' +
561-
' production credentials.'
562-
);
563-
if (typeof document !== 'undefined') {
564-
expect(document.querySelector('.firebase-emulator-warning')).to.be.null;
565-
}
566-
});
567-
});
568-
569-
context('toJSON', () => {
570-
it('works when theres no current user', () => {
571-
expect(JSON.stringify(auth)).to.eq(
572-
'{"apiKey":"test-api-key","authDomain":"localhost","appName":"test-app"}'
573-
);
574-
});
575-
576-
it('also stringifies the current user', () => {
577-
auth.currentUser = ({
578-
toJSON: (): object => ({ foo: 'bar' })
579-
} as unknown) as User;
580-
expect(JSON.stringify(auth)).to.eq(
581-
'{"apiKey":"test-api-key","authDomain":"localhost",' +
582-
'"appName":"test-app","currentUser":{"foo":"bar"}}'
583-
);
584-
});
585-
});
586-
});

packages-exp/auth-exp/src/core/auth/auth_impl.ts

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -272,20 +272,6 @@ export class AuthImpl implements Auth, _FirebaseService {
272272
this.languageCode = _getUserLanguage();
273273
}
274274

275-
useEmulator(url: string, options?: { disableWarnings: boolean }): void {
276-
_assert(this._canInitEmulator, this, AuthErrorCode.EMULATOR_CONFIG_FAILED);
277-
278-
_assert(
279-
/^https?:\/\//.test(url),
280-
this,
281-
AuthErrorCode.INVALID_EMULATOR_SCHEME
282-
);
283-
284-
this.config.emulator = { url };
285-
this.settings.appVerificationDisabledForTesting = true;
286-
emitEmulatorWarning(!!options?.disableWarnings);
287-
}
288-
289275
async _delete(): Promise<void> {
290276
this._deleted = true;
291277
}
@@ -570,43 +556,3 @@ class Subscription<T> {
570556
return this.observer.next.bind(this.observer);
571557
}
572558
}
573-
574-
function emitEmulatorWarning(disableBanner: boolean): void {
575-
function attachBanner(): void {
576-
const el = document.createElement('p');
577-
const sty = el.style;
578-
el.innerText =
579-
'Running in emulator mode. Do not use with production credentials.';
580-
sty.position = 'fixed';
581-
sty.width = '100%';
582-
sty.backgroundColor = '#ffffff';
583-
sty.border = '.1em solid #000000';
584-
sty.color = '#ff0000';
585-
sty.bottom = '0px';
586-
sty.left = '0px';
587-
sty.margin = '0px';
588-
sty.zIndex = '10000';
589-
sty.textAlign = 'center';
590-
el.classList.add('firebase-emulator-warning');
591-
document.body.appendChild(el);
592-
}
593-
594-
if (typeof console !== 'undefined' && typeof console.info === 'function') {
595-
console.info(
596-
'WARNING: You are using the Auth Emulator,' +
597-
' which is intended for local testing only. Do not use with' +
598-
' production credentials.'
599-
);
600-
}
601-
if (
602-
typeof window !== 'undefined' &&
603-
typeof document !== 'undefined' &&
604-
!disableBanner
605-
) {
606-
if (document.readyState === 'loading') {
607-
window.addEventListener('DOMContentLoaded', attachBanner);
608-
} else {
609-
attachBanner();
610-
}
611-
}
612-
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { expect, use } from 'chai';
19+
import * as chaiAsPromised from 'chai-as-promised';
20+
import * as sinon from 'sinon';
21+
import * as sinonChai from 'sinon-chai';
22+
23+
import { FirebaseError } from '@firebase/util';
24+
25+
import { endpointUrl, mockEndpoint } from '../../../test/helpers/api/helper';
26+
import { testAuth, TestAuth, testUser } from '../../../test/helpers/mock_auth';
27+
import * as fetch from '../../../test/helpers/mock_fetch';
28+
import { Endpoint } from '../../api';
29+
import { User } from '../../model/user';
30+
import { _castAuth } from './auth_impl';
31+
import { useAuthEmulator } from './emulator';
32+
33+
use(sinonChai);
34+
use(chaiAsPromised);
35+
36+
describe('core/auth/emulator', () => {
37+
let auth: TestAuth;
38+
let user: User;
39+
let normalEndpoint: fetch.Route;
40+
let emulatorEndpoint: fetch.Route;
41+
42+
beforeEach(async () => {
43+
auth = await testAuth();
44+
user = testUser(_castAuth(auth), 'uid', 'email', true);
45+
fetch.setUp();
46+
normalEndpoint = mockEndpoint(Endpoint.DELETE_ACCOUNT, {});
47+
emulatorEndpoint = fetch.mock(
48+
`http://localhost:2020/${endpointUrl(Endpoint.DELETE_ACCOUNT).replace(
49+
/^.*:\/\//,
50+
''
51+
)}`,
52+
{}
53+
);
54+
});
55+
56+
afterEach(() => {
57+
fetch.tearDown();
58+
sinon.restore();
59+
60+
// The DOM persists through tests; remove the banner if it is attached
61+
const banner =
62+
typeof document !== 'undefined'
63+
? document.querySelector('.firebase-emulator-warning')
64+
: null;
65+
if (banner) {
66+
banner.parentElement?.removeChild(banner);
67+
}
68+
});
69+
70+
context('useAuthEmulator', () => {
71+
it('fails if a network request has already been made', async () => {
72+
await user.delete();
73+
expect(() => useAuthEmulator(auth, 'http://localhost:2020')).to.throw(
74+
FirebaseError,
75+
'auth/emulator-config-failed'
76+
);
77+
});
78+
79+
it('updates the endpoint appropriately', async () => {
80+
useAuthEmulator(auth, 'http://localhost:2020');
81+
await user.delete();
82+
expect(normalEndpoint.calls.length).to.eq(0);
83+
expect(emulatorEndpoint.calls.length).to.eq(1);
84+
});
85+
86+
it('checks the scheme properly', () => {
87+
expect(() => useAuthEmulator(auth, 'http://localhost:2020')).not.to.throw;
88+
delete auth.config.emulator;
89+
expect(() => useAuthEmulator(auth, 'https://localhost:2020')).not.to
90+
.throw;
91+
delete auth.config.emulator;
92+
expect(() => useAuthEmulator(auth, 'ssh://localhost:2020')).to.throw(
93+
FirebaseError,
94+
'auth/invalid-emulator-scheme'
95+
);
96+
delete auth.config.emulator;
97+
expect(() => useAuthEmulator(auth, 'localhost:2020')).to.throw(
98+
FirebaseError,
99+
'auth/invalid-emulator-scheme'
100+
);
101+
});
102+
103+
it('attaches a banner to the DOM', () => {
104+
useAuthEmulator(auth, 'http://localhost:2020');
105+
if (typeof document !== 'undefined') {
106+
const el = document.querySelector('.firebase-emulator-warning')!;
107+
expect(el).not.to.be.null;
108+
expect(el.textContent).to.eq(
109+
'Running in emulator mode. ' +
110+
'Do not use with production credentials.'
111+
);
112+
}
113+
});
114+
115+
it('logs out a warning to the console', () => {
116+
sinon.stub(console, 'info');
117+
useAuthEmulator(auth, 'http://localhost:2020');
118+
expect(console.info).to.have.been.calledWith(
119+
'WARNING: You are using the Auth Emulator,' +
120+
' which is intended for local testing only. Do not use with' +
121+
' production credentials.'
122+
);
123+
});
124+
125+
it('logs out the warning but has no banner if disableBanner true', () => {
126+
sinon.stub(console, 'info');
127+
useAuthEmulator(auth, 'http://localhost:2020', { disableWarnings: true });
128+
expect(console.info).to.have.been.calledWith(
129+
'WARNING: You are using the Auth Emulator,' +
130+
' which is intended for local testing only. Do not use with' +
131+
' production credentials.'
132+
);
133+
if (typeof document !== 'undefined') {
134+
expect(document.querySelector('.firebase-emulator-warning')).to.be.null;
135+
}
136+
});
137+
});
138+
139+
context('toJSON', () => {
140+
it('works when theres no current user', () => {
141+
expect(JSON.stringify(auth)).to.eq(
142+
'{"apiKey":"test-api-key","authDomain":"localhost","appName":"test-app"}'
143+
);
144+
});
145+
146+
it('also stringifies the current user', () => {
147+
auth.currentUser = ({
148+
toJSON: (): object => ({ foo: 'bar' })
149+
} as unknown) as User;
150+
expect(JSON.stringify(auth)).to.eq(
151+
'{"apiKey":"test-api-key","authDomain":"localhost",' +
152+
'"appName":"test-app","currentUser":{"foo":"bar"}}'
153+
);
154+
});
155+
});
156+
});

0 commit comments

Comments
 (0)