Skip to content

Commit 22300f8

Browse files
authored
Merge 1085d07 into ee27660
2 parents ee27660 + 1085d07 commit 22300f8

File tree

5 files changed

+174
-13
lines changed

5 files changed

+174
-13
lines changed

packages-exp/auth-exp/test/helpers/integration/emulator_rest_helpers.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,22 @@ export async function resetEmulator(): Promise<void> {
7777
await FetchProvider.fetch()(url, { method: 'DELETE' });
7878
}
7979

80+
export async function createAnonAccount(): Promise<{
81+
localId: string;
82+
idToken: string;
83+
refreshToken: string;
84+
}> {
85+
const url = `${getEmulatorUrl()}/identitytoolkit.googleapis.com/v1/accounts:signUp?key=fake-key`;
86+
const response = await (
87+
await FetchProvider.fetch()(url, {
88+
method: 'POST',
89+
body: '{}',
90+
headers: { 'Content-Type': 'application/json' }
91+
})
92+
).json();
93+
return response;
94+
}
95+
8096
function buildEmulatorUrlForPath(endpoint: string): string {
8197
const emulatorBaseUrl = getEmulatorUrl();
8298
const projectId = getAppConfig().projectId;

packages-exp/auth-exp/test/integration/webdriver/persistence.test.ts

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,36 @@
1818
// eslint-disable-next-line import/no-extraneous-dependencies
1919
import { UserCredential } from '@firebase/auth-exp';
2020
import { expect } from 'chai';
21+
import { User } from '../../../internal';
22+
import { createAnonAccount } from '../../helpers/integration/emulator_rest_helpers';
2123
import { API_KEY } from '../../helpers/integration/settings';
22-
import { AnonFunction, PersistenceFunction } from './util/functions';
24+
import {
25+
AnonFunction,
26+
CoreFunction,
27+
PersistenceFunction
28+
} from './util/functions';
2329
import { browserDescribe } from './util/test_runner';
2430

31+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
32+
async function testPersistedUser() {
33+
const account = await createAnonAccount();
34+
return {
35+
uid: account.localId,
36+
emailVerified: false,
37+
isAnonymous: true,
38+
providerData: [],
39+
stsTokenManager: {
40+
refreshToken: account.refreshToken,
41+
accessToken: account.idToken,
42+
expirationTime: Date.now() + 3600 * 1000
43+
},
44+
createdAt: Date.now().toString(),
45+
lastLoginAt: Date.now().toString()
46+
};
47+
}
48+
2549
browserDescribe('WebDriver persistence test', driver => {
50+
const fullPersistenceKey = `firebase:authUser:${API_KEY}:[DEFAULT]`;
2651
context('default persistence hierarchy (indexedDB > localStorage)', () => {
2752
it('stores user in indexedDB by default', async () => {
2853
const cred: UserCredential = await driver.call(
@@ -39,9 +64,7 @@ browserDescribe('WebDriver persistence test', driver => {
3964
).to.eql({});
4065

4166
const snap = await driver.call(PersistenceFunction.INDEXED_DB_SNAP);
42-
expect(snap)
43-
.to.have.property(`firebase:authUser:${API_KEY}:[DEFAULT]`)
44-
.that.contains({ uid });
67+
expect(snap).to.have.property(fullPersistenceKey).that.contains({ uid });
4568

4669
// Persistence should survive a refresh:
4770
await driver.webDriver.navigate().refresh();
@@ -71,9 +94,7 @@ browserDescribe('WebDriver persistence test', driver => {
7194
).to.eql({});
7295

7396
const snap = await driver.call(PersistenceFunction.INDEXED_DB_SNAP);
74-
expect(snap)
75-
.to.have.property(`firebase:authUser:${API_KEY}:[DEFAULT]`)
76-
.that.contains({ uid });
97+
expect(snap).to.have.property(fullPersistenceKey).that.contains({ uid });
7798

7899
// Persistence should survive a refresh:
79100
await driver.webDriver.navigate().refresh();
@@ -100,9 +121,7 @@ browserDescribe('WebDriver persistence test', driver => {
100121
).to.eql({});
101122

102123
const snap = await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP);
103-
expect(snap)
104-
.to.have.property(`firebase:authUser:${API_KEY}:[DEFAULT]`)
105-
.that.contains({ uid });
124+
expect(snap).to.have.property(fullPersistenceKey).that.contains({ uid });
106125

107126
// Persistence should survive a refresh:
108127
await driver.webDriver.navigate().refresh();
@@ -139,9 +158,84 @@ browserDescribe('WebDriver persistence test', driver => {
139158
await driver.waitForAuthInit();
140159
expect(await driver.getUserSnapshot()).to.equal(null);
141160
});
142-
});
143161

144-
// TODO: Upgrade tests (e.g. migrate user from localStorage to indexedDB).
162+
it('migrate stored user from localStorage if indexedDB is available', async () => {
163+
const persistedUser = await testPersistedUser();
164+
await driver.webDriver.navigate().refresh();
165+
await driver.call(PersistenceFunction.LOCAL_STORAGE_SET, {
166+
[fullPersistenceKey]: persistedUser
167+
});
168+
await driver.injectConfigAndInitAuth();
169+
await driver.waitForAuthInit();
170+
171+
// User from localStorage should be picked up.
172+
const user = await driver.call<User>(CoreFunction.USER_SNAPSHOT);
173+
expect(user.uid).eql(persistedUser.uid);
174+
175+
// User should be migrated to indexedDB, and the key in localStorage should be deleted.
176+
const snap = await driver.call(PersistenceFunction.INDEXED_DB_SNAP);
177+
expect(snap)
178+
.to.have.property(fullPersistenceKey)
179+
.that.contains({ uid: persistedUser.uid });
180+
expect(await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP)).to.eql(
181+
{}
182+
);
183+
});
184+
185+
it('migrate stored user to localStorage if indexedDB is readonly', async () => {
186+
// Sign in first, which gets persisted in indexedDB.
187+
const cred: UserCredential = await driver.call(
188+
AnonFunction.SIGN_IN_ANONYMOUSLY
189+
);
190+
const uid = cred.user.uid;
191+
192+
await driver.webDriver.navigate().refresh();
193+
await driver.call(PersistenceFunction.MAKE_INDEXED_DB_READONLY);
194+
await driver.injectConfigAndInitAuth();
195+
await driver.waitForAuthInit();
196+
197+
// User from indexedDB should be picked up.
198+
const user = await driver.call<User>(CoreFunction.USER_SNAPSHOT);
199+
expect(user.uid).eql(uid);
200+
201+
// User should be migrated to localStorage, and the key in indexedDB should be deleted.
202+
const snap = await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP);
203+
expect(snap).to.have.property(fullPersistenceKey).that.contains({ uid });
204+
expect(await driver.call(PersistenceFunction.INDEXED_DB_SNAP)).to.eql({});
205+
});
206+
207+
it('use in-memory and clear all persistences if indexedDB and localStorage are both broken', async () => {
208+
const persistedUser = await testPersistedUser();
209+
await driver.webDriver.navigate().refresh();
210+
await driver.call(PersistenceFunction.LOCAL_STORAGE_SET, {
211+
[fullPersistenceKey]: persistedUser
212+
});
213+
// Simulate browsers that do not support indexedDB.
214+
await driver.webDriver.executeScript('delete window.indexedDB;');
215+
// Simulate browsers denying writes to localStorage (e.g. Safari private browsing).
216+
await driver.webDriver.executeScript(
217+
'Storage.prototype.setItem = () => { throw new Error("setItem disabled for testing"); };'
218+
);
219+
await driver.injectConfigAndInitAuth();
220+
await driver.waitForAuthInit();
221+
222+
// User from localStorage should be picked up.
223+
const user = await driver.call<User>(CoreFunction.USER_SNAPSHOT);
224+
expect(user.uid).eql(persistedUser.uid);
225+
226+
// Both storage should be cleared.
227+
expect(await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP)).to.eql(
228+
{}
229+
);
230+
expect(await driver.call(PersistenceFunction.INDEXED_DB_SNAP)).to.eql({});
231+
232+
// User will be gone (a.k.a. logged out) after refresh.
233+
await driver.webDriver.navigate().refresh();
234+
await driver.injectConfigAndInitAuth();
235+
await driver.waitForAuthInit();
236+
expect(await driver.getUserSnapshot()).to.equal(null);
237+
});
238+
});
145239

146240
// TODO: Compatibility tests (e.g. sign in with JS SDK and should stay logged in with TS SDK).
147241
});

packages-exp/auth-exp/test/integration/webdriver/static/persistence.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,15 @@ export async function clearPersistence() {
3333
export async function localStorageSnap() {
3434
return dumpStorage(localStorage);
3535
}
36+
export async function localStorageSet(dict) {
37+
setInStorage(localStorage, dict);
38+
}
3639
export async function sessionStorageSnap() {
3740
return dumpStorage(sessionStorage);
3841
}
42+
export async function sessionStorageSet(dict) {
43+
setInStorage(sessionStorage, dict);
44+
}
3945

4046
const DB_OBJECTSTORE_NAME = 'firebaseLocalStorage';
4147

@@ -58,6 +64,21 @@ export async function indexedDBSnap() {
5864
return result;
5965
}
6066

67+
// Mock functions for testing edge cases
68+
export async function makeIndexedDBReadonly() {
69+
IDBObjectStore.prototype.add = IDBObjectStore.prototype.put = () => {
70+
return {
71+
error: 'add/put is disabled for test purposes',
72+
readyState: 'done',
73+
addEventListener(event, listener) {
74+
if (event === 'error') {
75+
void Promise.resolve({}).then(listener);
76+
}
77+
}
78+
};
79+
};
80+
}
81+
6182
function dumpStorage(storage) {
6283
const result = {};
6384
for (let i = 0; i < storage.length; i++) {
@@ -67,6 +88,16 @@ function dumpStorage(storage) {
6788
return result;
6889
}
6990

91+
function setInStorage(storage, dict) {
92+
for (const [key, value] of Object.entries(dict)) {
93+
if (value === undefined) {
94+
storage.removeItem(key);
95+
} else {
96+
storage.setItem(key, JSON.stringify(value));
97+
}
98+
}
99+
}
100+
70101
function dbPromise(dbRequest) {
71102
return new Promise((resolve, reject) => {
72103
dbRequest.addEventListener('success', () => {

packages-exp/auth-exp/test/integration/webdriver/util/auth_driver.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,23 @@ export class AuthDriver {
5252

5353
async stop(): Promise<void> {
5454
authTestServer.stop();
55+
if (process.env.WEBDRIVER_BROWSER_LOGS) {
56+
await this.webDriver
57+
.manage()
58+
.logs()
59+
.get('browser')
60+
.then(
61+
logs => {
62+
for (const { level, message } of logs) {
63+
console.log(level.name, message);
64+
}
65+
},
66+
() =>
67+
console.log(
68+
'Failed to dump browser logs (this is normal for Firefox).'
69+
)
70+
);
71+
}
5572
await this.webDriver.quit();
5673
}
5774

packages-exp/auth-exp/test/integration/webdriver/util/functions.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ export enum CoreFunction {
6464
export enum PersistenceFunction {
6565
CLEAR_PERSISTENCE = 'persistence.clearPersistence',
6666
LOCAL_STORAGE_SNAP = 'persistence.localStorageSnap',
67+
LOCAL_STORAGE_SET = 'persistence.localStorageSet',
6768
SESSION_STORAGE_SNAP = 'persistence.sessionStorageSnap',
68-
INDEXED_DB_SNAP = 'persistence.indexedDBSnap'
69+
SESSION_STORAGE_SET = 'persistence.sessionStorageSet',
70+
INDEXED_DB_SNAP = 'persistence.indexedDBSnap',
71+
MAKE_INDEXED_DB_READONLY = 'persistence.makeIndexedDBReadonly'
6972
}

0 commit comments

Comments
 (0)