Skip to content

Commit 87c6245

Browse files
committed
Add integration testing framework to auth compat, plus the auth test ported over
1 parent a6d29aa commit 87c6245

File tree

7 files changed

+314
-23
lines changed

7 files changed

+314
-23
lines changed

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ import {
2424
InstantiationMode
2525
} from '@firebase/component';
2626

27-
import { FirebaseAuth } from '@firebase/auth-types';
27+
import { EmailAuthProvider, EmailAuthProvider_Instance, FacebookAuthProvider, FacebookAuthProvider_Instance, FirebaseAuth, GithubAuthProvider, GithubAuthProvider_Instance, GoogleAuthProvider, GoogleAuthProvider_Instance, OAuthProvider, PhoneAuthProvider, PhoneAuthProvider_Instance, PhoneMultiFactorGenerator, RecaptchaVerifier, RecaptchaVerifier_Instance, SAMLAuthProvider, TwitterAuthProvider, TwitterAuthProvider_Instance } from '@firebase/auth-types';
2828
import { version } from './package.json';
2929
import { Auth } from './src/auth';
3030
import { Persistence } from './src/persistence';
31-
import { PhoneAuthProvider } from './src/phone_auth_provider';
31+
import { PhoneAuthProvider as CompatAuthProvider } from './src/phone_auth_provider';
3232
import { _getClientPlatform } from './src/platform';
33-
import { RecaptchaVerifier } from './src/recaptcha_verifier';
33+
import { RecaptchaVerifier as CompatRecaptchaVerifier } from './src/recaptcha_verifier';
3434

3535
const AUTH_TYPE = 'auth';
3636

@@ -44,6 +44,24 @@ declare module '@firebase/app-compat' {
4444
interface FirebaseNamespace {
4545
auth: {
4646
(app?: FirebaseApp): FirebaseAuth;
47+
Auth: typeof FirebaseAuth;
48+
EmailAuthProvider: typeof EmailAuthProvider;
49+
EmailAuthProvider_Instance: typeof EmailAuthProvider_Instance;
50+
FacebookAuthProvider: typeof FacebookAuthProvider;
51+
FacebookAuthProvider_Instance: typeof FacebookAuthProvider_Instance;
52+
GithubAuthProvider: typeof GithubAuthProvider;
53+
GithubAuthProvider_Instance: typeof GithubAuthProvider_Instance;
54+
GoogleAuthProvider: typeof GoogleAuthProvider;
55+
GoogleAuthProvider_Instance: typeof GoogleAuthProvider_Instance;
56+
OAuthProvider: typeof OAuthProvider;
57+
SAMLAuthProvider: typeof SAMLAuthProvider;
58+
PhoneAuthProvider: typeof PhoneAuthProvider;
59+
PhoneAuthProvider_Instance: typeof PhoneAuthProvider_Instance;
60+
PhoneMultiFactorGenerator: typeof PhoneMultiFactorGenerator;
61+
RecaptchaVerifier: typeof RecaptchaVerifier;
62+
RecaptchaVerifier_Instance: typeof RecaptchaVerifier_Instance;
63+
TwitterAuthProvider: typeof TwitterAuthProvider;
64+
TwitterAuthProvider_Instance: typeof TwitterAuthProvider_Instance;
4765
};
4866
}
4967
interface FirebaseApp {
@@ -84,9 +102,9 @@ function registerAuthCompat(instance: _FirebaseNamespace): void {
84102
GoogleAuthProvider: impl.GoogleAuthProvider,
85103
OAuthProvider: impl.OAuthProvider,
86104
// SAMLAuthProvider,
87-
PhoneAuthProvider,
105+
PhoneAuthProvider: CompatAuthProvider,
88106
PhoneMultiFactorGenerator: impl.PhoneMultiFactorGenerator,
89-
RecaptchaVerifier,
107+
RecaptchaVerifier: CompatRecaptchaVerifier,
90108
TwitterAuthProvider: impl.TwitterAuthProvider,
91109
Auth: {
92110
Persistence

packages-exp/auth-compat-exp/karma.conf.js

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,55 @@
1616
*/
1717

1818
const karmaBase = require('../../config/karma.base');
19+
const { argv } = require('yargs');
1920

2021
const files = ['src/**/*.test.ts'];
2122

2223
module.exports = function (config) {
2324
const karmaConfig = Object.assign({}, karmaBase, {
2425
// files to load into karma
25-
files: files,
26+
files: getTestFiles(),
2627
preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] },
2728
// frameworks to use
2829
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
29-
frameworks: ['mocha']
30+
frameworks: ['mocha'],
31+
32+
client: Object.assign({}, karmaBase.client, getClientConfig())
3033
});
3134

3235
config.set(karmaConfig);
3336
};
3437

35-
module.exports.files = files;
38+
function getTestFiles() {
39+
if (argv.integration) {
40+
return ['test/**/*.test.ts'];
41+
} else {
42+
return ['src/**/*.test.ts'];
43+
}
44+
}
45+
46+
function getClientConfig() {
47+
if (!argv.integration) {
48+
return {};
49+
}
50+
51+
if (!process.env.GCLOUD_PROJECT || !process.env.FIREBASE_AUTH_EMULATOR_HOST) {
52+
console.error(
53+
'Local testing against emulator requested, but ' +
54+
'GCLOUD_PROJECT and FIREBASE_AUTH_EMULATOR_HOST env variables ' +
55+
'are missing'
56+
);
57+
process.exit(1);
58+
}
59+
60+
return {
61+
authAppConfig: {
62+
apiKey: 'local-api-key',
63+
projectId: process.env.GCLOUD_PROJECT,
64+
authDomain: 'local-auth-domain'
65+
},
66+
authEmulatorHost: process.env.FIREBASE_AUTH_EMULATOR_HOST
67+
};
68+
}
69+
70+
module.exports.files = getTestFiles();

packages-exp/auth-compat-exp/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
"test:all": "run-p test:browser test:node",
2323
"test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all",
2424
"test:browser": "karma start --single-run",
25-
"test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha src/**/*.test.* --file ../auth-exp/src/platform_browser/iframe/gapi.iframes.ts --config ../../config/mocharc.node.js"
25+
"test:browser:integration": "karma start --single-run --integration",
26+
"test:node": "ts-node -O '{\"module\": \"commonjs\", \"target\": \"es6\"}' scripts/run_node_tests.ts",
27+
"test:node:integration": "ts-node -O '{\"module\": \"commonjs\", \"target\": \"es6\"}' scripts/run_node_tests.ts",
28+
"test:integration": "run-s test:browser:integration test:node:integration"
2629
},
2730
"peerDependencies": {
2831
"@firebase/app-compat": "0.x"
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* @license
3+
* Copyright 2020 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 { resolve } from 'path';
19+
20+
import { spawn } from 'child-process-promise';
21+
import * as yargs from 'yargs';
22+
23+
const argv = yargs.options({
24+
integration: {
25+
type: 'boolean'
26+
},
27+
webdriver: {
28+
type: 'boolean'
29+
}
30+
}).argv;
31+
32+
const nyc = resolve(__dirname, '../../../node_modules/.bin/nyc');
33+
const mocha = resolve(__dirname, '../../../node_modules/.bin/mocha');
34+
35+
process.env.TS_NODE_COMPILER_OPTIONS = '{"module":"commonjs", "target": "es6"}';
36+
37+
let testConfig = [
38+
'src/**/*.test.ts',
39+
];
40+
41+
if (argv.integration) {
42+
testConfig = ['test/integration/flows/**.test.ts'];
43+
} else if (argv.webdriver) {
44+
testConfig = ['test/integration/webdriver/**.test.ts', '--delay'];
45+
}
46+
47+
let args = [
48+
'--reporter',
49+
'lcovonly',
50+
mocha,
51+
...testConfig,
52+
'--file',
53+
'../auth-exp/src/platform_browser/iframe/gapi.iframes.ts',
54+
'--config',
55+
'../../config/mocharc.node.js'
56+
];
57+
58+
// Make sure that the environment variables are present for local test
59+
if (argv.integration || argv.webdriver) {
60+
if (!process.env.GCLOUD_PROJECT || !process.env.FIREBASE_AUTH_EMULATOR_HOST) {
61+
console.error(
62+
'Local testing against emulator requested, but ' +
63+
'GCLOUD_PROJECT and FIREBASE_AUTH_EMULATOR_HOST env variables ' +
64+
'are missing'
65+
);
66+
process.exit(1);
67+
}
68+
}
69+
70+
args = args.concat(argv._ as string[]);
71+
72+
const spawned = spawn(nyc, args, {
73+
stdio: 'inherit',
74+
cwd: process.cwd()
75+
});
76+
77+
const childProcess = spawned.childProcess;
78+
spawned.catch(() => {
79+
childProcess.kill();
80+
process.exit(1);
81+
});
82+
83+
process.once('exit', () => childProcess.kill());
84+
process.once('SIGINT', () => childProcess.kill('SIGINT'));
85+
process.once('SIGTERM', () => childProcess.kill('SIGTERM'));
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as sinon from 'sinon';
2+
import firebase from '@firebase/app-compat';
3+
import * as exp from '@firebase/auth-exp/internal';
4+
import { getAppConfig, getEmulatorUrl } from '../../../auth-exp/test/helpers/integration/settings';
5+
import { resetEmulator } from '../../../auth-exp/test/helpers/integration/emulator_rest_helpers';
6+
7+
export function initializeTestInstance(): void {
8+
firebase.initializeApp(getAppConfig());
9+
const stub = stubConsoleToSilenceEmulatorWarnings();
10+
firebase.auth().useEmulator(getEmulatorUrl()!);
11+
stub.restore();
12+
}
13+
14+
export async function cleanUpTestInstance(): Promise<void> {
15+
for (const app of firebase.apps) {
16+
await app.delete();
17+
}
18+
await resetEmulator();
19+
}
20+
21+
export function randomEmail(): string {
22+
return `${exp._generateEventId('test.email.')}@integration.test`;
23+
}
24+
25+
function stubConsoleToSilenceEmulatorWarnings(): sinon.SinonStub {
26+
const originalConsoleInfo = console.info.bind(console);
27+
return sinon.stub(console, 'info').callsFake((...args: unknown[]) => {
28+
if (
29+
!JSON.stringify(args[0]).includes(
30+
'WARNING: You are using the Auth Emulator'
31+
)
32+
) {
33+
originalConsoleInfo(...args);
34+
}
35+
});
36+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* @license
3+
* Copyright 2020 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+
21+
22+
import firebase from '@firebase/app-compat';
23+
import '@firebase/auth-compat';
24+
import { FirebaseError } from '@firebase/util';
25+
import { cleanUpTestInstance, initializeTestInstance, randomEmail } from '../../helpers/helpers';
26+
27+
use(chaiAsPromised);
28+
29+
describe('Integration test: anonymous auth', () => {
30+
beforeEach(() => {
31+
initializeTestInstance();
32+
});
33+
34+
afterEach(async () => {
35+
await cleanUpTestInstance();
36+
});
37+
38+
it('signs in anonymously', async () => {
39+
const userCred = await firebase.auth().signInAnonymously();
40+
expect(firebase.auth().currentUser).to.eq(userCred.user);
41+
expect(userCred.operationType).to.eq('signIn');
42+
43+
const user = userCred.user!;
44+
expect(user.isAnonymous).to.be.true;
45+
expect(user.uid).to.be.a('string');
46+
});
47+
48+
it('second sign in on the same device yields same user', async () => {
49+
const { user: userA } = await firebase.auth().signInAnonymously();
50+
const { user: userB } = await firebase.auth().signInAnonymously();
51+
52+
expect(userA!.uid).to.eq(userB!.uid);
53+
});
54+
55+
context('email/password interaction', () => {
56+
let email: string;
57+
58+
beforeEach(() => {
59+
email = randomEmail();
60+
});
61+
62+
it('anonymous / email-password accounts remain independent', async () => {
63+
let anonCred = await firebase.auth().signInAnonymously();
64+
const emailCred = await firebase.auth().createUserWithEmailAndPassword(
65+
email,
66+
'password'
67+
);
68+
expect(emailCred.user!.uid).not.to.eql(anonCred.user!.uid);
69+
70+
await firebase.auth().signOut();
71+
anonCred = await firebase.auth().signInAnonymously();
72+
const emailSignIn = await firebase.auth().signInWithEmailAndPassword(
73+
email,
74+
'password'
75+
);
76+
expect(emailCred.user!.uid).to.eql(emailSignIn.user!.uid);
77+
expect(emailSignIn.user!.uid).not.to.eql(anonCred.user!.uid);
78+
});
79+
80+
it('account can be upgraded by setting email and password', async () => {
81+
const { user: anonUser } = await firebase.auth().signInAnonymously();
82+
await anonUser!.updateEmail(email);
83+
await anonUser!.updatePassword('password');
84+
85+
await firebase.auth().signOut();
86+
87+
const { user: emailPassUser } = await firebase.auth().signInWithEmailAndPassword(
88+
email,
89+
'password'
90+
);
91+
expect(emailPassUser!.uid).to.eq(anonUser!.uid);
92+
});
93+
94+
it('account can be linked using email and password', async () => {
95+
const { user: anonUser } = await firebase.auth().signInAnonymously();
96+
const cred = firebase.auth.EmailAuthProvider.credential(email, 'password');
97+
await anonUser!.linkWithCredential(cred);
98+
await firebase.auth().signOut();
99+
100+
const { user: emailPassUser } = await firebase.auth().signInWithEmailAndPassword(
101+
email,
102+
'password'
103+
);
104+
expect(emailPassUser!.uid).to.eq(anonUser!.uid);
105+
});
106+
107+
it('account cannot be linked with existing email/password', async () => {
108+
await firebase.auth().createUserWithEmailAndPassword(email, 'password');
109+
const { user: anonUser } = await firebase.auth().signInAnonymously();
110+
const cred = firebase.auth.EmailAuthProvider.credential(email, 'password');
111+
await expect(anonUser!.linkWithCredential(cred)).to.be.rejectedWith(
112+
FirebaseError,
113+
'auth/email-already-in-use'
114+
);
115+
});
116+
});
117+
});

0 commit comments

Comments
 (0)