Skip to content

Commit 3a5997d

Browse files
committed
Add updateProfile, updateEmail, updatePassword
1 parent 724af8a commit 3a5997d

File tree

10 files changed

+269
-100
lines changed

10 files changed

+269
-100
lines changed

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

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export class AuthImpl implements Auth {
7171
}
7272

7373
this._isInitialized = true;
74-
this._notifyStateListeners();
74+
this.notifyAuthListeners();
7575
});
7676
}
7777
languageCode: string | null = null;
@@ -82,12 +82,15 @@ export class AuthImpl implements Auth {
8282
throw new Error('Method not implemented.');
8383
}
8484

85-
updateCurrentUser(user: User | null): Promise<void> {
86-
return this.queue(() => this.directlySetCurrentUser(user));
85+
async updateCurrentUser(user: User | null): Promise<void> {
86+
return this.queue(async () => {
87+
await this.directlySetCurrentUser(user);
88+
this.notifyAuthListeners();
89+
});
8790
}
8891

89-
signOut(): Promise<void> {
90-
return this.queue(() => this.directlySetCurrentUser(null));
92+
async signOut(): Promise<void> {
93+
return this.updateCurrentUser(null);
9194
}
9295

9396
setPersistence(persistence: Persistence): Promise<void> {
@@ -122,13 +125,20 @@ export class AuthImpl implements Auth {
122125
);
123126
}
124127

125-
async _persistAndNotifyIfCurrent(user: User): Promise<void> {
128+
async _persistUserIfCurrent(user: User): Promise<void> {
126129
if (user === this.currentUser) {
127-
return this.updateCurrentUser(user);
130+
return this.queue(async () => this.directlySetCurrentUser(user));
128131
}
129132
}
130133

131-
_notifyStateListeners(): void {
134+
/** Notifies listeners only if the user is current */
135+
_notifyListenersIfCurrent(user: User): void {
136+
if (user === this.currentUser) {
137+
this.notifyAuthListeners();
138+
}
139+
}
140+
141+
private notifyAuthListeners(): void {
132142
if (!this._isInitialized) {
133143
return;
134144
}
@@ -177,8 +187,6 @@ export class AuthImpl implements Auth {
177187
} else {
178188
await this.assertedPersistence.removeCurrentUser();
179189
}
180-
181-
this._notifyStateListeners();
182190
}
183191

184192
private queue(action: AsyncAction): Promise<void> {

packages-exp/auth-exp/src/core/persistence/in_memory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import * as externs from '@firebase/auth-types-exp';
1919

2020
import { Persistence, PersistenceType, PersistenceValue } from '../persistence';
2121

22-
class InMemoryPersistence implements Persistence {
22+
export class InMemoryPersistence implements Persistence {
2323
type: PersistenceType = PersistenceType.NONE;
2424
storage: {
2525
[key: string]: PersistenceValue;

packages-exp/auth-exp/src/core/user/account_info.test.ts

Lines changed: 164 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ import { UserInfo } from '@firebase/auth-types-exp';
2424

2525
// import { UserInfo } from '@firebase/auth-types-exp';
2626
import { mockEndpoint } from '../../../test/api/helper';
27-
import { testUser } from '../../../test/mock_auth';
27+
import { testPersistence, testUser } from '../../../test/mock_auth';
2828
import * as fetch from '../../../test/mock_fetch';
2929
import { Endpoint } from '../../api';
30+
import { Auth } from '../../model/auth';
3031
import { User } from '../../model/user';
3132
import { ProviderId } from '../providers';
32-
// import { ProviderId } from '../providers';
33-
import { updateProfile } from './account_info';
33+
import { updateEmail, updatePassword, updateProfile } from './account_info';
3434

3535
use(chaiAsPromised);
3636
use(sinonChai);
@@ -49,83 +49,194 @@ describe('core/user/profile', () => {
4949

5050
beforeEach(() => {
5151
user = testUser('uid', '', true);
52+
fetch.setUp();
5253
});
5354

5455
afterEach(() => {
5556
sinon.restore();
57+
fetch.tearDown();
5658
});
5759

58-
beforeEach(fetch.setUp);
59-
afterEach(fetch.tearDown);
60-
61-
it('returns immediately if profile object is empty', async () => {
62-
const ep = mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {});
63-
await updateProfile(user, {});
64-
expect(ep.calls).to.be.empty;
65-
});
60+
describe('#updateProfile', () => {
61+
it('returns immediately if profile object is empty', async () => {
62+
const ep = mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {});
63+
await updateProfile(user, {});
64+
expect(ep.calls).to.be.empty;
65+
});
6666

67-
it('calls the setAccountInfo endpoint', async () => {
68-
const ep =mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {});
67+
it('calls the setAccountInfo endpoint', async () => {
68+
const ep =mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {});
6969

70-
await updateProfile(user, {displayName: 'displayname', photoURL: 'photo'});
71-
expect(ep.calls[0].request).to.eql({
72-
idToken: 'access-token',
73-
displayName: 'displayname',
74-
photoUrl: 'photo',
70+
await updateProfile(user, {displayName: 'displayname', photoURL: 'photo'});
71+
expect(ep.calls[0].request).to.eql({
72+
idToken: 'access-token',
73+
displayName: 'displayname',
74+
photoUrl: 'photo',
75+
});
7576
});
76-
});
7777

78-
it('sets the fields on the user based on the response', async () => {
79-
mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {
80-
displayName: 'response-name',
81-
photoUrl: 'response-photo',
78+
it('sets the fields on the user based on the response', async () => {
79+
mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {
80+
displayName: 'response-name',
81+
photoUrl: 'response-photo',
82+
});
83+
84+
await updateProfile(user, {displayName: 'displayname', photoURL: 'photo'});
85+
expect(user.displayName).to.eq('response-name');
86+
expect(user.photoURL).to.eq('response-photo');
8287
});
8388

84-
await updateProfile(user, {displayName: 'displayname', photoURL: 'photo'});
85-
expect(user.displayName).to.eq('response-name');
86-
expect(user.photoURL).to.eq('response-photo');
89+
it('sets the fields on the password provider', async () => {
90+
mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {
91+
displayName: 'response-name',
92+
photoUrl: 'response-photo',
93+
});
94+
user.providerData = [{...PASSWORD_PROVIDER}];
95+
96+
await updateProfile(user, {displayName: 'displayname', photoURL: 'photo'});
97+
const provider = user.providerData[0];
98+
expect(provider.displayName).to.eq('response-name');
99+
expect(provider.photoURL).to.eq('response-photo');
100+
});
87101
});
88102

89-
it('sets the fields on the passwd provider', async () => {
90-
mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {
91-
displayName: 'response-name',
92-
photoUrl: 'response-photo',
103+
describe('#updateEmail', () => {
104+
it('calls the setAccountInfo endpoint and reloads the user', async () => {
105+
const set = mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {});
106+
mockEndpoint(Endpoint.GET_ACCOUNT_INFO, {users: [
107+
{localId: 'new-uid-to-prove-refresh-got-called'},
108+
]});
109+
110+
await updateEmail(user, '[email protected]');
111+
expect(set.calls[0].request).to.eql({
112+
idToken: 'access-token',
113+
114+
});
115+
116+
expect(user.uid).to.eq('new-uid-to-prove-refresh-got-called');
93117
});
94-
user.providerData = [{...PASSWORD_PROVIDER}];
118+
});
119+
120+
describe('#updatePassword', () => {
121+
it('calls the setAccountInfo endpoint and reloads the user', async () => {
122+
const set = mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {});
123+
mockEndpoint(Endpoint.GET_ACCOUNT_INFO, {users: [
124+
{localId: 'new-uid-to-prove-refresh-got-called'},
125+
]});
126+
127+
await updatePassword(user, 'pass');
128+
expect(set.calls[0].request).to.eql({
129+
idToken: 'access-token',
130+
password: 'pass',
131+
});
95132

96-
await updateProfile(user, {displayName: 'displayname', photoURL: 'photo'});
97-
const provider = user.providerData[0];
98-
expect(provider.displayName).to.eq('response-name');
99-
expect(provider.photoURL).to.eq('response-photo');
133+
expect(user.uid).to.eq('new-uid-to-prove-refresh-got-called');
134+
});
100135
});
101136

102137
describe('notifications', () => {
103-
beforeEach(() => {
104-
user.auth.currentUser = user;
138+
let auth: Auth;
139+
let idTokenChange: sinon.SinonStub;
140+
141+
beforeEach(async () => {
142+
auth = user.auth;
143+
idTokenChange = sinon.stub();
144+
auth.onIdTokenChanged(idTokenChange);
145+
146+
// Flush token change promises which are floating
147+
await auth.updateCurrentUser(user);
148+
auth._isInitialized = true;
149+
idTokenChange.resetHistory();
105150
});
106151

107-
it('triggers a token update if necessary', async () => {
108-
mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {
109-
idToken: 'new-id-token',
110-
refreshToken: 'new-refresh-token',
111-
expiresIn: 300,
152+
describe('#updateProfile', () => {
153+
it('triggers a token update if necessary', async () => {
154+
mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {
155+
idToken: 'new-id-token',
156+
refreshToken: 'new-refresh-token',
157+
expiresIn: 300,
158+
});
159+
160+
await updateProfile(user, {displayName: 'd'});
161+
expect(idTokenChange).to.have.been.called;
162+
expect(testPersistence.lastPersistedBlob).to.eql(user.toPlainObject());
112163
});
113164

114-
const notifySpy = sinon.stub(user.auth, '_notifyStateListeners');
115-
await updateProfile(user, {displayName: 'd'});
116-
expect(notifySpy).to.have.been.called;
165+
it('does NOT trigger a token update if unnecessary', async () => {
166+
mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {
167+
idToken: 'access-token',
168+
refreshToken: 'refresh-token',
169+
expiresIn: 300,
170+
});
171+
172+
await updateProfile(user, {displayName: 'd'});
173+
expect(idTokenChange).not.to.have.been.called;
174+
expect(testPersistence.lastPersistedBlob).to.eql(user.toPlainObject());
175+
});
117176
});
118177

119-
it('does NOT trigger a token update if unnecessary', async () => {
120-
mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {
121-
idToken: 'access-token',
122-
refreshToken: 'refresh-token',
123-
expiresIn: 300,
178+
describe('#updateEmail', () => {
179+
beforeEach(() => {
180+
// This is necessary because this method calls reload; we don't care about that though,
181+
// for these tests we're looking at the change listeners
182+
mockEndpoint(Endpoint.GET_ACCOUNT_INFO, {users: [{}]});
124183
});
125184

126-
const notifySpy = sinon.stub(user.auth, '_notifyStateListeners');
127-
await updateProfile(user, {displayName: 'd'});
128-
expect(notifySpy).not.to.have.been.called;
185+
it('triggers a token update if necessary', async () => {
186+
mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {
187+
idToken: 'new-id-token',
188+
refreshToken: 'new-refresh-token',
189+
expiresIn: 300,
190+
});
191+
192+
await updatePassword(user, '[email protected]');
193+
expect(idTokenChange).to.have.been.called;
194+
expect(testPersistence.lastPersistedBlob).to.eql(user.toPlainObject());
195+
});
196+
197+
it('does NOT trigger a token update if unnecessary', async () => {
198+
mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {
199+
idToken: 'access-token',
200+
refreshToken: 'refresh-token',
201+
expiresIn: 300,
202+
});
203+
204+
await updateEmail(user, '[email protected]');
205+
expect(idTokenChange).not.to.have.been.called;
206+
expect(testPersistence.lastPersistedBlob).to.eql(user.toPlainObject());
207+
});
208+
});
209+
210+
describe('#updatePassword', () => {
211+
beforeEach(() => {
212+
// This is necessary because this method calls reload; we don't care about that though,
213+
// for these tests we're looking at the change listeners
214+
mockEndpoint(Endpoint.GET_ACCOUNT_INFO, {users: [{}]});
215+
});
216+
217+
it('triggers a token update if necessary', async () => {
218+
mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {
219+
idToken: 'new-id-token',
220+
refreshToken: 'new-refresh-token',
221+
expiresIn: 300,
222+
});
223+
224+
await updatePassword(user, 'pass');
225+
expect(idTokenChange).to.have.been.called;
226+
expect(testPersistence.lastPersistedBlob).to.eql(user.toPlainObject());
227+
});
228+
229+
it('does NOT trigger a token update if unnecessary', async () => {
230+
mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {
231+
idToken: 'access-token',
232+
refreshToken: 'refresh-token',
233+
expiresIn: 300,
234+
});
235+
236+
await updatePassword(user, 'pass');
237+
expect(idTokenChange).not.to.have.been.called;
238+
expect(testPersistence.lastPersistedBlob).to.eql(user.toPlainObject());
239+
});
129240
});
130241
});
131242
});

0 commit comments

Comments
 (0)