Skip to content

Commit b8f1614

Browse files
authored
Setup v2 triggers for integration tests (#1075)
Also remove region setting option of the test since it's not possible to dynamically set region using function config when parsing triggers via container contract. Just adding a single v2 trigger def for now - with the structure introduced here, it should be easy to add more triggers in the near future.
1 parent 5f8770f commit b8f1614

14 files changed

+122
-72
lines changed

integration_test/functions/src/index.ts

+76-19
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,24 @@ import * as admin from 'firebase-admin';
55
import * as functions from 'firebase-functions';
66
import * as fs from 'fs';
77

8-
import * as v1 from './v1/index';
9-
const numTests = Object.keys(v1).filter((k) =>
10-
({}.hasOwnProperty.call(v1[k], '__endpoint'))
11-
).length;
12-
export { v1 };
8+
import * as v1 from './v1';
9+
import * as v2 from './v2';
10+
const getNumTests = (m: object): number => {
11+
return Object.keys(m).filter((k) =>
12+
({}.hasOwnProperty.call(m[k], '__endpoint'))
13+
).length;
14+
};
15+
const numTests = getNumTests(v1) + getNumTests(v2);
16+
export { v1, v2 };
1317

1418
import * as testLab from './v1/testLab-utils';
19+
import { REGION } from './region';
1520

1621
const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG);
17-
const REGION = functions.config().functions.test_region;
1822
admin.initializeApp();
1923

20-
function callHttpsTrigger(name: string, data: any) {
21-
return fetch(
24+
async function callHttpsTrigger(name: string, data: any) {
25+
const resp = await fetch(
2226
`https://${REGION}-${firebaseConfig.projectId}.cloudfunctions.net/${name}`,
2327
{
2428
method: 'POST',
@@ -28,19 +32,56 @@ function callHttpsTrigger(name: string, data: any) {
2832
body: JSON.stringify({ data }),
2933
}
3034
);
35+
if (!resp.ok) {
36+
throw Error(resp.statusText);
37+
}
38+
}
39+
40+
async function callV2HttpsTrigger(
41+
name: string,
42+
data: any,
43+
accessToken: string
44+
) {
45+
let resp = await fetch(
46+
`https://cloudfunctions.googleapis.com/v2beta/projects/${firebaseConfig.projectId}/locations/${REGION}/functions/${name}`,
47+
{
48+
headers: {
49+
Authorization: `Bearer ${accessToken}`,
50+
},
51+
}
52+
);
53+
if (!resp.ok) {
54+
throw new Error(resp.statusText);
55+
}
56+
const fn = await resp.json();
57+
const uri = fn.serviceConfig?.uri;
58+
if (!uri) {
59+
throw new Error(`Cannot call v2 https trigger ${name} - no uri found`);
60+
}
61+
resp = await fetch(uri, {
62+
method: 'POST',
63+
headers: {
64+
'Content-Type': 'application/json',
65+
},
66+
body: JSON.stringify({ data }),
67+
});
68+
if (!resp.ok) {
69+
throw new Error(resp.statusText);
70+
}
3171
}
3272

33-
async function callScheduleTrigger(functionName: string, region: string) {
34-
const accessToken = await admin.credential
35-
.applicationDefault()
36-
.getAccessToken();
73+
async function callScheduleTrigger(
74+
functionName: string,
75+
region: string,
76+
accessToken: string
77+
) {
3778
const response = await fetch(
3879
`https://cloudscheduler.googleapis.com/v1/projects/${firebaseConfig.projectId}/locations/us-central1/jobs/firebase-schedule-${functionName}-${region}:run`,
3980
{
4081
method: 'POST',
4182
headers: {
4283
'Content-Type': 'application/json',
43-
Authorization: `Bearer ${accessToken.access_token}`,
84+
Authorization: `Bearer ${accessToken}`,
4485
},
4586
}
4687
);
@@ -56,7 +97,7 @@ async function updateRemoteConfig(
5697
testId: string,
5798
accessToken: string
5899
): Promise<void> {
59-
await fetch(
100+
const resp = await fetch(
60101
`https://firebaseremoteconfig.googleapis.com/v1/projects/${firebaseConfig.projectId}/remoteConfig`,
61102
{
62103
method: 'PUT',
@@ -69,9 +110,12 @@ async function updateRemoteConfig(
69110
body: JSON.stringify({ version: { description: testId } }),
70111
}
71112
);
113+
if (!resp.ok) {
114+
throw new Error(resp.statusText);
115+
}
72116
}
73117

74-
function v1Tests(testId: string, accessToken: string) {
118+
function v1Tests(testId: string, accessToken: string): Promise<void>[] {
75119
return [
76120
// A database write to trigger the Firebase Realtime Database tests.
77121
admin
@@ -109,9 +153,16 @@ function v1Tests(testId: string, accessToken: string) {
109153
.storage()
110154
.bucket()
111155
.upload('/tmp/' + testId + '.txt'),
112-
testLab.startTestRun(firebaseConfig.projectId, testId),
156+
testLab.startTestRun(firebaseConfig.projectId, testId, accessToken),
113157
// Invoke the schedule for our scheduled function to fire
114-
callScheduleTrigger('v1-schedule', 'us-central1'),
158+
callScheduleTrigger('v1-schedule', 'us-central1', accessToken),
159+
];
160+
}
161+
162+
function v2Tests(testId: string, accessToken: string): Promise<void>[] {
163+
return [
164+
// Invoke a callable HTTPS trigger.
165+
callV2HttpsTrigger('v2-callabletests', { foo: 'bar', testId }, accessToken),
115166
];
116167
}
117168

@@ -136,15 +187,21 @@ export const integrationTests: any = functions
136187
const accessToken = await admin.credential
137188
.applicationDefault()
138189
.getAccessToken();
139-
await Promise.all([...v1Tests(testId, accessToken.access_token)]);
190+
await Promise.all([
191+
...v1Tests(testId, accessToken.access_token),
192+
...v2Tests(testId, accessToken.access_token),
193+
]);
140194
// On test completion, check that all tests pass and reply "PASS", or provide further details.
141195
functions.logger.info('Waiting for all tests to report they pass...');
142196
await new Promise<void>((resolve, reject) => {
143197
setTimeout(() => reject(new Error('Timeout')), 5 * 60 * 1000);
144198
let testsExecuted = 0;
145199
testIdRef.on('child_added', (snapshot) => {
200+
if (snapshot.key === 'timestamp') {
201+
return;
202+
}
146203
testsExecuted += 1;
147-
if (snapshot.key != 'timestamp' && !snapshot.val().passed) {
204+
if (!snapshot.val().passed) {
148205
reject(
149206
new Error(
150207
`test ${snapshot.key} failed; see database for details.`
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// TODO: Add back support for selecting region for integration test once params is ready.
2+
export const REGION = 'us-central1';

integration_test/functions/src/v1/auth-tests.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import * as admin from 'firebase-admin';
22
import * as functions from 'firebase-functions';
33
import { expectEq, TestSuite } from '../testing';
4+
import { REGION } from '../region';
45
import UserMetadata = admin.auth.UserRecord;
56

6-
const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1';
7-
87
export const createUserTests: any = functions
98
.region(REGION)
109
.auth.user()

integration_test/functions/src/v1/database-tests.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import * as admin from 'firebase-admin';
22
import * as functions from 'firebase-functions';
33
import { expectEq, expectMatches, TestSuite } from '../testing';
4+
import { REGION } from '../region';
45
import DataSnapshot = admin.database.DataSnapshot;
56

67
const testIdFieldName = 'testId';
7-
const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1';
88

99
export const databaseTests: any = functions
1010
.region(REGION)

integration_test/functions/src/v1/firestore-tests.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import * as admin from 'firebase-admin';
22
import * as functions from 'firebase-functions';
33
import { expectDeepEq, expectEq, TestSuite } from '../testing';
4+
import { REGION } from '../region';
45
import DocumentSnapshot = admin.firestore.DocumentSnapshot;
56

67
const testIdFieldName = 'documentId';
7-
const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1';
88

99
export const firestoreTests: any = functions
1010
.runWith({

integration_test/functions/src/v1/https-tests.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import * as functions from 'firebase-functions';
2+
import { REGION } from '../region';
23
import { expectEq, TestSuite } from '../testing';
34

4-
const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1';
5-
65
export const callableTests: any = functions.region(REGION).https.onCall((d) => {
76
return new TestSuite('https onCall')
87
.it('should have the correct data', (data: any) =>

integration_test/functions/src/v1/pubsub-tests.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import * as admin from 'firebase-admin';
22
import * as functions from 'firebase-functions';
3+
import { REGION } from '../region';
34
import { evaluate, expectEq, success, TestSuite } from '../testing';
45
import PubsubMessage = functions.pubsub.Message;
56

6-
const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1';
7-
87
// TODO(inlined) use multiple queues to run inline.
98
// Expected message data: {"hello": "world"}
109
export const pubsubTests: any = functions

integration_test/functions/src/v1/remoteConfig-tests.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import * as functions from 'firebase-functions';
2+
import { REGION } from '../region';
23
import { expectEq, TestSuite } from '../testing';
34
import TemplateVersion = functions.remoteConfig.TemplateVersion;
45

5-
const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1';
6-
76
export const remoteConfigTests: any = functions
87
.region(REGION)
98
.remoteConfig.onUpdate((v, c) => {

integration_test/functions/src/v1/storage-tests.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import * as functions from 'firebase-functions';
2+
import { REGION } from '../region';
23
import { expectEq, TestSuite } from '../testing';
34
import ObjectMetadata = functions.storage.ObjectMetadata;
45

5-
const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1';
6-
76
export const storageTests: any = functions
87
.runWith({
98
timeoutSeconds: 540,

integration_test/functions/src/v1/testLab-tests.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as functions from 'firebase-functions';
2+
import { REGION } from '../region';
23
import { expectEq, TestSuite } from '../testing';
34
import TestMatrix = functions.testLab.TestMatrix;
4-
const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1';
55

66
export const testLabTests: any = functions
77
.runWith({

integration_test/functions/src/v1/testLab-utils.ts

+19-13
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,31 @@ const TESTING_API_SERVICE_NAME = 'testing.googleapis.com';
1616
*
1717
* @param projectId Project for which the test run will be created
1818
* @param testId Test id which will be encoded in client info details
19+
* @param accessToken accessToken to attach to requested for authentication
1920
*/
20-
export async function startTestRun(projectId: string, testId: string) {
21-
const accessToken = await admin.credential
22-
.applicationDefault()
23-
.getAccessToken();
21+
export async function startTestRun(
22+
projectId: string,
23+
testId: string,
24+
accessToken: string
25+
) {
2426
const device = await fetchDefaultDevice(accessToken);
2527
return await createTestMatrix(accessToken, projectId, testId, device);
2628
}
2729

28-
async function fetchDefaultDevice(
29-
accessToken: admin.GoogleOAuthAccessToken
30-
): Promise<AndroidDevice> {
31-
const response = await fetch(
30+
async function fetchDefaultDevice(accessToken: string): Promise<AndroidDevice> {
31+
const resp = await fetch(
3232
`https://${TESTING_API_SERVICE_NAME}/v1/testEnvironmentCatalog/ANDROID`,
3333
{
3434
headers: {
35-
Authorization: 'Bearer ' + accessToken.access_token,
35+
Authorization: 'Bearer ' + accessToken,
3636
'Content-Type': 'application/json',
3737
},
3838
}
3939
);
40-
const data = await response.json();
40+
if (!resp.ok) {
41+
throw new Error(resp.statusText);
42+
}
43+
const data = await resp.json();
4144
const models = data?.androidDeviceCatalog?.models || [];
4245
const defaultModels = models.filter(
4346
(m) =>
@@ -63,7 +66,7 @@ async function fetchDefaultDevice(
6366
}
6467

6568
async function createTestMatrix(
66-
accessToken: admin.GoogleOAuthAccessToken,
69+
accessToken: string,
6770
projectId: string,
6871
testId: string,
6972
device: AndroidDevice
@@ -95,16 +98,19 @@ async function createTestMatrix(
9598
},
9699
},
97100
};
98-
await fetch(
101+
const resp = await fetch(
99102
`https://${TESTING_API_SERVICE_NAME}/v1/projects/${projectId}/testMatrices`,
100103
{
101104
method: 'POST',
102105
headers: {
103-
Authorization: 'Bearer ' + accessToken.access_token,
106+
Authorization: 'Bearer ' + accessToken,
104107
'Content-Type': 'application/json',
105108
},
106109
body: JSON.stringify(body),
107110
}
108111
);
112+
if (!resp.ok) {
113+
throw new Error(resp.statusText);
114+
}
109115
return;
110116
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { onCall } from 'firebase-functions/v2/https';
2+
import { expectEq, TestSuite } from '../testing';
3+
4+
export const callabletests = onCall((req) => {
5+
return new TestSuite('v2 https onCall')
6+
.it('should have the correct data', (data: any) =>
7+
expectEq(data?.foo, 'bar')
8+
)
9+
.run(req.data.testId, req.data);
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { REGION } from '../region';
2+
import { setGlobalOptions } from 'firebase-functions/v2';
3+
setGlobalOptions({ region: REGION });
4+
5+
export * from './https-tests';

0 commit comments

Comments
 (0)