Skip to content

Commit 7643000

Browse files
committed
test(e2e): refactor helpers w/modular syntax, named imports, async
this is a non-functional change but brings the helper function codebase up to spec also uses node 22 now and has updated firebase-tools
1 parent e8619c8 commit 7643000

File tree

12 files changed

+119
-125
lines changed

12 files changed

+119
-125
lines changed

.github/workflows/scripts/functions/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"logs": "firebase functions:log"
1010
},
1111
"engines": {
12-
"node": "18"
12+
"node": "22"
1313
},
1414
"main": "lib/index.js",
1515
"dependencies": {
@@ -18,7 +18,7 @@
1818
},
1919
"devDependencies": {
2020
"firebase-functions-test": "^3.4.1",
21-
"firebase-tools": "^14.12.1",
21+
"firebase-tools": "^14.14.0",
2222
"typescript": "^5.9.2"
2323
},
2424
"private": true

.github/workflows/scripts/functions/src/fetchAppCheckToken.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,13 @@
77
* See License file for more information.
88
*/
99

10-
import * as admin from 'firebase-admin';
11-
import * as functions from 'firebase-functions/v2';
12-
import { CallableRequest } from 'firebase-functions/v2/https';
10+
import { getAppCheck } from 'firebase-admin/app-check';
11+
import { CallableRequest, onCall } from 'firebase-functions/v2/https';
1312

1413
// Note: this will only work in a live environment, not locally via the Firebase emulator.
15-
export const fetchAppCheckTokenV2 = functions.https.onCall(
16-
async (req: CallableRequest<{ appId: string }>) => {
17-
const { appId } = req.data;
18-
const expireTimeMillis = Math.floor(Date.now() / 1000) + 60 * 60;
19-
const result = await admin.appCheck().createToken(appId);
20-
return { ...result, expireTimeMillis };
21-
},
22-
);
14+
export const fetchAppCheckTokenV2 = onCall(async (req: CallableRequest<{ appId: string }>) => {
15+
const { appId } = req.data;
16+
const expireTimeMillis = Math.floor(Date.now() / 1000) + 60 * 60;
17+
const result = await getAppCheck().createToken(appId);
18+
return { ...result, expireTimeMillis };
19+
});

.github/workflows/scripts/functions/src/index.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
1-
import * as functions from 'firebase-functions/v2';
2-
import { CallableRequest } from 'firebase-functions/v2/https';
1+
import { App, initializeApp } from 'firebase-admin/app';
2+
import { logger } from 'firebase-functions/v2';
3+
import { CallableRequest, onRequest, onCall } from 'firebase-functions/v2/https';
34

4-
// // Start writing Firebase Functions
5-
// // https://firebase.google.com/docs/functions/typescript
6-
//
7-
export const helloWorldV2 = functions.https.onRequest((_, response) => {
8-
functions.logger.info('Hello logs!', { structuredData: true });
5+
let _app: App;
6+
7+
export function getAdminApp(): App {
8+
if (!_app) {
9+
_app = initializeApp();
10+
}
11+
return _app;
12+
}
13+
14+
export const helloWorldV2 = onRequest((_, response) => {
15+
logger.info('Hello logs!', { structuredData: true });
916
response.send('{ "data": "Hello from Firebase!" }');
1017
});
1118

12-
export const sleeperV2 = functions.https.onCall(
13-
async (req: CallableRequest<{ delay?: number }>) => {
14-
functions.logger.info('Sleeper function starting');
15-
return await new Promise(() => {
16-
functions.logger.info('Sleeping this long: ' + (req.data.delay ?? 3000));
17-
setTimeout(() => functions.logger.info('done sleeping'), req.data.delay ?? 3000);
18-
});
19-
},
20-
);
19+
export const sleeperV2 = onCall(async (req: CallableRequest<{ delay?: number }>) => {
20+
logger.info('Sleeper function starting');
21+
return await new Promise(() => {
22+
logger.info('Sleeping this long: ' + (req.data.delay ?? 3000));
23+
setTimeout(() => logger.info('done sleeping'), req.data.delay ?? 3000);
24+
});
25+
});
2126

2227
export { testFunctionCustomRegion } from './testFunctionCustomRegion';
2328
export { testFunctionDefaultRegionV2 } from './testFunctionDefaultRegion';

.github/workflows/scripts/functions/src/sendFCM.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,18 @@
77
* See License file for more information.
88
*/
99

10-
import * as functions from 'firebase-functions/v2';
11-
import { CallableRequest } from 'firebase-functions/v2/https';
12-
10+
import { logger } from 'firebase-functions/v2';
11+
import { CallableRequest, onCall } from 'firebase-functions/v2/https';
1312
import { getMessaging, TokenMessage } from 'firebase-admin/messaging';
1413

1514
// Note: this will only work in a live environment, not locally via the Firebase emulator.
16-
export const sendFCM = functions.https.onCall(
15+
export const sendFCM = onCall(
1716
async (req: CallableRequest<{ message: TokenMessage; delay?: number }>) => {
1817
const { message, delay } = req.data;
1918
return await new Promise(() => {
20-
functions.logger.info('Sleeping this many milliseconds: ' + (delay ?? 0));
19+
logger.info('Sleeping this many milliseconds: ' + (delay ?? 0));
2120
setTimeout(async () => {
22-
functions.logger.info('done sleeping');
21+
logger.info('done sleeping');
2322
const result = await getMessaging().send(message);
2423
return { messageId: result };
2524
}, delay ?? 0);

.github/workflows/scripts/functions/src/testFunctionCustomRegion.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
* See License file for more information.
88
*/
99

10-
import * as functions from 'firebase-functions/v1';
10+
import { onCall } from 'firebase-functions/v2/https';
1111

12-
// stay v1 here - v2 functions don't support custom regions well,
13-
// they require httpsCallableFromUrl which is tested separately
14-
export const testFunctionCustomRegion = functions
15-
.region('europe-west1')
16-
.https.onCall(() => 'europe-west1');
12+
export const testFunctionCustomRegion = onCall(
13+
{
14+
region: 'europe-west1',
15+
},
16+
() => 'europe-west1',
17+
);

.github/workflows/scripts/functions/src/testFunctionDefaultRegion.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@
77
* See License file for more information.
88
*/
99

10-
import * as assert from 'assert';
10+
import { deepEqual } from 'assert';
1111
import { FirebaseError } from 'firebase-admin';
12-
import * as functions from 'firebase-functions/v2';
12+
import { CallableRequest, onCall, HttpsError } from 'firebase-functions/v2/https';
1313
import SAMPLE_DATA from './sample-data';
14-
import { CallableRequest } from 'firebase-functions/v2/https';
1514

16-
export const testFunctionDefaultRegionV2 = functions.https.onCall(
15+
export const testFunctionDefaultRegionV2 = onCall(
1716
(req: CallableRequest<{ type: string; asError: boolean; inputData: any }>) => {
1817
console.log(Date.now(), req.data);
1918

@@ -43,16 +42,16 @@ export const testFunctionDefaultRegionV2 = functions.https.onCall(
4342

4443
const { type, asError, inputData } = req.data;
4544
if (!Object.hasOwnProperty.call(SAMPLE_DATA, type)) {
46-
throw new functions.https.HttpsError('invalid-argument', 'Invalid test requested.');
45+
throw new HttpsError('invalid-argument', 'Invalid test requested.');
4746
}
4847

4948
const outputData = SAMPLE_DATA[type];
5049

5150
try {
52-
assert.deepEqual(outputData, inputData);
51+
deepEqual(outputData, inputData);
5352
} catch (e) {
5453
console.error(e);
55-
throw new functions.https.HttpsError(
54+
throw new HttpsError(
5655
'invalid-argument',
5756
'Input and Output types did not match.',
5857
(e as FirebaseError).message,
@@ -61,7 +60,7 @@ export const testFunctionDefaultRegionV2 = functions.https.onCall(
6160

6261
// all good
6362
if (asError) {
64-
throw new functions.https.HttpsError(
63+
throw new HttpsError(
6564
'cancelled',
6665
'Response data was requested to be sent as part of an Error payload, so here we are!',
6766
outputData,

.github/workflows/scripts/functions/src/testFunctionRemoteConfigUpdate.ts

Lines changed: 53 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66
*
77
* See License file for more information.
88
*/
9-
import * as admin from 'firebase-admin';
10-
import * as functions from 'firebase-functions/v2';
11-
import { CallableRequest } from 'firebase-functions/v2/https';
9+
// import * as admin from 'firebase-admin';
10+
import { getRemoteConfig } from 'firebase-admin/remote-config';
11+
import { logger } from 'firebase-functions/v2';
12+
import { CallableRequest, onCall } from 'firebase-functions/v2/https';
13+
import { getAdminApp } from '.';
1214

13-
admin.initializeApp();
14-
const remoteConfig = admin.remoteConfig();
15-
16-
export const testFunctionRemoteConfigUpdateV2 = functions.https.onCall(
17-
(
15+
export const testFunctionRemoteConfigUpdateV2 = onCall(
16+
async (
1817
req: CallableRequest<{
1918
operations: {
2019
delete: string[] | undefined;
@@ -23,61 +22,55 @@ export const testFunctionRemoteConfigUpdateV2 = functions.https.onCall(
2322
};
2423
}>,
2524
) => {
26-
console.log(Date.now(), req);
25+
let template = await getRemoteConfig(getAdminApp()).getTemplate();
26+
logger.info('received template version: ' + JSON.stringify(template.version));
27+
// logger.info('received template: ' + JSON.stringify(template, null, 2));
2728

28-
return new Promise(function (resolve, reject) {
29-
remoteConfig
30-
.getTemplate()
31-
.then((template: any) => {
32-
console.log('received template version: ' + JSON.stringify(template.version));
33-
// console.log('received template: ' + JSON.stringify(template, null, 2));
29+
if (req.data !== undefined && req.data.operations !== undefined) {
30+
modifyTemplate(req.data, template);
31+
}
3432

35-
if (req.data?.operations['delete'] !== undefined) {
36-
const deletions = req.data?.operations['delete'];
37-
deletions.forEach((deletion: string) => {
38-
console.log('deleting key: ' + deletion);
39-
if (template.parameters?.deletion !== undefined) {
40-
delete template.parameters.deletion;
41-
}
42-
});
43-
}
33+
// validate the template
34+
template = await getRemoteConfig(getAdminApp()).validateTemplate(template);
35+
logger.info('template is valid after updates.');
36+
template = await getRemoteConfig(getAdminApp()).publishTemplate(template);
37+
logger.info('template published, new version: ' + JSON.stringify(template.version));
4438

45-
if (req.data?.operations['add'] !== undefined) {
46-
const adds = req.data?.operations['add'];
47-
adds.forEach((add: { name: string; value: any }) => {
48-
console.log('adding key: ' + JSON.stringify(add));
49-
template.parameters[add.name] = {
50-
description: 'realtime test parameter',
51-
defaultValue: {
52-
value: add.value,
53-
},
54-
};
55-
});
56-
}
39+
return { templateVersion: template.version?.versionNumber };
40+
},
41+
);
5742

58-
if (req.data?.operations['update'] !== undefined) {
59-
const updates = req.data?.operations['update'];
60-
updates.forEach((update: { name: string; value: any }) => {
61-
console.log('updating key: ' + JSON.stringify(update));
62-
if (template.parameters[update.name] !== undefined) {
63-
template.parameters[update.name].defaultValue = update.value;
64-
}
65-
});
66-
}
43+
function modifyTemplate(data: any, template: any) {
44+
if (data.operations['delete'] !== undefined) {
45+
const deletions = data.operations['delete'];
46+
deletions.forEach((deletion: string) => {
47+
logger.info('deleting key: ' + deletion);
48+
if (template.parameters?.deletion !== undefined) {
49+
delete template.parameters.deletion;
50+
}
51+
});
52+
}
6753

68-
// validate the template
69-
remoteConfig.validateTemplate(template).then(template => {
70-
console.log('template is valid after updates.');
71-
remoteConfig.publishTemplate(template).then(template => {
72-
console.log('template published, new version: ' + JSON.stringify(template.version));
73-
resolve({ templateVersion: template.version?.versionNumber });
74-
});
75-
});
76-
})
77-
.catch((err: string) => {
78-
console.error('remoteConfig.getTemplate failure: ' + err);
79-
reject({ status: 'failure', message: err });
80-
});
54+
if (data.operations['add'] !== undefined) {
55+
const adds = data.operations['add'];
56+
adds.forEach((add: { name: string; value: any }) => {
57+
logger.info('adding key: ' + JSON.stringify(add));
58+
template.parameters[add.name] = {
59+
description: 'realtime test parameter',
60+
defaultValue: {
61+
value: add.value,
62+
},
63+
};
8164
});
82-
},
83-
);
65+
}
66+
67+
if (data.operations['update'] !== undefined) {
68+
const updates = data.operations['update'];
69+
updates.forEach((update: { name: string; value: any }) => {
70+
logger.info('updating key: ' + JSON.stringify(update));
71+
if (template.parameters[update.name] !== undefined) {
72+
template.parameters[update.name].defaultValue = update.value;
73+
}
74+
});
75+
}
76+
}

.github/workflows/scripts/functions/src/vertexaiFunctions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as functions from 'firebase-functions/v2';
1+
import { onRequest } from 'firebase-functions/v2/https';
22

33
const poem = [
44
'The wind whispers secrets through the trees,',
@@ -42,7 +42,7 @@ const response = {
4242
},
4343
};
4444

45-
export const testFetchStream = functions.https.onRequest(async (req, res) => {
45+
export const testFetchStream = onRequest(async (_, res) => {
4646
res.setHeader('Content-Type', 'text/event-stream');
4747
res.setHeader('Cache-Control', 'no-cache');
4848
res.setHeader('Connection', 'keep-alive');
@@ -57,6 +57,6 @@ export const testFetchStream = functions.https.onRequest(async (req, res) => {
5757
res.end();
5858
});
5959

60-
export const testFetch = functions.https.onRequest(async (req, res) => {
60+
export const testFetch = onRequest(async (_, res) => {
6161
res.json(response);
6262
});

.github/workflows/scripts/functions/yarn.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2684,9 +2684,9 @@ __metadata:
26842684
languageName: node
26852685
linkType: hard
26862686

2687-
"firebase-tools@npm:^14.12.1":
2688-
version: 14.12.1
2689-
resolution: "firebase-tools@npm:14.12.1"
2687+
"firebase-tools@npm:^14.14.0":
2688+
version: 14.14.0
2689+
resolution: "firebase-tools@npm:14.14.0"
26902690
dependencies:
26912691
"@electric-sql/pglite": "npm:^0.3.3"
26922692
"@electric-sql/pglite-tools": "npm:^0.2.8"
@@ -2764,7 +2764,7 @@ __metadata:
27642764
zod-to-json-schema: "npm:^3.24.5"
27652765
bin:
27662766
firebase: lib/bin/firebase.js
2767-
checksum: 10/a50359d8ea96fbeb974ec5651cead9cc0e4e7296dd750f9b06027ae95cfe2815b9c572c7eb79602b853d0924e81f182b208cc1f34d28cc68760ae76f94ab5c5c
2767+
checksum: 10/08d7120f7e43da62a92d85771bacbac9991f3f78bca60aea60c138bec0e1e29bcf6ad13b76380daaef17b860b7cbfb74296a7394617b0e51dbc2d309436dfdaf
27682768
languageName: node
27692769
linkType: hard
27702770

@@ -2911,7 +2911,7 @@ __metadata:
29112911
firebase-admin: "npm:^13.4.0"
29122912
firebase-functions: "npm:^6.4.0"
29132913
firebase-functions-test: "npm:^3.4.1"
2914-
firebase-tools: "npm:^14.12.1"
2914+
firebase-tools: "npm:^14.14.0"
29152915
typescript: "npm:^5.9.2"
29162916
languageName: unknown
29172917
linkType: soft

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"eslint-plugin-prettier": "^5.5.4",
8787
"eslint-plugin-react": "^7.37.5",
8888
"firebase": "^12.1.0",
89-
"firebase-tools": "^14.12.1",
89+
"firebase-tools": "^14.14.0",
9090
"genversion": "^3.2.0",
9191
"google-java-format": "^2.0.1",
9292
"jest": "^30.0.5",

0 commit comments

Comments
 (0)