Skip to content

Commit b9340fa

Browse files
committed
refactored approach to flags
1 parent 1dc91a0 commit b9340fa

10 files changed

+264
-87
lines changed

packages/modules/gcloud/src/datastore-emulator-container.test.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Datastore } from "@google-cloud/datastore";
2+
import { Wait } from "testcontainers";
23
import { DatastoreEmulatorContainer, StartedDatastoreEmulatorContainer } from "./datastore-emulator-container";
34

45
describe("DatastoreEmulatorContainer", { timeout: 240_000 }, () => {
@@ -25,14 +26,35 @@ describe("DatastoreEmulatorContainer", { timeout: 240_000 }, () => {
2526

2627
// }
2728

28-
it("should have default host-port flag and database-mode flag", async (ctx) => {
29+
it("should have default host-port flag and database-mode flag", async () => {
2930
const datastoreEmulatorContainer = new DatastoreEmulatorContainer();
3031

31-
const flags = datastoreEmulatorContainer.expandFlags();
32+
const flags = datastoreEmulatorContainer["flagsManager"].expandFlags();
3233

3334
expect(flags.trim()).toEqual("--host-port=0.0.0.0:8080 --database-mode=datastore-mode");
3435
});
3536

37+
it("should be able to add flags after creating container", async () => {
38+
const datastoreEmulatorContainer = new DatastoreEmulatorContainer();
39+
// clear all default flags
40+
datastoreEmulatorContainer["flagsManager"].clearFlags();
41+
42+
// add some new flags
43+
const flags = datastoreEmulatorContainer
44+
.withFlag("host-port", "0.0.0.0:8080")
45+
.withFlag("database-mode", "datastore-mode")
46+
["flagsManager"].expandFlags();
47+
48+
// check new added flags exists
49+
expect(flags.trim()).toEqual("--host-port=0.0.0.0:8080 --database-mode=datastore-mode");
50+
51+
// check that container start command uses latest flags string
52+
const startedContainer = await datastoreEmulatorContainer
53+
.withWaitStrategy(Wait.forLogMessage(/.* start --host=0.0.0.0 --port=8080 --database-mode=datastore-mode/, 1))
54+
.start();
55+
await startedContainer.stop();
56+
});
57+
3658
async function checkDatastore(datastoreEmulatorContainer: StartedDatastoreEmulatorContainer) {
3759
expect(datastoreEmulatorContainer).toBeDefined();
3860
const testProjectId = "test-project";

packages/modules/gcloud/src/datastore-emulator-container.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,47 @@
1-
import { AbstractStartedContainer, StartedTestContainer, Wait } from "testcontainers";
2-
import { GenericGCloudEmulatorContainer } from "./generic-emulator-container";
1+
import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait } from "testcontainers";
2+
import { EmulatorFlagsManager } from "./emulator-flags-manager";
33

44
const EMULATOR_PORT = 8080;
55
const CMD = `gcloud beta emulators firestore start`;
66
const DEFAULT_IMAGE = "gcr.io/google.com/cloudsdktool/cloud-sdk";
77

8-
export class DatastoreEmulatorContainer extends GenericGCloudEmulatorContainer {
8+
export class DatastoreEmulatorContainer extends GenericContainer {
9+
private readonly _flagsManager: EmulatorFlagsManager;
910
constructor(image = DEFAULT_IMAGE) {
1011
super(image);
12+
this._flagsManager = new EmulatorFlagsManager();
1113
this.withExposedPorts(EMULATOR_PORT)
1214
.withFlag("host-port", `0.0.0.0:${EMULATOR_PORT}`)
1315
.withFlag("database-mode", `datastore-mode`)
14-
.withCommand(["/bin/sh", "-c", `${CMD} ${this.expandFlags()}`])
16+
// explicitly call withCommand() fn here
17+
// it will be called implicitly inside prev withFlag() call
18+
.withCommand(["/bin/sh", "-c", this.getCmd()])
1519
.withWaitStrategy(Wait.forLogMessage(RegExp(".*running.*"), 1))
1620
.withStartupTimeout(120_000);
1721
}
1822

23+
private getCmd(): string {
24+
return `${CMD} ${this.flagsManager.expandFlags()}`;
25+
}
26+
27+
private get flagsManager() {
28+
return this._flagsManager;
29+
}
30+
31+
/**
32+
* Adds flag as argument to emulator start command.
33+
* Adding same flag name twice replaces existing flag value.
34+
* @param name flag name. Must be set to non-empty string. May optionally contain -- prefix.
35+
* @param value flag value. May be empty string.
36+
* @returns this instance for chaining.
37+
*/
38+
public withFlag(name: string, value: string) {
39+
this.flagsManager.withFlag(name, value);
40+
// we need to 'refresh' command as we add new flag
41+
this.withCommand(["/bin/sh", "-c", this.getCmd()]);
42+
return this;
43+
}
44+
1945
public override async start(): Promise<StartedDatastoreEmulatorContainer> {
2046
return new StartedDatastoreEmulatorContainer(await super.start());
2147
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { EmulatorFlagsManager } from "./emulator-flags-manager";
2+
3+
describe("EmulatorFlagsManager", () => {
4+
it("should add flag without --", async () => {
5+
const flagsManager = new EmulatorFlagsManager().withFlag("database-mode", "firestore-native");
6+
7+
const flags = flagsManager.expandFlags();
8+
9+
expect(flags.trim()).toEqual("--database-mode=firestore-native");
10+
});
11+
12+
it("should add flag with --", async () => {
13+
const flagsManager = new EmulatorFlagsManager().withFlag("--database-mode", "firestore-native");
14+
15+
const flags = flagsManager.expandFlags();
16+
17+
expect(flags.trim()).toEqual("--database-mode=firestore-native");
18+
});
19+
20+
it("should add many flags", async () => {
21+
const flagsManager = new EmulatorFlagsManager()
22+
.withFlag("database-mode", "firestore-native")
23+
.withFlag("--host-port", "0.0.0.0:8080");
24+
25+
const flags = flagsManager.expandFlags();
26+
27+
expect(flags.trim()).toEqual("--database-mode=firestore-native --host-port=0.0.0.0:8080");
28+
});
29+
30+
it("should overwrite same flag if added more than once", async () => {
31+
const flagsManager = new EmulatorFlagsManager()
32+
.withFlag("database-mode", "firestore-native")
33+
.withFlag("--database-mode", "datastore-mode");
34+
35+
const flags = flagsManager.expandFlags();
36+
37+
expect(flags.trim()).toEqual("--database-mode=datastore-mode");
38+
});
39+
40+
it("should add flag with no value", async () => {
41+
const flagsManager = new EmulatorFlagsManager().withFlag("database-mode", "").withFlag("--host-port", "");
42+
43+
const flags = flagsManager.expandFlags();
44+
45+
expect(flags.trim()).toEqual("--database-mode= --host-port=");
46+
});
47+
48+
it("should throw if flag name not set", async () => {
49+
expect(() => new EmulatorFlagsManager().withFlag("", "firestore-native")).toThrowError();
50+
});
51+
52+
it("should clear all flags added", async () => {
53+
const flagsManager = new EmulatorFlagsManager()
54+
.withFlag("database-mode", "firestore-native")
55+
.withFlag("host-port", "0.0.0.0:8080");
56+
57+
flagsManager.clearFlags();
58+
const flags = flagsManager.expandFlags();
59+
60+
expect(flags.trim()).toEqual("");
61+
});
62+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
export class EmulatorFlagsManager {
2+
private flags: { name: string; value: string }[] = [];
3+
4+
/**
5+
* Adds flag as argument to emulator start command.
6+
* Adding same flag name twice replaces existing flag value.
7+
* @param name flag name. Must be set to non-empty string. May optionally contain -- prefix.
8+
* @param value flag value. May be empty string.
9+
* @returns this instance for chaining.
10+
*/
11+
public withFlag(name: string, value: string): this {
12+
if (!name) throw new Error("Flag name must be set.");
13+
// replace flag if it already exists
14+
const idx = this.flags.findIndex((f) => f.name == this.trimFlagName(name));
15+
if (idx >= 0) this.flags = [...this.flags.slice(0, idx), ...this.flags.slice(idx + 1)];
16+
this.flags.push({ name, value });
17+
return this;
18+
}
19+
20+
private trimFlagName(name: string): string {
21+
return name?.startsWith("--") ? name.slice(2) : name;
22+
}
23+
24+
private flagToString(f: { name: string; value: string }): string {
25+
return `--${this.trimFlagName(f.name)}=${f.value}`;
26+
}
27+
28+
/**
29+
*
30+
* @returns string with all flag names and values, concatenated in same order they were added.
31+
*/
32+
public expandFlags(): string {
33+
return `${this.flags.reduce((p, c) => p + " " + this.flagToString(c), "")}`;
34+
}
35+
36+
/**
37+
* Clears all added flags.
38+
*/
39+
public clearFlags() {
40+
this.flags = [];
41+
}
42+
}

packages/modules/gcloud/src/firestore-emulator-container.test.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import * as admin from "firebase-admin";
2+
import { Wait } from "testcontainers";
23
import { FirestoreEmulatorContainer, StartedFirestoreEmulatorContainer } from "./firestore-emulator-container";
34

45
describe("FirestoreEmulatorContainer", { timeout: 240_000 }, () => {
56
afterEach(async (ctx) => {
6-
if (ctx.task.name === "should have default host-port flag") return;
7+
if (!["should work using default version", "should work using version 468.0.0"].includes(ctx.task.name)) return;
78
await admin.app().delete();
89
});
910

@@ -30,14 +31,35 @@ describe("FirestoreEmulatorContainer", { timeout: 240_000 }, () => {
3031

3132
// }
3233

33-
it("should have default host-port flag", async (ctx) => {
34+
it("should have default host-port flag", async () => {
3435
const firestoreEmulatorContainer = new FirestoreEmulatorContainer();
3536

36-
const flags = firestoreEmulatorContainer.expandFlags();
37+
const flags = firestoreEmulatorContainer["flagsManager"].expandFlags();
3738

3839
expect(flags.trim()).toEqual("--host-port=0.0.0.0:8080");
3940
});
4041

42+
it("should be able to add flags after creating container", async () => {
43+
const firestoreEmulatorContainer = new FirestoreEmulatorContainer();
44+
// clear all default flags
45+
firestoreEmulatorContainer["flagsManager"].clearFlags();
46+
47+
// add some new flags
48+
const flags = firestoreEmulatorContainer
49+
.withFlag("host-port", "0.0.0.0:8080")
50+
.withFlag("database-mode", "datastore-mode")
51+
["flagsManager"].expandFlags();
52+
53+
// check new added flags exists
54+
expect(flags.trim()).toEqual("--host-port=0.0.0.0:8080 --database-mode=datastore-mode");
55+
56+
// check that container start command uses latest flags string
57+
const startedContainer = await firestoreEmulatorContainer
58+
.withWaitStrategy(Wait.forLogMessage(/.* start --host=0.0.0.0 --port=8080 --database-mode=datastore-mode/, 1))
59+
.start();
60+
await startedContainer.stop();
61+
});
62+
4163
async function checkFirestore(firestoreEmulatorContainer: StartedFirestoreEmulatorContainer) {
4264
expect(firestoreEmulatorContainer).toBeDefined();
4365
const testProjectId = "test-project";

packages/modules/gcloud/src/firestore-emulator-container.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,46 @@
1-
import { AbstractStartedContainer, StartedTestContainer, Wait } from "testcontainers";
2-
import { GenericGCloudEmulatorContainer } from "./generic-emulator-container";
1+
import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait } from "testcontainers";
2+
import { EmulatorFlagsManager } from "./emulator-flags-manager";
33

44
const EMULATOR_PORT = 8080;
55
const CMD = `gcloud beta emulators firestore start`;
66
const DEFAULT_IMAGE = "gcr.io/google.com/cloudsdktool/cloud-sdk";
77

8-
export class FirestoreEmulatorContainer extends GenericGCloudEmulatorContainer {
8+
export class FirestoreEmulatorContainer extends GenericContainer {
9+
private readonly _flagsManager: EmulatorFlagsManager;
910
constructor(image = DEFAULT_IMAGE) {
1011
super(image);
12+
this._flagsManager = new EmulatorFlagsManager();
1113
this.withExposedPorts(EMULATOR_PORT)
1214
.withFlag("host-port", `0.0.0.0:${EMULATOR_PORT}`)
13-
.withCommand(["/bin/sh", "-c", `${CMD} ${this.expandFlags()}`])
15+
// explicitly call withCommand() fn here
16+
// it will be called implicitly inside prev withFlag() call
17+
.withCommand(["/bin/sh", "-c", this.getCmd()])
1418
.withWaitStrategy(Wait.forLogMessage(RegExp(".*running.*"), 1))
1519
.withStartupTimeout(120_000);
1620
}
1721

22+
private getCmd(): string {
23+
return `${CMD} ${this.flagsManager.expandFlags()}`;
24+
}
25+
26+
private get flagsManager() {
27+
return this._flagsManager;
28+
}
29+
30+
/**
31+
* Adds flag as argument to emulator start command.
32+
* Adding same flag name twice replaces existing flag value.
33+
* @param name flag name. Must be set to non-empty string. May optionally contain -- prefix.
34+
* @param value flag value. May be empty string.
35+
* @returns this instance for chaining.
36+
*/
37+
public withFlag(name: string, value: string) {
38+
this.flagsManager.withFlag(name, value);
39+
// we need to 'refresh' command as we add new flag
40+
this.withCommand(["/bin/sh", "-c", this.getCmd()]);
41+
return this;
42+
}
43+
1844
public override async start(): Promise<StartedFirestoreEmulatorContainer> {
1945
return new StartedFirestoreEmulatorContainer(await super.start());
2046
}

packages/modules/gcloud/src/generic-emulator-container.test.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.

packages/modules/gcloud/src/generic-emulator-container.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.

0 commit comments

Comments
 (0)