Skip to content

Commit ee793c7

Browse files
authored
Update integration test so that it works again. (#869)
* Updated node versions under test * Updated node dependencies to modern versions * Removed lodash dependency * Minor updates to TypeScript annotations/style * Fix URL and authentication when invoking a cloud schedule * Fix Cloud Schedule test to not finish until tests are complete
1 parent f2435b1 commit ee793c7

File tree

10 files changed

+202
-208
lines changed

10 files changed

+202
-208
lines changed

integration_test/firestore.rules

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
rules_version = "2";
2+
13
service cloud.firestore {
24
match /databases/{database}/documents {
35
match /{document=**} {
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import * as functions from 'firebase-functions';
2-
import * as _ from 'lodash';
32
import { expectEq, TestSuite } from './testing';
43

54
const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1';
65

76
export const callableTests: any = functions.region(REGION).https.onCall((d) => {
87
return new TestSuite('https onCall')
9-
.it('should have the correct data', (data) =>
10-
expectEq(_.get(data, 'foo'), 'bar')
8+
.it('should have the correct data', (data: any) =>
9+
expectEq(data?.foo, 'bar')
1110
)
1211
.run(d.testId, d);
1312
});

integration_test/functions/src/index.ts

Lines changed: 122 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as admin from 'firebase-admin';
33
import * as functions from 'firebase-functions';
44
import * as fs from 'fs';
55
import * as https from 'https';
6+
import { PubSub } from '@google-cloud/pubsub';
67

78
export * from './pubsub-tests';
89
export * from './database-tests';
@@ -18,6 +19,7 @@ import * as utils from './test-utils';
1819
import * as testLab from './testLab-utils';
1920

2021
import 'firebase-functions'; // temporary shim until process.env.FIREBASE_CONFIG available natively in GCF(BUG 63586213)
22+
import { config } from 'firebase-functions';
2123
const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG);
2224
admin.initializeApp();
2325
const REGION = functions.config().functions.test_region;
@@ -37,26 +39,42 @@ function callHttpsTrigger(name: string, data: any, baseUrl) {
3739
);
3840
}
3941

40-
function callScheduleTrigger(functionName: string, region: string) {
41-
return new Promise((resolve, reject) => {
42+
async function callScheduleTrigger(functionName: string, region: string) {
43+
const accessToken = await admin.credential
44+
.applicationDefault()
45+
.getAccessToken();
46+
return new Promise<string>((resolve, reject) => {
4247
const request = https.request(
4348
{
4449
method: 'POST',
4550
host: 'cloudscheduler.googleapis.com',
46-
path: `projects/${firebaseConfig.projectId}/locations/us-central1/jobs/firebase-schedule-${functionName}-${region}:run`,
51+
path: `/v1/projects/${firebaseConfig.projectId}/locations/us-central1/jobs/firebase-schedule-${functionName}-${region}:run`,
4752
headers: {
4853
'Content-Type': 'application/json',
54+
Authorization: `Bearer ${accessToken.access_token}`,
4955
},
5056
},
5157
(response) => {
58+
if (response.statusCode! / 100 != 2) {
59+
reject(
60+
new Error('Failed request with status ' + response.statusCode!)
61+
);
62+
return;
63+
}
5264
let body = '';
5365
response.on('data', (chunk) => {
5466
body += chunk;
5567
});
56-
response.on('end', () => resolve(body));
68+
response.on('end', () => {
69+
console.log(`Successfully scheduled function ${functionName}`);
70+
resolve(body);
71+
});
5772
}
5873
);
59-
request.on('error', reject);
74+
request.on('error', (err) => {
75+
console.error('Failed to schedule cloud scheduler job with error', err);
76+
reject(err);
77+
});
6078
request.write('{}');
6179
request.end();
6280
});
@@ -67,14 +85,13 @@ export const integrationTests: any = functions
6785
.runWith({
6886
timeoutSeconds: 540,
6987
})
70-
.https.onRequest((req: Request, resp: Response) => {
88+
.https.onRequest(async (req: Request, resp: Response) => {
7189
// We take the base url for our https call (cloudfunctions.net, txckloud.net, etc) from the request
7290
// so that it changes with the environment that the tests are run in
7391
const baseUrl = req.hostname
7492
.split('.')
7593
.slice(1)
7694
.join('.');
77-
const pubsub: any = require('@google-cloud/pubsub')();
7895
const testId = admin
7996
.database()
8097
.ref()
@@ -83,114 +100,107 @@ export const integrationTests: any = functions
83100
.database()
84101
.ref(`testRuns/${testId}/timestamp`)
85102
.set(Date.now());
103+
const testIdRef = admin.database().ref(`testRuns/${testId}`);
86104
console.log('testId is: ', testId);
87105
fs.writeFile('/tmp/' + testId + '.txt', 'test', () => {});
88-
return Promise.all([
89-
// A database write to trigger the Firebase Realtime Database tests.
90-
admin
91-
.database()
92-
.ref(`dbTests/${testId}/start`)
93-
.set({ '.sv': 'timestamp' }),
94-
// A Pub/Sub publish to trigger the Cloud Pub/Sub tests.
95-
pubsub
96-
.topic('pubsubTests')
97-
.publisher()
98-
.publish(Buffer.from(JSON.stringify({ testId }))),
99-
// A user creation to trigger the Firebase Auth user creation tests.
100-
admin
101-
.auth()
102-
.createUser({
103-
email: `${testId}@fake.com`,
104-
password: 'secret',
105-
displayName: `${testId}`,
106-
})
107-
.then((userRecord) => {
108-
// A user deletion to trigger the Firebase Auth user deletion tests.
109-
admin.auth().deleteUser(userRecord.uid);
110-
}),
111-
// A firestore write to trigger the Cloud Firestore tests.
112-
admin
113-
.firestore()
114-
.collection('tests')
115-
.doc(testId)
116-
.set({ test: testId }),
117-
// Invoke a callable HTTPS trigger.
118-
callHttpsTrigger('callableTests', { foo: 'bar', testId }, baseUrl),
119-
// A Remote Config update to trigger the Remote Config tests.
120-
admin.credential
121-
.applicationDefault()
122-
.getAccessToken()
123-
.then((accessToken) => {
124-
const options = {
125-
hostname: 'firebaseremoteconfig.googleapis.com',
126-
path: `/v1/projects/${firebaseConfig.projectId}/remoteConfig`,
127-
method: 'PUT',
128-
headers: {
129-
Authorization: 'Bearer ' + accessToken.access_token,
130-
'Content-Type': 'application/json; UTF-8',
131-
'Accept-Encoding': 'gzip',
132-
'If-Match': '*',
133-
},
134-
};
135-
const request = https.request(options, (resp) => {});
136-
request.write(JSON.stringify({ version: { description: testId } }));
137-
request.end();
138-
}),
139-
// A storage upload to trigger the Storage tests
140-
admin
141-
.storage()
142-
.bucket()
143-
.upload('/tmp/' + testId + '.txt'),
144-
testLab.startTestRun(firebaseConfig.projectId, testId),
145-
// Invoke the schedule for our scheduled function to fire
146-
callScheduleTrigger('schedule', 'us-central1'),
147-
])
148-
.then(() => {
149-
// On test completion, check that all tests pass and reply "PASS", or provide further details.
150-
console.log('Waiting for all tests to report they pass...');
151-
const ref = admin.database().ref(`testRuns/${testId}`);
152-
return new Promise((resolve, reject) => {
153-
let testsExecuted = 0;
154-
ref.on('child_added', (snapshot) => {
155-
testsExecuted += 1;
156-
if (snapshot.key != 'timestamp' && !snapshot.val().passed) {
157-
reject(
158-
new Error(
159-
`test ${snapshot.key} failed; see database for details.`
160-
)
161-
);
162-
return;
163-
}
164-
console.log(
165-
`${snapshot.key} passed (${testsExecuted} of ${numTests})`
166-
);
167-
if (testsExecuted < numTests) {
168-
// Not all tests have completed. Wait longer.
169-
return;
170-
}
171-
// All tests have passed!
172-
resolve();
173-
});
174-
})
175-
.then(() => {
176-
ref.off(); // No more need to listen.
177-
return Promise.resolve();
106+
try {
107+
await Promise.all([
108+
// A database write to trigger the Firebase Realtime Database tests.
109+
admin
110+
.database()
111+
.ref(`dbTests/${testId}/start`)
112+
.set({ '.sv': 'timestamp' }),
113+
// A Pub/Sub publish to trigger the Cloud Pub/Sub tests.
114+
new PubSub()
115+
.topic('pubsubTests')
116+
.publish(Buffer.from(JSON.stringify({ testId }))),
117+
// A user creation to trigger the Firebase Auth user creation tests.
118+
admin
119+
.auth()
120+
.createUser({
121+
email: `${testId}@fake.com`,
122+
password: 'secret',
123+
displayName: `${testId}`,
178124
})
179-
.catch((err) => {
180-
ref.off(); // No more need to listen.
181-
return Promise.reject(err);
182-
});
183-
})
184-
.then(() => {
185-
console.log('All tests pass!');
186-
resp.status(200).send('PASS \n');
187-
})
188-
.catch((err) => {
189-
console.log(`Some tests failed: ${err}`);
190-
resp
191-
.status(500)
192-
.send(
193-
`FAIL - details at https://${process.env.GCLOUD_PROJECT}.firebaseio.com/testRuns/${testId}`
125+
.then((userRecord) => {
126+
// A user deletion to trigger the Firebase Auth user deletion tests.
127+
admin.auth().deleteUser(userRecord.uid);
128+
}),
129+
// A firestore write to trigger the Cloud Firestore tests.
130+
admin
131+
.firestore()
132+
.collection('tests')
133+
.doc(testId)
134+
.set({ test: testId }),
135+
// Invoke a callable HTTPS trigger.
136+
callHttpsTrigger('callableTests', { foo: 'bar', testId }, baseUrl),
137+
// A Remote Config update to trigger the Remote Config tests.
138+
admin.credential
139+
.applicationDefault()
140+
.getAccessToken()
141+
.then((accessToken) => {
142+
const options = {
143+
hostname: 'firebaseremoteconfig.googleapis.com',
144+
path: `/v1/projects/${firebaseConfig.projectId}/remoteConfig`,
145+
method: 'PUT',
146+
headers: {
147+
Authorization: 'Bearer ' + accessToken.access_token,
148+
'Content-Type': 'application/json; UTF-8',
149+
'Accept-Encoding': 'gzip',
150+
'If-Match': '*',
151+
},
152+
};
153+
const request = https.request(options, (resp) => {});
154+
request.write(JSON.stringify({ version: { description: testId } }));
155+
request.end();
156+
}),
157+
// A storage upload to trigger the Storage tests
158+
admin
159+
.storage()
160+
.bucket()
161+
.upload('/tmp/' + testId + '.txt'),
162+
testLab.startTestRun(firebaseConfig.projectId, testId),
163+
// Invoke the schedule for our scheduled function to fire
164+
callScheduleTrigger('schedule', 'us-central1'),
165+
]);
166+
167+
// On test completion, check that all tests pass and reply "PASS", or provide further details.
168+
console.log('Waiting for all tests to report they pass...');
169+
await new Promise<void>((resolve, reject) => {
170+
setTimeout(() => reject(new Error('Timeout')), 5 * 60 * 1000);
171+
let testsExecuted = 0;
172+
testIdRef.on('child_added', (snapshot) => {
173+
testsExecuted += 1;
174+
if (snapshot.key != 'timestamp' && !snapshot.val().passed) {
175+
reject(
176+
new Error(
177+
`test ${snapshot.key} failed; see database for details.`
178+
)
179+
);
180+
return;
181+
}
182+
console.log(
183+
`${snapshot.key} passed (${testsExecuted} of ${numTests})`
194184
);
185+
if (testsExecuted < numTests) {
186+
// Not all tests have completed. Wait longer.
187+
return;
188+
}
189+
// All tests have passed!
190+
resolve();
191+
});
195192
});
193+
console.log('All tests pass!');
194+
resp.status(200).send('PASS \n');
195+
} catch (err) {
196+
console.log(`Some tests failed: ${err}`);
197+
resp
198+
.status(500)
199+
.send(
200+
`FAIL - details at ${functions.firebaseConfig()
201+
.databaseURL!}/testRuns/${testId}`
202+
);
203+
} finally {
204+
testIdRef.off('child_added');
205+
}
196206
});

integration_test/functions/src/pubsub-tests.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,15 @@ export const schedule: any = functions
6666
.region(REGION)
6767
.pubsub.schedule('every 10 hours') // This is a dummy schedule, since we need to put a valid one in.
6868
// For the test, the job is triggered by the jobs:run api
69-
.onRun((context) => {
70-
let testId;
69+
.onRun(async (context) => {
7170
const db = admin.database();
72-
return new Promise(async (resolve, reject) => {
73-
await db
74-
.ref('testRuns')
75-
.orderByChild('timestamp')
76-
.limitToLast(1)
77-
.on('value', (snap) => {
78-
testId = Object.keys(snap.val())[0];
79-
new TestSuite('pubsub scheduleOnRun')
80-
.it('should trigger when the scheduler fires', () => success())
81-
.run(testId, null);
82-
});
83-
resolve();
84-
});
71+
const snap = await db
72+
.ref('testRuns')
73+
.orderByChild('timestamp')
74+
.limitToLast(1)
75+
.once('value');
76+
const testId = Object.keys(snap.val())[0];
77+
return new TestSuite('pubsub scheduleOnRun')
78+
.it('should trigger when the scheduler fires', () => success())
79+
.run(testId, null);
8580
});

integration_test/functions/src/testLab-tests.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as functions from 'firebase-functions';
2-
import * as _ from 'lodash';
32
import { TestSuite, expectEq } from './testing';
43
import TestMatrix = functions.testLab.TestMatrix;
54
const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1';
@@ -24,5 +23,5 @@ export const testLabTests: any = functions
2423
expectEq(matrix.state, 'INVALID')
2524
)
2625

27-
.run(_.get(matrix, 'clientInfo.details.testId'), matrix, context);
26+
.run(matrix?.clientInfo?.details?.testId, matrix, context);
2827
});

integration_test/functions/src/testLab-utils.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as http from 'http';
22
import * as https from 'https';
33
import * as admin from 'firebase-admin';
4-
import * as _ from 'lodash';
54
import * as utils from './test-utils';
65

76
interface AndroidDevice {
@@ -35,7 +34,7 @@ async function fetchDefaultDevice(
3534
requestOptions(accessToken, 'GET', '/v1/testEnvironmentCatalog/ANDROID')
3635
);
3736
const data = JSON.parse(response);
38-
const models = _.get(data, 'androidDeviceCatalog.models', []);
37+
const models = data?.androidDeviceCatalog?.models || [];
3938
const defaultModels = models.filter(
4039
(m) =>
4140
m.tags !== undefined &&

0 commit comments

Comments
 (0)