Skip to content

Commit a8e8c03

Browse files
authored
Added functions tests (#8)
Added tests for the Functions SDK. Also abstracted the Auth operation `waitForUserSignIn` and put it in `lib/util.ts` so that both Auth and Functions may use it.
1 parent fc2a96c commit a8e8c03

File tree

9 files changed

+338
-24
lines changed

9 files changed

+338
-24
lines changed

app/page.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ export default async function Page() {
6060
<li><Link href="/tests/firestore/web_client">Firestore Web SDK client-side tests</Link></li>
6161
<li><Link href="/tests/firestore/web_ssr">Firestore Web SDK server-side tests</Link></li>
6262
</ul>
63+
<p />
64+
<li>Functions</li>
65+
<ul>
66+
<li><Link href="/tests/functions/web_client">Functions Web SDK client-side tests</Link></li>
67+
<li><Link href="/tests/functions/web_ssr">Functions Web SDK server-side tests</Link></li>
68+
</ul>
6369
</ul>
6470
</>
6571
);

app/tests/auth/lib/test.ts

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
* limitations under the License.
1616
*/
1717
import { deleteApp, initializeApp, initializeServerApp } from 'firebase/app';
18-
import { deleteUser, getAuth, onAuthStateChanged, signInAnonymously } from 'firebase/auth';
18+
import { deleteUser, getAuth, signInAnonymously } from 'firebase/auth';
1919
import { firebaseConfig } from 'lib/firebase';
20-
import { OK, OK_SKIPPED, FAILED } from 'lib/util';
20+
import { OK, OK_SKIPPED, FAILED, waitForUserSignIn } from 'lib/util';
2121

2222
export type TestResults = {
2323
initializeAppResult: string,
@@ -49,27 +49,6 @@ export function initializeTestResults(): TestResults {
4949
};
5050
}
5151

52-
async function waitForUserSignedIn(auth): Promise<void> {
53-
const promise: Promise<void> = new Promise<void>((resolve, reject) => {
54-
let completed: boolean = false;
55-
const unsubscribe = onAuthStateChanged(auth, (user) => {
56-
if (user) {
57-
completed = true;
58-
unsubscribe();
59-
resolve();
60-
}
61-
});
62-
setTimeout(() => {
63-
if (!completed) {
64-
completed = true;
65-
unsubscribe();
66-
reject();
67-
}
68-
}, 3000);
69-
});
70-
return promise;
71-
}
72-
7352
export async function testAuth(isServer: boolean = false): Promise<TestResults> {
7453
const result: TestResults = initializeTestResults();
7554
try {
@@ -79,7 +58,7 @@ export async function testAuth(isServer: boolean = false): Promise<TestResults>
7958
await auth.authStateReady();
8059
result.initializeAuthResult = OK;
8160
await signInAnonymously(auth);
82-
await waitForUserSignedIn(auth);
61+
await waitForUserSignIn(auth);
8362
if (auth.currentUser !== null) {
8463
result.signInAnonymouslyResult = OK;
8564
const idToken = await auth.currentUser.getIdToken();
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @license
3+
* Copyright 2024 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+
'use client'
18+
19+
import { useState, useEffect } from 'react'
20+
import { testFunctions, initializeTestResults } from '../lib/test';
21+
import ResultsDisplay from './results_display';
22+
23+
export default function CsrTestRunner() {
24+
const [testStatus, setTestStatus] = useState<string>("running...");
25+
const [testResults, setTestResults] = useState(initializeTestResults());
26+
useEffect(() => {
27+
const asyncTest = async () => {
28+
setTestResults(await testFunctions());
29+
setTestStatus("Complete!");
30+
}
31+
asyncTest().catch((e) => {
32+
console.error("Error encountered during testing: ", e);
33+
setTestStatus("Errored!");
34+
});
35+
}, []);
36+
37+
return (
38+
<ResultsDisplay statusString={testStatus} testResults={testResults} />
39+
);
40+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* @license
3+
* Copyright 2024 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+
import Link from 'next/link';
18+
export default function ResultsDisplay({ statusString, testResults }) {
19+
return (
20+
<>
21+
<h2 title="testStatus">{statusString}</h2>
22+
<h4 title="initializeAppResult">initializeAppResult: {testResults.initializeAppResult}</h4>
23+
<h4 title="initializeAuthResult">initializeAuthResult: {testResults.initializeAuthResult}</h4>
24+
<h4 title="authUserSignedInResult">authUserSignedInResult: {testResults.authUserSignedInResult}</h4>
25+
<h4 title="initializeFunctionsResult">initializeFunctionsResult: {testResults.initializeFunctionsResult}</h4>
26+
<h4 title="createCallTestResult">createCallTestResult: {testResults.createCallTestResult}</h4>
27+
<h4 title="callTestInvokedSuccessfullyResult">callTestInvokedSuccessfullyResult: {testResults.callTestInvokedSuccessfullyResult}</h4>
28+
<h4 title="callTestResponseResult">callTestResponseResult: {testResults.callTestResponseResult}</h4>
29+
<h4 title="httpCallableTestInvokedSuccessfullyResult">httpCallableTestInvokedSuccessfullyResult: {testResults.httpCallableTestInvokedSuccessfullyResult}</h4>
30+
<h4 title="httpCallableResponseResult">httpCallableResponseResult: {testResults.httpCallableResponseResult}</h4>
31+
<h4 title="deleteUserResult">deleteUserResult: {testResults.deleteUserResult}</h4>
32+
<h4 title="authSignedOutResult">deleteUserResult: {testResults.deleteUserResult}</h4>
33+
<h4 title="deleteAppResult">deleteAppResult: {testResults.deleteAppResult}</h4>
34+
<p />
35+
<Link href="/">Back to test index</Link>
36+
</>
37+
);
38+
}

app/tests/functions/lib/test.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* @license
3+
* Copyright 2024 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 { deleteApp, initializeApp } from 'firebase/app';
19+
import { firebaseConfig } from 'lib/firebase';
20+
import { getFunctions, httpsCallable, httpsCallableFromURL } from 'firebase/functions';
21+
import { getAuth, deleteUser, signInAnonymously } from 'firebase/auth';
22+
import { OK, FAILED, waitForUserSignIn } from 'lib/util';
23+
24+
export type TestResults = {
25+
initializeAppResult: string,
26+
initializeAuthResult: string,
27+
authUserSignedInResult: string,
28+
initializeFunctionsResult: string,
29+
createCallTestResult: string,
30+
callTestInvokedSuccessfullyResult: string,
31+
callTestResponseResult: string,
32+
httpCallableTestInvokedSuccessfullyResult: string,
33+
httpCallableResponseResult: string,
34+
deleteUserResult: string,
35+
authSignedOutResult: string,
36+
deleteAppResult: string
37+
};
38+
39+
export function initializeTestResults(): TestResults {
40+
return {
41+
initializeAppResult: FAILED,
42+
initializeAuthResult: FAILED,
43+
authUserSignedInResult: FAILED,
44+
initializeFunctionsResult: FAILED,
45+
createCallTestResult: FAILED,
46+
callTestInvokedSuccessfullyResult: FAILED,
47+
callTestResponseResult: FAILED,
48+
httpCallableTestInvokedSuccessfullyResult: FAILED,
49+
httpCallableResponseResult: FAILED,
50+
deleteUserResult: FAILED,
51+
authSignedOutResult: FAILED,
52+
deleteAppResult: FAILED
53+
};
54+
}
55+
56+
export async function testFunctions(): Promise<TestResults> {
57+
const result: TestResults = initializeTestResults();
58+
try {
59+
const firebaseApp = initializeApp(firebaseConfig);
60+
if (firebaseApp === null) {
61+
return result;
62+
}
63+
result.initializeAppResult = OK;
64+
65+
const auth = getAuth(firebaseApp);
66+
await auth.authStateReady();
67+
result.initializeAuthResult = OK;
68+
69+
await signInAnonymously(auth);
70+
await waitForUserSignIn(auth);
71+
if (auth.currentUser) {
72+
result.authUserSignedInResult = OK;
73+
74+
const functions = getFunctions(firebaseApp);
75+
if (functions === null) {
76+
return result;
77+
}
78+
result.initializeFunctionsResult = OK;
79+
80+
const callTest = httpsCallable<{ data: string }, { word: string }>(
81+
functions,
82+
'callTest'
83+
);
84+
if (!callTest) {
85+
return result;
86+
}
87+
result.createCallTestResult = OK;
88+
const callTestResult = await callTest({ data: 'blah' });
89+
result.callTestInvokedSuccessfullyResult = OK;
90+
if (callTestResult.data.word === 'hellooo') {
91+
result.callTestResponseResult = OK;
92+
}
93+
94+
const httpCallableTest = httpsCallableFromURL<{ data: string }, { word: string }>(
95+
functions,
96+
`https://us-central1-${firebaseConfig.projectId}.cloudfunctions.net/callTest`
97+
);
98+
const httpsCallableResult = await httpCallableTest({ data: 'blah' });
99+
result.httpCallableTestInvokedSuccessfullyResult = OK;
100+
if (httpsCallableResult.data.word === 'hellooo') {
101+
result.httpCallableResponseResult = OK;
102+
}
103+
104+
await deleteUser(auth.currentUser);
105+
result.deleteUserResult = OK;
106+
107+
await auth.signOut();
108+
result.authSignedOutResult = OK;
109+
}
110+
111+
deleteApp(firebaseApp);
112+
result.deleteAppResult = OK;
113+
} catch (e) {
114+
console.error("Caught error: ", e);
115+
}
116+
return result;
117+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* @license
3+
* Copyright 2024 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+
import type { Metadata } from 'next'
18+
import CSRTestRunner from '../components/csr_test_runner';
19+
20+
export const metadata: Metadata = {
21+
title: 'Functions Web SDK CSR test'
22+
}
23+
24+
export default function Page() {
25+
return (
26+
<>
27+
<h1>Functions CSR Test results:</h1>
28+
<CSRTestRunner />
29+
</>
30+
);
31+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @license
3+
* Copyright 2024 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+
import type { Metadata } from 'next'
18+
import { testFunctions, TestResults } from '../lib/test';
19+
import ResultsDisplay from '../components/results_display';
20+
21+
export const metadata: Metadata = {
22+
title: 'Functions Web SDK SSR test'
23+
}
24+
25+
export default async function Page() {
26+
const testResults: TestResults = await testFunctions();
27+
return (
28+
<>
29+
<h1>Functions SSR Test results:</h1>
30+
<ResultsDisplay statusString='Tests Complete!' testResults={testResults} />
31+
</>
32+
);
33+
}

lib/util.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17+
import { Auth, onAuthStateChanged } from 'firebase/auth';
1718

1819
export const OK: string = "OK";
1920
export const OK_SKIPPED: string = "OK - SKIPPED";
@@ -22,3 +23,24 @@ export const FAILED: string = "FAILED";
2223
export async function sleep(ms) {
2324
return new Promise(resolve => setTimeout(resolve, ms));
2425
}
26+
27+
export async function waitForUserSignIn(auth : Auth): Promise<void> {
28+
const promise: Promise<void> = new Promise<void>((resolve, reject) => {
29+
let completed: boolean = false;
30+
const unsubscribe = onAuthStateChanged(auth, (user) => {
31+
if (user) {
32+
completed = true;
33+
unsubscribe();
34+
resolve();
35+
}
36+
});
37+
setTimeout(() => {
38+
if (!completed) {
39+
completed = true;
40+
unsubscribe();
41+
reject();
42+
}
43+
}, 3000);
44+
});
45+
return promise;
46+
}

0 commit comments

Comments
 (0)