From b00000d49a3240761ef65ef740c7f143da9673e3 Mon Sep 17 00:00:00 2001 From: Izaak Gough Date: Thu, 24 Sep 2026 16:36:31 +0100 Subject: [PATCH 1/3] test(functions): stub the default compute service account lookup checkIam.spec.ts and fabricator.spec.ts left gce.getDefaultServiceAccount unstubbed, so every test needing the default compute account made a live request to compute.googleapis.com and raced mocha's 2s timeout. These are the checkIam timeouts currently failing on main. --- src/deploy/functions/checkIam.spec.ts | 5 +++++ src/deploy/functions/release/fabricator.spec.ts | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/deploy/functions/checkIam.spec.ts b/src/deploy/functions/checkIam.spec.ts index b1dadcef3c6..00b9900b775 100644 --- a/src/deploy/functions/checkIam.spec.ts +++ b/src/deploy/functions/checkIam.spec.ts @@ -3,10 +3,12 @@ import * as sinon from "sinon"; import * as checkIam from "./checkIam"; import * as storage from "../../gcp/storage"; import * as rm from "../../gcp/resourceManager"; +import * as gce from "../../gcp/computeEngine"; import * as backend from "./backend"; const projectId = "my-project"; const projectNumber = "123456789"; +const defaultComputeServiceAccount = `${projectNumber}-compute@developer.gserviceaccount.com`; const STORAGE_RES = { email_address: "service-123@gs-project-accounts.iam.gserviceaccount.com", @@ -30,6 +32,9 @@ describe("checkIam", () => { let setIamStub: sinon.SinonStub; beforeEach(() => { + // Unstubbed, this reaches the Compute API over the network and each test that + // needs the default service account races mocha's 2s timeout. + sinon.stub(gce, "getDefaultServiceAccount").resolves(defaultComputeServiceAccount); storageStub = sinon .stub(storage, "getServiceAccount") .throws("unexpected call to storage.getServiceAccount"); diff --git a/src/deploy/functions/release/fabricator.spec.ts b/src/deploy/functions/release/fabricator.spec.ts index 64127af797d..f255fa92054 100644 --- a/src/deploy/functions/release/fabricator.spec.ts +++ b/src/deploy/functions/release/fabricator.spec.ts @@ -27,6 +27,8 @@ import * as gce from "../../../gcp/computeEngine"; import * as iam from "../../../gcp/iam"; import * as resourcemanager from "../../../gcp/resourceManager"; +const DEFAULT_COMPUTE_SERVICE_ACCOUNT = "1234567-compute@developer.gserviceaccount.com"; + describe("Fabricator", () => { // Stub all GCP APIs to make sure this test is hermetic let gcf: sinon.SinonStubbedInstance; @@ -40,6 +42,7 @@ describe("Fabricator", () => { let tasks: sinon.SinonStubbedInstance; let services: sinon.SinonStubbedInstance; let identityPlatform: sinon.SinonStubbedInstance; + let computeEngine: sinon.SinonStubbedInstance; beforeEach(() => { gcf = sinon.stub(gcfNS); @@ -53,6 +56,7 @@ describe("Fabricator", () => { tasks = sinon.stub(cloudtasksNS); services = sinon.stub(servicesNS); identityPlatform = sinon.stub(identityPlatformNS); + computeEngine = sinon.stub(gce); gcf.functionFromEndpoint.restore(); gcfv2.functionFromEndpoint.restore(); @@ -104,6 +108,9 @@ describe("Fabricator", () => { identityPlatform.setBlockingFunctionsConfig.rejects( new Error("unexpected identityPlatform.setBlockingFunctionsConfig"), ); + // Unstubbed, this reaches the Compute API over the network and every test that + // needs the default service account races mocha's 2s timeout. + computeEngine.getDefaultServiceAccount.resolves(DEFAULT_COMPUTE_SERVICE_ACCOUNT); }); afterEach(() => { @@ -892,7 +899,7 @@ describe("Fabricator", () => { await fab.createV2Function(ep, new scraper.SourceTokenScraper()); expect(run.setInvokerCreate).to.have.been.calledWith(ep.project, "service", [ - await gce.getDefaultServiceAccount(fab.projectNumber), + DEFAULT_COMPUTE_SERVICE_ACCOUNT, ]); }); From 26ac8769beda6ca9c28d3928ca9ca717a15f525b Mon Sep 17 00:00:00 2001 From: Izaak Gough Date: Fri, 25 Sep 2026 12:25:13 +0100 Subject: [PATCH 2/3] test: derive the stubbed compute service account from its argument A stub that resolves a fixed address no longer pins that callers pass the project number to getDefaultServiceAccount. Use callsFake so the tests keep asserting that, and stub the same lookup in the apphosting secrets spec, which still reached the Compute API over the network. --- src/apphosting/secrets/index.spec.ts | 8 +++++++- src/deploy/functions/checkIam.spec.ts | 9 ++++++--- src/deploy/functions/release/fabricator.spec.ts | 8 ++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/apphosting/secrets/index.spec.ts b/src/apphosting/secrets/index.spec.ts index 68a8e40be9e..97f05b63383 100644 --- a/src/apphosting/secrets/index.spec.ts +++ b/src/apphosting/secrets/index.spec.ts @@ -28,6 +28,12 @@ describe("secrets", () => { gcsm.labels.restore(); gcsm.getIamPolicy.throws("Unexpected getIamPolicy call"); gcsm.setIamPolicy.throws("Unexpected setIamPolicy call"); + // Unstubbed, this reaches the Compute API over the network and races mocha's 2s + // timeout. The fake derives the address from its argument so the tests still pin + // that the project number is passed through. + sinon + .stub(gce, "getDefaultServiceAccount") + .callsFake((pn: string) => Promise.resolve(`${pn}-compute@developer.gserviceaccount.com`)); }); afterEach(() => { @@ -49,7 +55,7 @@ describe("secrets", () => { const backend = {} as any as apphosting.Backend; expect(await secrets.serviceAccountsForBackend("number", backend)).to.deep.equal({ buildServiceAccount: gcb.getDefaultServiceAccount("number"), - runServiceAccount: await gce.getDefaultServiceAccount("number"), + runServiceAccount: "number-compute@developer.gserviceaccount.com", }); }); }); diff --git a/src/deploy/functions/checkIam.spec.ts b/src/deploy/functions/checkIam.spec.ts index 00b9900b775..e6034b9e594 100644 --- a/src/deploy/functions/checkIam.spec.ts +++ b/src/deploy/functions/checkIam.spec.ts @@ -8,7 +8,6 @@ import * as backend from "./backend"; const projectId = "my-project"; const projectNumber = "123456789"; -const defaultComputeServiceAccount = `${projectNumber}-compute@developer.gserviceaccount.com`; const STORAGE_RES = { email_address: "service-123@gs-project-accounts.iam.gserviceaccount.com", @@ -33,8 +32,12 @@ describe("checkIam", () => { beforeEach(() => { // Unstubbed, this reaches the Compute API over the network and each test that - // needs the default service account races mocha's 2s timeout. - sinon.stub(gce, "getDefaultServiceAccount").resolves(defaultComputeServiceAccount); + // needs the default service account races mocha's 2s timeout. The fake derives + // the address from its argument so the tests still pin that checkIam passes the + // project number through. + sinon + .stub(gce, "getDefaultServiceAccount") + .callsFake((pn: string) => Promise.resolve(`${pn}-compute@developer.gserviceaccount.com`)); storageStub = sinon .stub(storage, "getServiceAccount") .throws("unexpected call to storage.getServiceAccount"); diff --git a/src/deploy/functions/release/fabricator.spec.ts b/src/deploy/functions/release/fabricator.spec.ts index f255fa92054..1940b7e2a31 100644 --- a/src/deploy/functions/release/fabricator.spec.ts +++ b/src/deploy/functions/release/fabricator.spec.ts @@ -109,8 +109,12 @@ describe("Fabricator", () => { new Error("unexpected identityPlatform.setBlockingFunctionsConfig"), ); // Unstubbed, this reaches the Compute API over the network and every test that - // needs the default service account races mocha's 2s timeout. - computeEngine.getDefaultServiceAccount.resolves(DEFAULT_COMPUTE_SERVICE_ACCOUNT); + // needs the default service account races mocha's 2s timeout. The fake derives + // the address from its argument so the tests still pin that the fabricator passes + // the project number through. + computeEngine.getDefaultServiceAccount.callsFake((pn: string) => + Promise.resolve(`${pn}-compute@developer.gserviceaccount.com`), + ); }); afterEach(() => { From 008456f9236e04760142c5eb411fd5818a8f667a Mon Sep 17 00:00:00 2001 From: Izaak Gough Date: Wed, 30 Sep 2026 11:00:11 +0100 Subject: [PATCH 3/3] test(cloudscheduler): stub the default compute service account lookup jobFromEndpoint resolves the default compute service account for v2 endpoints. The spec left that lookup unstubbed, so the five v2 tests made a live request to compute.googleapis.com and raced mocha's 2s timeout. --- src/gcp/cloudscheduler.spec.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/gcp/cloudscheduler.spec.ts b/src/gcp/cloudscheduler.spec.ts index 85e2900d29c..004e13aa2bf 100644 --- a/src/gcp/cloudscheduler.spec.ts +++ b/src/gcp/cloudscheduler.spec.ts @@ -1,10 +1,12 @@ import { expect } from "chai"; +import * as sinon from "sinon"; import nock from "../test/helpers/nock"; import { FirebaseError } from "../error"; import * as api from "../api"; import * as backend from "../deploy/functions/backend"; import * as cloudscheduler from "./cloudscheduler"; +import * as gce from "./computeEngine"; import { cloneDeep } from "../utils"; const VERSION = "v1"; @@ -157,6 +159,19 @@ describe("cloudscheduler", () => { }); describe("jobFromEndpoint", () => { + beforeEach(() => { + // Unstubbed, this reaches the Compute API over the network and each v2 endpoint + // test races mocha's 2s timeout. The fake derives the address from its argument + // so the tests still pin that jobFromEndpoint passes the project number through. + sinon + .stub(gce, "getDefaultServiceAccount") + .callsFake((pn: string) => Promise.resolve(`${pn}-compute@developer.gserviceaccount.com`)); + }); + + afterEach(() => { + sinon.verifyAndRestore(); + }); + const V1_ENDPOINT: backend.Endpoint = { platform: "gcfv1", id: "id",