Skip to content

Commit 498c51a

Browse files
committed
fix(integration_tests): permissions and task queue cleanup
1 parent 8d3e563 commit 498c51a

File tree

2 files changed

+173
-1
lines changed

2 files changed

+173
-1
lines changed

integration_test/README.md

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,45 @@ Add test file mapping in the case statement (lines 175-199).
248248

249249
## Authentication
250250

251+
### Local Development
251252
Place your service account key at `sa.json` in the root directory. This file is git-ignored.
252253

254+
### Cloud Build
255+
Cloud Build uses Application Default Credentials (ADC) automatically. However, the Cloud Build service account requires specific permissions for the Google Cloud services used in tests:
256+
257+
**Required IAM Roles for Cloud Build Service Account:**
258+
- `roles/cloudtasks.admin` - For Cloud Tasks integration tests
259+
- `roles/cloudscheduler.admin` - For Cloud Scheduler integration tests
260+
- `roles/cloudtestservice.testAdmin` - For Firebase Test Lab integration tests
261+
- `roles/firebase.admin` - For Firebase services (already included)
262+
- `roles/pubsub.publisher` - For Pub/Sub integration tests (already included)
263+
264+
**Multi-Project Setup:**
265+
Tests deploy to multiple projects (typically one for V1 tests and one for V2 tests). The Cloud Build service account needs the above permissions on **all target projects**:
266+
267+
```bash
268+
# Grant permissions to each target project
269+
gcloud projects add-iam-policy-binding TARGET_PROJECT_ID \
270+
--member="serviceAccount:[email protected]" \
271+
--role="roles/cloudtasks.admin"
272+
273+
gcloud projects add-iam-policy-binding TARGET_PROJECT_ID \
274+
--member="serviceAccount:[email protected]" \
275+
--role="roles/cloudscheduler.admin"
276+
277+
gcloud projects add-iam-policy-binding TARGET_PROJECT_ID \
278+
--member="serviceAccount:[email protected]" \
279+
--role="roles/cloudtestservice.testAdmin"
280+
281+
gcloud projects add-iam-policy-binding TARGET_PROJECT_ID \
282+
--member="serviceAccount:[email protected]" \
283+
--role="roles/firebase.admin"
284+
```
285+
286+
Replace:
287+
- `TARGET_PROJECT_ID` with each project where tests will be deployed
288+
- `CLOUD_BUILD_PROJECT_NUMBER` with the project number where Cloud Build runs
289+
253290
## Test Isolation
254291

255292
Each test run gets a unique TEST_RUN_ID that:
@@ -284,10 +321,37 @@ Format: `t_<timestamp>_<random>` (e.g., `t_1757979490_xkyqun`)
284321
- Ensure TEST_RUN_ID environment variable is set
285322
- Check test logs in logs/ directory
286323

324+
### Permission Errors in Cloud Build
325+
If you see authentication errors like "Could not refresh access token" or "Permission denied":
326+
- Verify Cloud Build service account has required IAM roles on all target projects
327+
- Check project numbers: `gcloud projects describe PROJECT_ID --format="value(projectNumber)"`
328+
- Grant missing permissions to each target project:
329+
```bash
330+
# For Cloud Tasks
331+
gcloud projects add-iam-policy-binding TARGET_PROJECT_ID \
332+
--member="serviceAccount:[email protected]" \
333+
--role="roles/cloudtasks.admin"
334+
335+
# For Cloud Scheduler
336+
gcloud projects add-iam-policy-binding TARGET_PROJECT_ID \
337+
--member="serviceAccount:[email protected]" \
338+
--role="roles/cloudscheduler.admin"
339+
340+
# For Test Lab
341+
gcloud projects add-iam-policy-binding TARGET_PROJECT_ID \
342+
--member="serviceAccount:[email protected]" \
343+
--role="roles/cloudtestservice.testAdmin"
344+
345+
# For Firebase services
346+
gcloud projects add-iam-policy-binding TARGET_PROJECT_ID \
347+
--member="serviceAccount:[email protected]" \
348+
--role="roles/firebase.admin"
349+
```
350+
287351
### Cleanup Issues
288352
- Use `npm run cleanup:list` to find orphaned test runs
289353
- Manual cleanup: `firebase functions:delete <function-name> --project <project-id> --force`
290-
- Check for leftover test functions: `firebase functions:list --project functions-integration-tests | grep Test`
354+
- Check for leftover test functions: `firebase functions:list --project PROJECT_ID | grep Test`
291355
- Check Firestore/Database console for orphaned test data
292356

293357
## Benefits

integration_test/scripts/run-tests.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,51 @@ class TestRunner {
621621
// Ignore cleanup errors
622622
}
623623
}
624+
625+
// Clean up Cloud Tasks queues if tasks tests were run
626+
if (metadata.suites.some((s) => s.name.includes("tasks"))) {
627+
await this.cleanupCloudTasksQueues(metadata);
628+
}
629+
}
630+
631+
/**
632+
* Clean up Cloud Tasks queues created by tests
633+
*/
634+
async cleanupCloudTasksQueues(metadata) {
635+
this.log(" Cleaning up Cloud Tasks queues...", "warn");
636+
637+
const region = metadata.region || DEFAULT_REGION;
638+
const projectId = metadata.projectId;
639+
640+
// Extract queue names from metadata (function names become queue names in v1)
641+
const queueNames = new Set();
642+
for (const suite of metadata.suites || []) {
643+
if (suite.name.includes("tasks")) {
644+
for (const func of suite.functions || []) {
645+
if (func.name && func.name.includes("Tests")) {
646+
// Function name becomes the queue name in v1
647+
queueNames.add(func.name);
648+
}
649+
}
650+
}
651+
}
652+
653+
// Delete each queue
654+
for (const queueName of queueNames) {
655+
try {
656+
this.log(` Deleting Cloud Tasks queue: ${queueName}`, "warn");
657+
658+
// Try gcloud command to delete the queue
659+
await this.exec(
660+
`gcloud tasks queues delete ${queueName} --location=${region} --project=${projectId} --quiet`,
661+
{ silent: true }
662+
);
663+
this.log(` ✅ Deleted Cloud Tasks queue: ${queueName}`);
664+
} catch (error) {
665+
// Queue might not exist or already deleted, ignore errors
666+
this.log(` ⚠️ Could not delete queue ${queueName}: ${error.message}`, "warn");
667+
}
668+
}
624669
}
625670

626671
/**
@@ -719,13 +764,76 @@ class TestRunner {
719764
}
720765
}
721766

767+
// Clean up orphaned Cloud Tasks queues
768+
await this.cleanupOrphanedCloudTasksQueues();
769+
722770
// Clean up generated directory
723771
if (existsSync(GENERATED_DIR)) {
724772
this.log(" Cleaning up generated directory...", "warn");
725773
rmSync(GENERATED_DIR, { recursive: true, force: true });
726774
}
727775
}
728776

777+
/**
778+
* Clean up orphaned Cloud Tasks queues from previous test runs
779+
*/
780+
async cleanupOrphanedCloudTasksQueues() {
781+
this.log(" Checking for orphaned Cloud Tasks queues...", "warn");
782+
783+
const projects = ["functions-integration-tests", "functions-integration-tests-v2"];
784+
const region = DEFAULT_REGION;
785+
786+
for (const projectId of projects) {
787+
this.log(` Checking Cloud Tasks queues in project: ${projectId}`, "warn");
788+
789+
try {
790+
// List all queues in the project
791+
const result = await this.exec(
792+
`gcloud tasks queues list --location=${region} --project=${projectId} --format="value(name)"`,
793+
{ silent: true }
794+
);
795+
796+
const queueNames = result.stdout
797+
.split("\n")
798+
.map((line) => line.trim())
799+
.filter((line) => line.length > 0);
800+
801+
// Find test queues (containing "Tests" and test run ID pattern)
802+
const testQueues = queueNames.filter((queueName) => {
803+
const queueId = queueName.split("/").pop(); // Extract queue ID from full path
804+
return queueId && queueId.match(/Tests.*t[a-z0-9]{7,10}/);
805+
});
806+
807+
if (testQueues.length > 0) {
808+
this.log(
809+
` Found ${testQueues.length} orphaned test queue(s) in ${projectId}. Cleaning up...`,
810+
"warn"
811+
);
812+
813+
for (const queuePath of testQueues) {
814+
try {
815+
const queueId = queuePath.split("/").pop();
816+
this.log(` Deleting orphaned queue: ${queueId}`, "warn");
817+
818+
await this.exec(
819+
`gcloud tasks queues delete ${queueId} --location=${region} --project=${projectId} --quiet`,
820+
{ silent: true }
821+
);
822+
this.log(` ✅ Deleted orphaned queue: ${queueId}`);
823+
} catch (error) {
824+
this.log(` ⚠️ Could not delete queue ${queuePath}: ${error.message}`, "warn");
825+
}
826+
}
827+
} else {
828+
this.log(` ✅ No orphaned test queues found in ${projectId}`, "success");
829+
}
830+
} catch (e) {
831+
// Project might not be accessible or Cloud Tasks API not enabled
832+
this.log(` ⚠️ Could not check queues in ${projectId}: ${e.message}`, "warn");
833+
}
834+
}
835+
}
836+
729837
/**
730838
* Run a single suite
731839
*/

0 commit comments

Comments
 (0)