Skip to content

Commit 954c30c

Browse files
committed
test: improve coverage for apphosting yaml configs (#10355)
* test: improve coverage for apphosting yaml configs ### Description Implement loading, merging, storing assertions and fix environment map formatting bugs for app hosting setup. ### Scenarios Tested - Loading basic strings and merging overlapping targets * fix: use destructuring to remove variable property in toEnvMap
1 parent 188ea29 commit 954c30c

2 files changed

Lines changed: 111 additions & 49 deletions

File tree

src/apphosting/yaml.spec.ts

Lines changed: 109 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,116 @@
11
import { expect } from "chai";
2-
import { AppHostingYamlConfig } from "./yaml";
3-
4-
describe("merge", () => {
5-
it("merges incoming apphosting yaml config with precendence", () => {
6-
const apphostingYaml = AppHostingYamlConfig.empty();
7-
apphostingYaml.env = {
8-
ENV_1: { value: "env_1" },
9-
ENV_2: { value: "env_2" },
10-
SECRET: { secret: "secret_1" },
11-
};
12-
13-
const incomingAppHostingYaml = AppHostingYamlConfig.empty();
14-
incomingAppHostingYaml.env = {
15-
ENV_1: { value: "incoming_env_1" },
16-
ENV_3: { value: "incoming_env_3" },
17-
SECRET_2: { value: "incoming_secret_2" },
18-
};
19-
20-
apphostingYaml.merge(incomingAppHostingYaml);
21-
expect(apphostingYaml.env).to.deep.equal({
22-
ENV_1: { value: "incoming_env_1" },
23-
ENV_2: { value: "env_2" },
24-
ENV_3: { value: "incoming_env_3" },
25-
SECRET: { secret: "secret_1" },
26-
SECRET_2: { value: "incoming_secret_2" },
2+
import * as sinon from "sinon";
3+
import { AppHostingYamlConfig, toEnvMap, toEnvList } from "./yaml";
4+
import * as configModule from "./config";
5+
import * as utils from "../utils";
6+
import * as fsutils from "../fsutils";
7+
import { FirebaseError } from "../error";
8+
9+
describe("apphosting/yaml", () => {
10+
let fileExistsStub: sinon.SinonStub;
11+
let readFileStub: sinon.SinonStub;
12+
let storeStub: sinon.SinonStub;
13+
14+
beforeEach(() => {
15+
fileExistsStub = sinon.stub(fsutils, "fileExistsSync");
16+
readFileStub = sinon.stub(utils, "readFileFromDirectory");
17+
storeStub = sinon.stub(configModule, "store");
18+
});
19+
20+
afterEach(() => {
21+
sinon.restore();
22+
});
23+
24+
describe("loadFromFile", () => {
25+
it("should successfully load configuration with env vars", async () => {
26+
fileExistsStub.returns(true);
27+
readFileStub.resolves({ source: "env:\n - variable: FOO\n value: bar" });
28+
29+
const res = await AppHostingYamlConfig.loadFromFile("apphosting.yaml");
30+
31+
expect(res.filename).to.equal("apphosting.yaml");
32+
expect(res.env).to.deep.equal({ FOO: { value: "bar" } });
33+
});
34+
35+
it("should throw if file does not exist", async () => {
36+
fileExistsStub.returns(false);
37+
38+
await expect(AppHostingYamlConfig.loadFromFile("missing.yaml")).to.be.rejectedWith(
39+
FirebaseError,
40+
/Cannot load missing.yaml from given path/,
41+
);
42+
});
43+
44+
it("should return empty env if file contains no env", async () => {
45+
fileExistsStub.returns(true);
46+
readFileStub.resolves({ source: "runConfig:\n cpu: 2" });
47+
48+
const res = await AppHostingYamlConfig.loadFromFile("apphosting.yaml");
49+
50+
expect(res.env).to.deep.equal({});
51+
});
52+
});
53+
54+
describe("empty", () => {
55+
it("should create an empty config", () => {
56+
const res = AppHostingYamlConfig.empty();
57+
expect(res.env).to.deep.equal({});
2758
});
2859
});
2960

30-
it("conditionally allows secrets to become plaintext", () => {
31-
const apphostingYaml = AppHostingYamlConfig.empty();
32-
apphostingYaml.env = {
33-
API_KEY: { secret: "api_key" },
34-
};
35-
36-
const incomingYaml = AppHostingYamlConfig.empty();
37-
incomingYaml.env = {
38-
API_KEY: { value: "plaintext" },
39-
};
40-
41-
expect(() =>
42-
apphostingYaml.merge(incomingYaml, /* alllowSecretsToBecomePlaintext */ false),
43-
).to.throw("Cannot convert secret to plaintext in apphosting yaml");
44-
45-
expect(() =>
46-
apphostingYaml.merge(incomingYaml, /* alllowSecretsToBecomePlaintext */ true),
47-
).to.not.throw();
48-
expect(apphostingYaml.env).to.deep.equal({
49-
API_KEY: { value: "plaintext" },
61+
describe("merge", () => {
62+
it("should override variables from incoming config", () => {
63+
const base = AppHostingYamlConfig.empty();
64+
base.env = { FOO: { value: "1" }, BAR: { secret: "sec" } };
65+
66+
const other = AppHostingYamlConfig.empty();
67+
other.env = { FOO: { value: "2" }, BAZ: { value: "3" } };
68+
69+
base.merge(other, true);
70+
71+
expect(base.env).to.deep.equal({
72+
FOO: { value: "2" },
73+
BAR: { secret: "sec" },
74+
BAZ: { value: "3" },
75+
});
76+
});
77+
78+
it("should throw when a secret turns into plaintext and allowSecretsToBecomePlaintext is false", () => {
79+
const base = AppHostingYamlConfig.empty();
80+
base.env = { DB_PASS: { secret: "my-secret" } };
81+
82+
const other = AppHostingYamlConfig.empty();
83+
other.env = { DB_PASS: { value: "plaintext" } };
84+
85+
expect(() => base.merge(other, false)).to.throw(/Cannot convert secret to plaintext/);
86+
});
87+
});
88+
89+
describe("utilities", () => {
90+
it("toEnvMap", () => {
91+
const list = [{ variable: "FOO", value: "bar" }];
92+
const map = toEnvMap(list);
93+
expect(map).to.deep.equal({ FOO: { value: "bar" } });
94+
});
95+
96+
it("toEnvList", () => {
97+
const map = { FOO: { value: "bar" } };
98+
const list = toEnvList(map);
99+
expect(list).to.deep.equal([{ variable: "FOO", value: "bar" }]);
100+
});
101+
});
102+
103+
describe("upsertFile", () => {
104+
it("should parse, merge, and store successfully", async () => {
105+
fileExistsStub.returns(true);
106+
readFileStub.resolves({ source: "env:\n - variable: FOO\n value: bar" });
107+
108+
const conf = AppHostingYamlConfig.empty();
109+
conf.env = { BAZ: { value: "qux" } };
110+
111+
await conf.upsertFile("apphosting.yaml");
112+
113+
expect(storeStub).to.have.been.calledOnce;
50114
});
51115
});
52116
});

src/apphosting/yaml.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,8 @@ export class AppHostingYamlConfig {
9393
export function toEnvMap(envs: Env[]): EnvMap {
9494
return Object.fromEntries(
9595
envs.map((env) => {
96-
const variable = env.variable;
97-
const tmp = { ...env };
98-
delete (env as any).variable;
99-
return [variable, tmp];
96+
const { variable, ...rest } = env;
97+
return [variable, rest];
10098
}),
10199
);
102100
}

0 commit comments

Comments
 (0)