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
245 changes: 245 additions & 0 deletions src/deploy/apphosting/prepare.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import prepare, {
getBackendConfigs,
injectEnvVarsFromApphostingConfig,
injectAutoInitEnvVars,
injectAngularEnvVars,
} from "./prepare";
import * as localbuilds from "../../apphosting/localbuilds";
import * as managementApps from "../../management/apps";
Expand All @@ -29,6 +30,7 @@ import { Options } from "../../options";
import { AppHostingSingle } from "../../firebaseConfig";
import * as fs from "fs";
import * as fsAsync from "../../fsAsync";
import * as utils from "../../utils";

const BASE_OPTS = {
cwd: "/",
Expand Down Expand Up @@ -811,4 +813,247 @@ describe("apphosting", () => {
expect(runtimeEnv["foo"]["AUTO_VAR_1"]?.value).to.equal("auto1");
});
});

describe("injectAngularEnvVars", () => {
let existsStub: sinon.SinonStub;
let readFileSyncStub: sinon.SinonStub;

beforeEach(() => {
existsStub = fs.existsSync as sinon.SinonStub;
readFileSyncStub = sinon.stub(fs, "readFileSync");
});

it("should do nothing for non-Angular applications", async () => {
const cfg: AppHostingSingle = { backendId: "foo", rootDir: "/", ignore: [] };
const buildEnv: Record<string, EnvMap> = { foo: {} };
const runtimeEnv: Record<string, EnvMap> = { foo: {} };

existsStub.returns(false);

await injectAngularEnvVars(
cfg,
"/app-dir",
"my-project",
"us-central1",
buildEnv,
runtimeEnv,
);

expect(buildEnv["foo"]).to.be.empty;
expect(runtimeEnv["foo"]).to.be.empty;
});

it("should inject defaults for Angular applications when headers are missing", async () => {
const cfg: AppHostingSingle = { backendId: "foo", rootDir: "/", ignore: [] };
const buildEnv: Record<string, EnvMap> = { foo: {} };
const runtimeEnv: Record<string, EnvMap> = { foo: {} };

existsStub.withArgs(sinon.match("package.json")).returns(true);
readFileSyncStub.withArgs(sinon.match("package.json")).returns(
JSON.stringify({
dependencies: {
"@angular/core": "^19.0.0",
},
}),
);

await injectAngularEnvVars(
cfg,
"/app-dir",
"my-project",
"us-central1",
buildEnv,
runtimeEnv,
);

expect(runtimeEnv["foo"]["NG_TRUST_PROXY_HEADERS"]).to.deep.equal({
value: "x-forwarded-host,x-forwarded-port,x-forwarded-proto,x-forwarded-for",
availability: ["RUNTIME"],
});
expect(runtimeEnv["foo"]["NG_ALLOWED_HOSTS"]).to.deep.equal({
value:
"foo-123456789.us-central1.run.app,foo--my-project.us-central1.hosted.app,foo--my-project.web.app,foo--my-project.firebaseapp.com",
availability: ["RUNTIME"],
});
});

it("should NOT override user-defined NG_TRUST_PROXY_HEADERS if it is a subset of allowed values", async () => {
const cfg: AppHostingSingle = { backendId: "foo", rootDir: "/", ignore: [] };
const buildEnv: Record<string, EnvMap> = { foo: {} };
const runtimeEnv: Record<string, EnvMap> = {
foo: {
NG_TRUST_PROXY_HEADERS: {
value: "x-forwarded-host,x-forwarded-proto",
availability: ["RUNTIME"],
},
},
};

existsStub.withArgs(sinon.match("package.json")).returns(true);
readFileSyncStub.returns(
JSON.stringify({
dependencies: { "@angular/core": "^19.0.0" },
}),
);

const warningSpy = sinon.spy(utils, "logLabeledWarning");

await injectAngularEnvVars(
cfg,
"/app-dir",
"my-project",
"us-central1",
buildEnv,
runtimeEnv,
);

expect(runtimeEnv["foo"]["NG_TRUST_PROXY_HEADERS"]).to.deep.equal({
value: "x-forwarded-host,x-forwarded-proto",
availability: ["RUNTIME"],
});
expect(warningSpy).to.not.have.been.called;
});

it("should throw a FirebaseError if user-defined NG_TRUST_PROXY_HEADERS contains invalid headers", async () => {
const cfg: AppHostingSingle = { backendId: "foo", rootDir: "/", ignore: [] };
const buildEnv: Record<string, EnvMap> = { foo: {} };
const runtimeEnv: Record<string, EnvMap> = {
foo: {
NG_TRUST_PROXY_HEADERS: {
value: "x-forwarded-host,invalid-header",
availability: ["RUNTIME"],
},
},
};

existsStub.withArgs(sinon.match("package.json")).returns(true);
readFileSyncStub.returns(
JSON.stringify({
dependencies: { "@angular/core": "^19.0.0" },
}),
);

await expect(
injectAngularEnvVars(cfg, "/app-dir", "my-project", "us-central1", buildEnv, runtimeEnv),
).to.be.rejectedWith(
FirebaseError,
/User-defined RUNTIME environment variable NG_TRUST_PROXY_HEADERS contains invalid headers/,
);
});

it("should override user-defined NG_TRUST_PROXY_HEADERS but NOT log a warning if defined as BUILD-only variable", async () => {
const cfg: AppHostingSingle = { backendId: "foo", rootDir: "/", ignore: [] };
const buildEnv: Record<string, EnvMap> = {
foo: {
NG_TRUST_PROXY_HEADERS: {
value: "x-forwarded-host,x-forwarded-proto",
availability: ["BUILD"],
},
},
};
const runtimeEnv: Record<string, EnvMap> = { foo: {} };

existsStub.withArgs(sinon.match("package.json")).returns(true);
readFileSyncStub.returns(
JSON.stringify({
dependencies: { "@angular/core": "^19.0.0" },
}),
);

const warningSpy = sinon.spy(utils, "logLabeledWarning");

await injectAngularEnvVars(
cfg,
"/app-dir",
"my-project",
"us-central1",
buildEnv,
runtimeEnv,
);

expect(runtimeEnv["foo"]["NG_TRUST_PROXY_HEADERS"]).to.deep.equal({
value: "x-forwarded-host,x-forwarded-port,x-forwarded-proto,x-forwarded-for",
availability: ["RUNTIME"],
});
expect(buildEnv["foo"]["NG_TRUST_PROXY_HEADERS"]).to.deep.equal({
value: "x-forwarded-host,x-forwarded-proto",
availability: ["BUILD"],
});
expect(warningSpy).to.not.have.been.called;
});

it("should NOT inject default NG_ALLOWED_HOSTS if user has defined it as RUNTIME variable", async () => {
const cfg: AppHostingSingle = { backendId: "foo", rootDir: "/", ignore: [] };
const buildEnv: Record<string, EnvMap> = { foo: {} };
const runtimeEnv: Record<string, EnvMap> = {
foo: {
NG_ALLOWED_HOSTS: {
value: "MY-CUSTOM-DOMAIN.COM",
availability: ["RUNTIME"],
},
},
};

existsStub.withArgs(sinon.match("package.json")).returns(true);
readFileSyncStub.returns(
JSON.stringify({
dependencies: { "@angular/core": "^19.0.0" },
}),
);

await injectAngularEnvVars(
cfg,
"/app-dir",
"my-project",
"us-central1",
buildEnv,
runtimeEnv,
);

expect(runtimeEnv["foo"]["NG_ALLOWED_HOSTS"]).to.deep.equal({
value: "MY-CUSTOM-DOMAIN.COM",
availability: ["RUNTIME"],
});
expect(buildEnv["foo"]["NG_ALLOWED_HOSTS"]).to.be.undefined;
});

it("should inject default NG_ALLOWED_HOSTS into runtimeEnv if user defined it as a BUILD-only variable", async () => {
const cfg: AppHostingSingle = { backendId: "foo", rootDir: "/", ignore: [] };
const buildEnv: Record<string, EnvMap> = {
foo: {
NG_ALLOWED_HOSTS: {
value: "MY-CUSTOM-DOMAIN.COM,foo-123456789.us-central1.run.app,Another-Domain.com",
availability: ["BUILD"],
},
},
};
const runtimeEnv: Record<string, EnvMap> = { foo: {} };

existsStub.withArgs(sinon.match("package.json")).returns(true);
readFileSyncStub.returns(
JSON.stringify({
dependencies: { "@angular/core": "^19.0.0" },
}),
);

await injectAngularEnvVars(
cfg,
"/app-dir",
"my-project",
"us-central1",
buildEnv,
runtimeEnv,
);

expect(runtimeEnv["foo"]["NG_ALLOWED_HOSTS"]).to.deep.equal({
value:
"foo-123456789.us-central1.run.app,foo--my-project.us-central1.hosted.app,foo--my-project.web.app,foo--my-project.firebaseapp.com",
availability: ["RUNTIME"],
});
expect(buildEnv["foo"]["NG_ALLOWED_HOSTS"]).to.deep.equal({
value: "MY-CUSTOM-DOMAIN.COM,foo-123456789.us-central1.run.app,Another-Domain.com",
availability: ["BUILD"],
});
});
});
});
Loading
Loading