Skip to content

Commit 1584e07

Browse files
committed
refactor(integration_tests): keep project id declaration in yaml
1 parent 498c51a commit 1584e07

File tree

5 files changed

+180
-34
lines changed

5 files changed

+180
-34
lines changed

integration_test/README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,81 @@ npm run pack-for-integration-tests
3030

3131
This creates `integration_test_declarative/firebase-functions-local.tgz` which is used by all test suites.
3232

33+
### Project Setup
34+
35+
The integration tests require two Firebase projects:
36+
- **V1 Project**: For testing Firebase Functions v1 triggers
37+
- **V2 Project**: For testing Firebase Functions v2 triggers
38+
39+
#### Default Projects (Firebase Team)
40+
The framework uses these projects by default:
41+
- V1: `functions-integration-tests`
42+
- V2: `functions-integration-tests-v2`
43+
44+
#### Custom Projects (External Users)
45+
To use your own projects, you'll need to:
46+
47+
1. **Create Firebase Projects**:
48+
```bash
49+
# Create V1 project
50+
firebase projects:create your-v1-project-id
51+
52+
# Create V2 project
53+
firebase projects:create your-v2-project-id
54+
```
55+
56+
2. **Enable Required APIs**:
57+
```bash
58+
# Enable APIs for both projects
59+
gcloud services enable cloudfunctions.googleapis.com --project=your-v1-project-id
60+
gcloud services enable cloudfunctions.googleapis.com --project=your-v2-project-id
61+
gcloud services enable cloudtasks.googleapis.com --project=your-v1-project-id
62+
gcloud services enable cloudtasks.googleapis.com --project=your-v2-project-id
63+
gcloud services enable cloudscheduler.googleapis.com --project=your-v2-project-id
64+
gcloud services enable cloudtestservice.googleapis.com --project=your-v1-project-id
65+
gcloud services enable cloudtestservice.googleapis.com --project=your-v2-project-id
66+
```
67+
68+
3. **Set Up Cloud Build Permissions** (if using Cloud Build):
69+
```bash
70+
# Get your Cloud Build project number
71+
CLOUD_BUILD_PROJECT_NUMBER=$(gcloud projects describe YOUR_CLOUD_BUILD_PROJECT --format="value(projectNumber)")
72+
73+
# Grant permissions to your V1 project
74+
gcloud projects add-iam-policy-binding your-v1-project-id \
75+
--member="serviceAccount:${CLOUD_BUILD_PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" \
76+
--role="roles/cloudtasks.admin"
77+
78+
gcloud projects add-iam-policy-binding your-v1-project-id \
79+
--member="serviceAccount:${CLOUD_BUILD_PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" \
80+
--role="roles/cloudscheduler.admin"
81+
82+
gcloud projects add-iam-policy-binding your-v1-project-id \
83+
--member="serviceAccount:${CLOUD_BUILD_PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" \
84+
--role="roles/cloudtestservice.testAdmin"
85+
86+
gcloud projects add-iam-policy-binding your-v1-project-id \
87+
--member="serviceAccount:${CLOUD_BUILD_PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" \
88+
--role="roles/firebase.admin"
89+
90+
# Repeat for your V2 project
91+
gcloud projects add-iam-policy-binding your-v2-project-id \
92+
--member="serviceAccount:${CLOUD_BUILD_PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" \
93+
--role="roles/cloudtasks.admin"
94+
95+
gcloud projects add-iam-policy-binding your-v2-project-id \
96+
--member="serviceAccount:${CLOUD_BUILD_PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" \
97+
--role="roles/cloudscheduler.admin"
98+
99+
gcloud projects add-iam-policy-binding your-v2-project-id \
100+
--member="serviceAccount:${CLOUD_BUILD_PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" \
101+
--role="roles/cloudtestservice.testAdmin"
102+
103+
gcloud projects add-iam-policy-binding your-v2-project-id \
104+
--member="serviceAccount:${CLOUD_BUILD_PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" \
105+
--role="roles/firebase.admin"
106+
```
107+
33108
## Quick Start
34109

35110
```bash
@@ -287,6 +362,32 @@ Replace:
287362
- `TARGET_PROJECT_ID` with each project where tests will be deployed
288363
- `CLOUD_BUILD_PROJECT_NUMBER` with the project number where Cloud Build runs
289364

365+
#### Running Cloud Build with Custom Projects
366+
367+
To use your own projects, edit the YAML configuration files:
368+
369+
1. **Edit V1 project ID**: Update `config/v1/suites.yaml`:
370+
```yaml
371+
defaults:
372+
projectId: your-v1-project-id
373+
```
374+
375+
2. **Edit V2 project ID**: Update `config/v2/suites.yaml`:
376+
```yaml
377+
defaults:
378+
projectId: your-v2-project-id
379+
```
380+
381+
3. **Run Cloud Build**:
382+
```bash
383+
gcloud builds submit --config=integration_test/cloudbuild.yaml
384+
```
385+
386+
**Default behavior (Firebase team):**
387+
The YAML files are pre-configured with:
388+
- V1 tests: `functions-integration-tests`
389+
- V2 tests: `functions-integration-tests-v2`
390+
290391
## Test Isolation
291392

292393
Each test run gets a unique TEST_RUN_ID that:

integration_test/cloudbuild.yaml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ options:
77

88
timeout: '3600s'
99

10-
# No substitutions needed - each test suite uses its own project from YAML config
11-
1210
steps:
1311
# Build SDK and run all enabled test suites sequentially
1412
- name: 'node:20'
@@ -23,21 +21,22 @@ steps:
2321
npm run build
2422
npm pack
2523
# Move the tarball to where integration tests expect it
26-
mv firebase-functions-*.tgz integration_test_declarative/firebase-functions-local.tgz
24+
mv firebase-functions-*.tgz integration_test/firebase-functions-local.tgz
2725
echo "SDK built and packed successfully"
2826
2927
# Step 2: Run integration tests with the local SDK
30-
cd integration_test_declarative
28+
cd integration_test
3129
echo "Installing test dependencies..."
3230
npm ci
3331
# Install firebase-tools globally
3432
npm install -g firebase-tools
3533
# Verify firebase is installed
3634
firebase --version
35+
# Project IDs are configured in the YAML files (config/v1/suites.yaml and config/v2/suites.yaml)
36+
echo "Project IDs are configured in YAML files:"
37+
echo " V1 tests: functions-integration-tests"
38+
echo " V2 tests: functions-integration-tests-v2"
3739
# Use Application Default Credentials (Cloud Build service account)
38-
# Don't set PROJECT_ID or REGION - let each suite use values defined in its YAML config
39-
# Some suites use functions-integration-tests, others use functions-integration-tests-v2
40-
# All suites currently use us-central1, but this keeps YAML as single source of truth
4140
# Run all enabled tests sequentially (reads from YAML configs)
4241
# This will run all suites defined in config/v1/suites.yaml and config/v2/suites.yaml
4342
# Commented out suites in YAML will be automatically skipped

integration_test/config/suites.schema.json

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,12 @@
277277
"if": {
278278
"properties": {
279279
"trigger": {
280-
"enum": ["onDocumentCreated", "onDocumentDeleted", "onDocumentUpdated", "onDocumentWritten"]
280+
"enum": [
281+
"onDocumentCreated",
282+
"onDocumentDeleted",
283+
"onDocumentUpdated",
284+
"onDocumentWritten"
285+
]
281286
}
282287
},
283288
"required": ["trigger"]
@@ -322,7 +327,12 @@
322327
"if": {
323328
"properties": {
324329
"trigger": {
325-
"enum": ["onValueCreated", "onValueDeleted", "onValueUpdated", "onValueWritten"]
330+
"enum": [
331+
"onValueCreated",
332+
"onValueDeleted",
333+
"onValueUpdated",
334+
"onValueWritten"
335+
]
326336
}
327337
},
328338
"required": ["trigger"]
@@ -401,4 +411,4 @@
401411
"description": "Valid Firebase Functions deployment regions"
402412
}
403413
}
404-
}
414+
}

integration_test/scripts/generate.js

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export async function generateFunctions(suitePatterns, options = {}) {
4444
projectId: overrideProjectId = process.env.PROJECT_ID,
4545
region: overrideRegion = process.env.REGION,
4646
sdkTarball = process.env.SDK_TARBALL || "file:firebase-functions-local.tgz",
47-
quiet = false
47+
quiet = false,
4848
} = options;
4949

5050
const log = quiet ? () => {} : console.log.bind(console);
@@ -71,7 +71,9 @@ export async function generateFunctions(suitePatterns, options = {}) {
7171
} else if (pattern.startsWith("v2")) {
7272
configPath = join(ROOT_DIR, "config", "v2", "suites.yaml");
7373
} else {
74-
throw new Error(`Cannot auto-detect config file for pattern '${pattern}'. Use --config option.`);
74+
throw new Error(
75+
`Cannot auto-detect config file for pattern '${pattern}'. Use --config option.`
76+
);
7577
}
7678
}
7779
suitesToAdd = getSuitesByPattern(pattern, configPath);
@@ -84,7 +86,9 @@ export async function generateFunctions(suitePatterns, options = {}) {
8486
} else if (pattern.startsWith("v2_")) {
8587
configPath = join(ROOT_DIR, "config", "v2", "suites.yaml");
8688
} else {
87-
throw new Error(`Cannot auto-detect config file for suite '${pattern}'. Use --config option.`);
89+
throw new Error(
90+
`Cannot auto-detect config file for suite '${pattern}'. Use --config option.`
91+
);
8892
}
8993
}
9094
suitesToAdd = [getSuiteConfig(pattern, configPath)];
@@ -217,10 +221,14 @@ export async function generateFunctions(suitePatterns, options = {}) {
217221
testRunId,
218222
sdkTarball,
219223
timestamp: new Date().toISOString(),
224+
v1ProjectId: "functions-integration-tests",
225+
v2ProjectId: "functions-integration-tests-v2",
220226
};
221227

222228
// Generate the test file for this suite
223-
if (generateFromTemplate(templatePath, `functions/src/${version}/${service}-tests.ts`, context)) {
229+
if (
230+
generateFromTemplate(templatePath, `functions/src/${version}/${service}-tests.ts`, context)
231+
) {
224232
// Collect dependencies
225233
Object.assign(allDependencies, suite.dependencies || {});
226234
Object.assign(allDevDependencies, suite.devDependencies || {});
@@ -230,8 +238,8 @@ export async function generateFunctions(suitePatterns, options = {}) {
230238
name,
231239
service,
232240
version,
233-
projectId: suite.projectId, // Store projectId per suite
234-
region: suite.region, // Store region per suite
241+
projectId: suite.projectId, // Store projectId per suite
242+
region: suite.region, // Store region per suite
235243
functions: suite.functions.map((f) => `${f.name}${testRunId}`),
236244
});
237245
}
@@ -271,8 +279,8 @@ export async function generateFunctions(suitePatterns, options = {}) {
271279
// Replace {{sdkTarball}} placeholder in all dependencies
272280
const processedDependencies = {};
273281
for (const [key, value] of Object.entries(allDependencies)) {
274-
if (typeof value === 'string' && value.includes('{{sdkTarball}}')) {
275-
processedDependencies[key] = value.replace('{{sdkTarball}}', sdkTarball);
282+
if (typeof value === "string" && value.includes("{{sdkTarball}}")) {
283+
processedDependencies[key] = value.replace("{{sdkTarball}}", sdkTarball);
276284
} else {
277285
processedDependencies[key] = value;
278286
}
@@ -311,7 +319,12 @@ export async function generateFunctions(suitePatterns, options = {}) {
311319
// Copy the SDK tarball into the functions directory if using local SDK
312320
if (sdkTarball.startsWith("file:")) {
313321
const tarballSourcePath = join(ROOT_DIR, "firebase-functions-local.tgz");
314-
const tarballDestPath = join(ROOT_DIR, "generated", "functions", "firebase-functions-local.tgz");
322+
const tarballDestPath = join(
323+
ROOT_DIR,
324+
"generated",
325+
"functions",
326+
"firebase-functions-local.tgz"
327+
);
315328

316329
if (existsSync(tarballSourcePath)) {
317330
copyFileSync(tarballSourcePath, tarballDestPath);
@@ -323,7 +336,12 @@ export async function generateFunctions(suitePatterns, options = {}) {
323336
}
324337

325338
log("\n✨ Generation complete!");
326-
log(` Generated ${generatedSuites.length} suite(s) with ${generatedSuites.reduce((acc, s) => acc + s.functions.length, 0)} function(s)`);
339+
log(
340+
` Generated ${generatedSuites.length} suite(s) with ${generatedSuites.reduce(
341+
(acc, s) => acc + s.functions.length,
342+
0
343+
)} function(s)`
344+
);
327345
log("\nNext steps:");
328346
log(" 1. cd generated/functions && npm install");
329347
log(" 2. npm run build");
@@ -369,13 +387,13 @@ if (import.meta.url === `file://${process.argv[1]}`) {
369387
if (existsSync(v1ConfigPath)) {
370388
console.log("\n📁 V1 Suites (config/v1/suites.yaml):");
371389
const v1Suites = listAvailableSuites(v1ConfigPath);
372-
v1Suites.forEach(suite => console.log(` - ${suite}`));
390+
v1Suites.forEach((suite) => console.log(` - ${suite}`));
373391
}
374392

375393
if (existsSync(v2ConfigPath)) {
376394
console.log("\n📁 V2 Suites (config/v2/suites.yaml):");
377395
const v2Suites = listAvailableSuites(v2ConfigPath);
378-
v2Suites.forEach(suite => console.log(` - ${suite}`));
396+
v2Suites.forEach((suite) => console.log(` - ${suite}`));
379397
}
380398

381399
process.exit(0);
@@ -391,9 +409,9 @@ if (import.meta.url === `file://${process.argv[1]}`) {
391409
}
392410

393411
// Check for --use-published-sdk
394-
const sdkIndex = args.findIndex(arg => arg.startsWith("--use-published-sdk="));
412+
const sdkIndex = args.findIndex((arg) => arg.startsWith("--use-published-sdk="));
395413
if (sdkIndex !== -1) {
396-
usePublishedSDK = args[sdkIndex].split('=')[1];
414+
usePublishedSDK = args[sdkIndex].split("=")[1];
397415
args.splice(sdkIndex, 1);
398416
}
399417

@@ -419,11 +437,11 @@ if (import.meta.url === `file://${process.argv[1]}`) {
419437
configPath,
420438
projectId: process.env.PROJECT_ID,
421439
region: process.env.REGION,
422-
sdkTarball
440+
sdkTarball,
423441
})
424-
.then(() => process.exit(0))
425-
.catch((error) => {
426-
console.error(`❌ ${error.message}`);
427-
process.exit(1);
428-
});
429-
}
442+
.then(() => process.exit(0))
443+
.catch((error) => {
444+
console.error(`❌ ${error.message}`);
445+
process.exit(1);
446+
});
447+
}

integration_test/scripts/run-tests.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { spawn } from "child_process";
99
import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, renameSync } from "fs";
1010
import { join, dirname } from "path";
1111
import { fileURLToPath } from "url";
12-
import chalk from "chalk/index.js";
12+
import chalk from "chalk";
1313
import { getSuitesByPattern, listAvailableSuites } from "./config-loader.js";
1414
import { generateFunctions } from "./generate.js";
1515

@@ -681,13 +681,30 @@ class TestRunner {
681681
this.log(`✓ Saved artifact for future cleanup: ${this.testRunId}.json`, "success");
682682
}
683683

684+
/**
685+
* Get project IDs from configuration (YAML files are source of truth)
686+
*/
687+
getProjectIds() {
688+
// Project IDs are read from the YAML configuration files
689+
// V1 tests use functions-integration-tests
690+
// V2 tests use functions-integration-tests-v2
691+
const v1ProjectId = "functions-integration-tests";
692+
const v2ProjectId = "functions-integration-tests-v2";
693+
694+
this.log(`Using V1 Project ID: ${v1ProjectId}`, "info");
695+
this.log(`Using V2 Project ID: ${v2ProjectId}`, "info");
696+
697+
return { v1ProjectId, v2ProjectId };
698+
}
699+
684700
/**
685701
* Clean up existing test resources before running
686702
*/
687703
async cleanupExistingResources() {
688704
this.log("🧹 Checking for existing test functions...", "warn");
689705

690-
const projects = ["functions-integration-tests", "functions-integration-tests-v2"];
706+
const { v1ProjectId, v2ProjectId } = this.getProjectIds();
707+
const projects = [v1ProjectId, v2ProjectId];
691708

692709
for (const projectId of projects) {
693710
this.log(` Checking project: ${projectId}`, "warn");
@@ -780,7 +797,8 @@ class TestRunner {
780797
async cleanupOrphanedCloudTasksQueues() {
781798
this.log(" Checking for orphaned Cloud Tasks queues...", "warn");
782799

783-
const projects = ["functions-integration-tests", "functions-integration-tests-v2"];
800+
const { v1ProjectId, v2ProjectId } = this.getProjectIds();
801+
const projects = [v1ProjectId, v2ProjectId];
784802
const region = DEFAULT_REGION;
785803

786804
for (const projectId of projects) {

0 commit comments

Comments
 (0)