Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
- [Fixed] Defer secret access permission granting to release phase to prevent service account 404 race conditions.
- Fixed parsing and path resolution bugs in `ext:export` options, and reverted `--extension-instance` option back to `--instance`.
- [Fixed] Increases default polling timeout for App Hosting operations and rollouts to 60 minutes.
- Fixed an issue where App Hosting deploys failed when the deploying account lacked permission to create or grant roles to the default compute service account, even when that service account already existed. (#10806)
- Improved the error shown when deploying to a Google Cloud project that does not have Firebase enabled (#10379)
- [Added] Add -f, --force option to `firebase ext:migrate`.
- [Fixed] Fix parameter type preservation and optional system parameter handling during extension updates in `firebase ext:migrate`.
55 changes: 55 additions & 0 deletions src/functionsConfig.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { expect } from "chai";
import nock from "./test/helpers/nock";

import * as functionsConfig from "./functionsConfig";
import { firebaseApiOrigin } from "./api";
import { FirebaseError } from "./error";

const FAKE_PROJECT_ID = "my-project";

describe("config.parseSetArgs", () => {
it("should throw if a reserved namespace is used", () => {
Expand Down Expand Up @@ -42,3 +47,53 @@
]);
});
});

describe("config.getFirebaseConfig", () => {
before(() => {
nock.disableNetConnect();
});

after(() => {
nock.enableNetConnect();
});

afterEach(() => {
expect(nock.isDone()).to.be.true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gemini suggests you also add nock.cleanAll(); to ensure that any unused interceptors are cleared between tests, especially if a test fails before reaching assertions.

});

it("should return the admin SDK config on success", async () => {
nock(firebaseApiOrigin())
.get(`/v1beta1/projects/${FAKE_PROJECT_ID}/adminSdkConfig`)
.reply(200, { projectId: FAKE_PROJECT_ID });

const config = await functionsConfig.getFirebaseConfig({ project: FAKE_PROJECT_ID });

expect(config).to.deep.eq({ projectId: FAKE_PROJECT_ID });
});

it("should throw a friendly error on 404 that points at projects:addfirebase", async () => {
nock(firebaseApiOrigin())
.get(`/v1beta1/projects/${FAKE_PROJECT_ID}/adminSdkConfig`)
.reply(404, { error: { message: "Requested entity was not found." } });

const err = await expect(functionsConfig.getFirebaseConfig({ project: FAKE_PROJECT_ID })).to.be

Check warning on line 79 in src/functionsConfig.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe assignment of an `any` value
.rejected;

expect(err).to.be.instanceOf(FirebaseError);
expect(err.status).to.eq(404);

Check warning on line 83 in src/functionsConfig.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe member access .status on an `any` value
expect(err.message).to.contain(`firebase projects:addfirebase ${FAKE_PROJECT_ID}`);

Check warning on line 84 in src/functionsConfig.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe member access .message on an `any` value
// getFirebaseConfig also backs functions:delete, functions:export and ext:*,
// so the message must not be phrased as a deploy failure.
expect(err.message).to.not.contain("deploy");

Check warning on line 87 in src/functionsConfig.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe member access .message on an `any` value
});

it("should rethrow non-404 errors as-is", async () => {
nock(firebaseApiOrigin())
.get(`/v1beta1/projects/${FAKE_PROJECT_ID}/adminSdkConfig`)
.reply(500, { error: { message: "Internal error" } });

await expect(functionsConfig.getFirebaseConfig({ project: FAKE_PROJECT_ID }))
.to.be.rejectedWith(FirebaseError, "Internal error")
.and.eventually.have.property("status", 500);
});
});
30 changes: 26 additions & 4 deletions src/functionsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,26 @@
firebase experiments:enable ${LEGACY_RUNTIME_CONFIG_EXPERIMENT}
`;

// adminSdkConfig 404s for any project this account can't see, so the message
// must not assert a cause. The addfirebase hint covers the Cloud-project case.
const projectNotFoundMessage = (projectId: string): string =>
`Firebase project ${clc.bold(projectId)} was not found.
Make sure the project exists and that your account has access to it.

If ${clc.bold(projectId)} is a Google Cloud project without Firebase, add Firebase to it:

firebase projects:addfirebase ${projectId}
`;

export function getFunctionsConfigDeprecationMessage(): string {

Check warning on line 53 in src/functionsConfig.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Missing JSDoc comment
return FUNCTIONS_CONFIG_DEPRECATION_MESSAGE;
}

export function logFunctionsConfigDeprecationWarning(): void {

Check warning on line 57 in src/functionsConfig.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Missing JSDoc comment
logWarningToStderr(FUNCTIONS_CONFIG_DEPRECATION_MESSAGE);
}

export function ensureLegacyRuntimeConfigCommandsEnabled(): void {

Check warning on line 61 in src/functionsConfig.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Missing JSDoc comment
if (experiments.isEnabled(LEGACY_RUNTIME_CONFIG_EXPERIMENT)) {
return;
}
Expand All @@ -72,7 +83,7 @@
configId: string,
varPath: string,
val: string | object,
): Promise<any> {

Check warning on line 86 in src/functionsConfig.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unexpected any. Specify a different type
if (configId === "" || varPath === "") {
const msg = "Invalid argument, each config value must have a 2-part key (e.g. foo.bar).";
throw new FirebaseError(msg);
Expand All @@ -80,13 +91,13 @@
return runtimeconfig.variables.set(projectId, configId, varPath, val);
}

function isReservedNamespace(id: Id) {

Check warning on line 94 in src/functionsConfig.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Missing return type on function
return RESERVED_NAMESPACES.some((reserved) => {
return id.config.toLowerCase().startsWith(reserved);
});
}

export async function ensureApi(options: any): Promise<void> {

Check warning on line 100 in src/functionsConfig.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Missing JSDoc comment
const projectId = needProjectId(options);
return ensureApiEnabled(projectId, "runtimeconfig.googleapis.com", "runtimeconfig", true);
}
Expand Down Expand Up @@ -114,10 +125,21 @@

export async function getFirebaseConfig(options: any): Promise<args.FirebaseConfig> {
const projectId = needProjectId(options);
const response = await apiClient.get<args.FirebaseConfig>(
`/v1beta1/projects/${projectId}/adminSdkConfig`,
);
return response.body;
try {
const response = await apiClient.get<args.FirebaseConfig>(
`/v1beta1/projects/${projectId}/adminSdkConfig`,
);
return response.body;
} catch (err: unknown) {
if (err instanceof FirebaseError && err.status === 404) {
throw new FirebaseError(projectNotFoundMessage(projectId), {
original: err,
exit: 1,
status: 404,
});
}
throw err;
}
}

// If you make changes to this function, run "node scripts/test-functions-config.js"
Expand Down
Loading