Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
30 changes: 30 additions & 0 deletions packages/modules/gcloud/src/datastore-emulator-container.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Datastore } from "@google-cloud/datastore";
import { Wait } from "testcontainers";
import { DatastoreEmulatorContainer, StartedDatastoreEmulatorContainer } from "./datastore-emulator-container";

describe("DatastoreEmulatorContainer", { timeout: 240_000 }, () => {
Expand All @@ -25,6 +26,35 @@ describe("DatastoreEmulatorContainer", { timeout: 240_000 }, () => {

// }

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

const flags = datastoreEmulatorContainer["flagsManager"].expandFlags();

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

it("should be able to add flags after creating container", async () => {
const datastoreEmulatorContainer = new DatastoreEmulatorContainer();
// clear all default flags
datastoreEmulatorContainer["flagsManager"].clearFlags();

// add some new flags
const flags = datastoreEmulatorContainer
.withFlag("host-port", "0.0.0.0:8080")
.withFlag("database-mode", "datastore-mode")
["flagsManager"].expandFlags();

// check new added flags exists
expect(flags.trim()).toEqual("--host-port=0.0.0.0:8080 --database-mode=datastore-mode");

// check that container start command uses latest flags string
const startedContainer = await datastoreEmulatorContainer
.withWaitStrategy(Wait.forLogMessage(/.* start --host=0.0.0.0 --port=8080 --database-mode=datastore-mode/, 1))
.start();
await startedContainer.stop();
});

async function checkDatastore(datastoreEmulatorContainer: StartedDatastoreEmulatorContainer) {
expect(datastoreEmulatorContainer).toBeDefined();
const testProjectId = "test-project";
Expand Down
33 changes: 31 additions & 2 deletions packages/modules/gcloud/src/datastore-emulator-container.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,47 @@
import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait } from "testcontainers";
import { EmulatorFlagsManager } from "./emulator-flags-manager";

const EMULATOR_PORT = 8080;
const CMD = `gcloud beta emulators firestore start --host-port 0.0.0.0:${EMULATOR_PORT} --database-mode=datastore-mode`;
const CMD = `gcloud beta emulators firestore start`;
const DEFAULT_IMAGE = "gcr.io/google.com/cloudsdktool/cloud-sdk";

export class DatastoreEmulatorContainer extends GenericContainer {
private readonly _flagsManager: EmulatorFlagsManager;
constructor(image = DEFAULT_IMAGE) {
super(image);
this._flagsManager = new EmulatorFlagsManager();
this.withExposedPorts(EMULATOR_PORT)
.withCommand(["/bin/sh", "-c", CMD])
.withFlag("host-port", `0.0.0.0:${EMULATOR_PORT}`)
.withFlag("database-mode", `datastore-mode`)
// explicitly call withCommand() fn here
// it will be called implicitly inside prev withFlag() call
.withCommand(["/bin/sh", "-c", this.getCmd()])
.withWaitStrategy(Wait.forLogMessage(RegExp(".*running.*"), 1))
.withStartupTimeout(120_000);
}

private getCmd(): string {
return `${CMD} ${this.flagsManager.expandFlags()}`;
}

private get flagsManager() {
return this._flagsManager;
}

/**
* Adds flag as argument to emulator start command.
* Adding same flag name twice replaces existing flag value.
* @param name flag name. Must be set to non-empty string. May optionally contain -- prefix.
* @param value flag value. May be empty string.
* @returns this instance for chaining.
*/
public withFlag(name: string, value: string) {
this.flagsManager.withFlag(name, value);
// we need to 'refresh' command as we add new flag
this.withCommand(["/bin/sh", "-c", this.getCmd()]);
return this;
}

public override async start(): Promise<StartedDatastoreEmulatorContainer> {
return new StartedDatastoreEmulatorContainer(await super.start());
}
Expand Down
62 changes: 62 additions & 0 deletions packages/modules/gcloud/src/emulator-flags-manager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { EmulatorFlagsManager } from "./emulator-flags-manager";

describe("EmulatorFlagsManager", () => {
it("should add flag without --", async () => {
const flagsManager = new EmulatorFlagsManager().withFlag("database-mode", "firestore-native");

const flags = flagsManager.expandFlags();

expect(flags.trim()).toEqual("--database-mode=firestore-native");
});

it("should add flag with --", async () => {
const flagsManager = new EmulatorFlagsManager().withFlag("--database-mode", "firestore-native");

const flags = flagsManager.expandFlags();

expect(flags.trim()).toEqual("--database-mode=firestore-native");
});

it("should add many flags", async () => {
const flagsManager = new EmulatorFlagsManager()
.withFlag("database-mode", "firestore-native")
.withFlag("--host-port", "0.0.0.0:8080");

const flags = flagsManager.expandFlags();

expect(flags.trim()).toEqual("--database-mode=firestore-native --host-port=0.0.0.0:8080");
});

it("should overwrite same flag if added more than once", async () => {
const flagsManager = new EmulatorFlagsManager()
.withFlag("database-mode", "firestore-native")
.withFlag("--database-mode", "datastore-mode");

const flags = flagsManager.expandFlags();

expect(flags.trim()).toEqual("--database-mode=datastore-mode");
});

it("should add flag with no value", async () => {
const flagsManager = new EmulatorFlagsManager().withFlag("database-mode", "").withFlag("--host-port", "");

const flags = flagsManager.expandFlags();

expect(flags.trim()).toEqual("--database-mode= --host-port=");
});

it("should throw if flag name not set", async () => {
expect(() => new EmulatorFlagsManager().withFlag("", "firestore-native")).toThrowError();
});

it("should clear all flags added", async () => {
const flagsManager = new EmulatorFlagsManager()
.withFlag("database-mode", "firestore-native")
.withFlag("host-port", "0.0.0.0:8080");

flagsManager.clearFlags();
const flags = flagsManager.expandFlags();

expect(flags.trim()).toEqual("");
});
});
42 changes: 42 additions & 0 deletions packages/modules/gcloud/src/emulator-flags-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export class EmulatorFlagsManager {
private flags: { name: string; value: string }[] = [];

/**
* Adds flag as argument to emulator start command.
* Adding same flag name twice replaces existing flag value.
* @param name flag name. Must be set to non-empty string. May optionally contain -- prefix.
* @param value flag value. May be empty string.
* @returns this instance for chaining.
*/
public withFlag(name: string, value: string): this {
if (!name) throw new Error("Flag name must be set.");
// replace flag if it already exists
const idx = this.flags.findIndex((f) => f.name == this.trimFlagName(name));
if (idx >= 0) this.flags = [...this.flags.slice(0, idx), ...this.flags.slice(idx + 1)];
this.flags.push({ name, value });
return this;
}

private trimFlagName(name: string): string {
return name?.startsWith("--") ? name.slice(2) : name;
}

private flagToString(f: { name: string; value: string }): string {
return `--${this.trimFlagName(f.name)}=${f.value}`;
}

/**
*
* @returns string with all flag names and values, concatenated in same order they were added.
*/
public expandFlags(): string {
return `${this.flags.reduce((p, c) => p + " " + this.flagToString(c), "")}`;
}

/**
* Clears all added flags.
*/
public clearFlags() {
this.flags = [];
}
}
33 changes: 32 additions & 1 deletion packages/modules/gcloud/src/firestore-emulator-container.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import * as admin from "firebase-admin";
import { Wait } from "testcontainers";
import { FirestoreEmulatorContainer, StartedFirestoreEmulatorContainer } from "./firestore-emulator-container";

describe("FirestoreEmulatorContainer", { timeout: 240_000 }, () => {
afterEach(async () => {
afterEach(async (ctx) => {
if (!["should work using default version", "should work using version 468.0.0"].includes(ctx.task.name)) return;
await admin.app().delete();
});

Expand All @@ -29,6 +31,35 @@ describe("FirestoreEmulatorContainer", { timeout: 240_000 }, () => {

// }

it("should have default host-port flag", async () => {
const firestoreEmulatorContainer = new FirestoreEmulatorContainer();

const flags = firestoreEmulatorContainer["flagsManager"].expandFlags();

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

it("should be able to add flags after creating container", async () => {
const firestoreEmulatorContainer = new FirestoreEmulatorContainer();
// clear all default flags
firestoreEmulatorContainer["flagsManager"].clearFlags();

// add some new flags
const flags = firestoreEmulatorContainer
.withFlag("host-port", "0.0.0.0:8080")
.withFlag("database-mode", "datastore-mode")
["flagsManager"].expandFlags();

// check new added flags exists
expect(flags.trim()).toEqual("--host-port=0.0.0.0:8080 --database-mode=datastore-mode");

// check that container start command uses latest flags string
const startedContainer = await firestoreEmulatorContainer
.withWaitStrategy(Wait.forLogMessage(/.* start --host=0.0.0.0 --port=8080 --database-mode=datastore-mode/, 1))
.start();
await startedContainer.stop();
});

async function checkFirestore(firestoreEmulatorContainer: StartedFirestoreEmulatorContainer) {
expect(firestoreEmulatorContainer).toBeDefined();
const testProjectId = "test-project";
Expand Down
32 changes: 30 additions & 2 deletions packages/modules/gcloud/src/firestore-emulator-container.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,46 @@
import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait } from "testcontainers";
import { EmulatorFlagsManager } from "./emulator-flags-manager";

const EMULATOR_PORT = 8080;
const CMD = `gcloud beta emulators firestore start --host-port 0.0.0.0:${EMULATOR_PORT}`;
const CMD = `gcloud beta emulators firestore start`;
const DEFAULT_IMAGE = "gcr.io/google.com/cloudsdktool/cloud-sdk";

export class FirestoreEmulatorContainer extends GenericContainer {
private readonly _flagsManager: EmulatorFlagsManager;
constructor(image = DEFAULT_IMAGE) {
super(image);
this._flagsManager = new EmulatorFlagsManager();
this.withExposedPorts(EMULATOR_PORT)
.withCommand(["/bin/sh", "-c", CMD])
.withFlag("host-port", `0.0.0.0:${EMULATOR_PORT}`)
// explicitly call withCommand() fn here
// it will be called implicitly inside prev withFlag() call
.withCommand(["/bin/sh", "-c", this.getCmd()])
.withWaitStrategy(Wait.forLogMessage(RegExp(".*running.*"), 1))
.withStartupTimeout(120_000);
}

private getCmd(): string {
return `${CMD} ${this.flagsManager.expandFlags()}`;
}

private get flagsManager() {
return this._flagsManager;
}

/**
* Adds flag as argument to emulator start command.
* Adding same flag name twice replaces existing flag value.
* @param name flag name. Must be set to non-empty string. May optionally contain -- prefix.
* @param value flag value. May be empty string.
* @returns this instance for chaining.
*/
public withFlag(name: string, value: string) {
this.flagsManager.withFlag(name, value);
// we need to 'refresh' command as we add new flag
this.withCommand(["/bin/sh", "-c", this.getCmd()]);
return this;
}

public override async start(): Promise<StartedFirestoreEmulatorContainer> {
return new StartedFirestoreEmulatorContainer(await super.start());
}
Expand Down
27 changes: 27 additions & 0 deletions packages/modules/gcloud/src/pubsub-emulator-container.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PubSub } from "@google-cloud/pubsub";
import { Wait } from "testcontainers";
import { PubSubEmulatorContainer, StartedPubSubEmulatorContainer } from "./pubsub-emulator-container";

describe("PubSubEmulatorContainer", { timeout: 240_000 }, () => {
Expand All @@ -10,6 +11,32 @@ describe("PubSubEmulatorContainer", { timeout: 240_000 }, () => {
await pubsubEmulatorContainer.stop();
});

it("should have default host-port flag", async () => {
const pubsubEmulatorContainer = new PubSubEmulatorContainer();

const flags = pubsubEmulatorContainer["flagsManager"].expandFlags();

expect(flags.trim()).toEqual("--host-port=0.0.0.0:8085");
});

it("should be able to add flags after creating container", async () => {
const pubsubEmulatorContainer = new PubSubEmulatorContainer();
// clear all default flags
pubsubEmulatorContainer["flagsManager"].clearFlags();

// add some new flags
const flags = pubsubEmulatorContainer.withFlag("host-port", "0.0.0.0:8081")["flagsManager"].expandFlags();

// check new added flags exists
expect(flags.trim()).toEqual("--host-port=0.0.0.0:8081");

// check that container start command uses latest flags string
const startedContainer = await pubsubEmulatorContainer
.withWaitStrategy(Wait.forLogMessage(/.* listening on 8081/, 1))
.start();
await startedContainer.stop();
});

async function checkPubSub(pubsubEmulatorContainer: StartedPubSubEmulatorContainer) {
expect(pubsubEmulatorContainer).toBeDefined();

Expand Down
40 changes: 33 additions & 7 deletions packages/modules/gcloud/src/pubsub-emulator-container.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,57 @@
import type { StartedTestContainer } from "testcontainers";
import { AbstractStartedContainer, GenericContainer, Wait } from "testcontainers";
import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait } from "testcontainers";
import { EmulatorFlagsManager } from "./emulator-flags-manager";

const EMULATOR_PORT = 8085;
const CMD = "gcloud beta emulators pubsub start --host-port 0.0.0.0:8085";
const CMD = "gcloud beta emulators pubsub start";
const DEFAULT_IMAGE = "gcr.io/google.com/cloudsdktool/google-cloud-cli";

export class PubSubEmulatorContainer extends GenericContainer {
private readonly _flagsManager: EmulatorFlagsManager;
private _projectId?: string;

constructor(image = DEFAULT_IMAGE) {
super(image);

this._flagsManager = new EmulatorFlagsManager();
this.withExposedPorts(EMULATOR_PORT)
.withFlag("host-port", `0.0.0.0:${EMULATOR_PORT}`)
.withWaitStrategy(Wait.forLogMessage(/Server started/g, 1))
.withStartupTimeout(120_000);
}

public withProjectId(projectId: string): PubSubEmulatorContainer {
private getCmd(): string {
return `${CMD} ${this.flagsManager.expandFlags()}`;
}

private get flagsManager() {
return this._flagsManager;
}

/**
* Adds flag as argument to emulator start command.
* Adding same flag name twice replaces existing flag value.
* @param name flag name. Must be set to non-empty string. May optionally contain -- prefix.
* @param value flag value. May be empty string.
* @returns this instance for chaining.
*/
public withFlag(name: string, value: string) {
this.flagsManager.withFlag(name, value);
// we need to 'refresh' command as we add new flag
this.withCommand(["/bin/sh", "-c", this.getCmd()]);
return this;
}

public withProjectId(projectId: string): this {
this._projectId = projectId;
return this;
}

public override async start(): Promise<StartedPubSubEmulatorContainer> {
// Determine the valid command-line prompt when starting the Pub/Sub emulator
const selectedProjectId = this._projectId ?? "test-project";
const commandLine = `${CMD} --project=${selectedProjectId}`;
this.withCommand(["/bin/sh", "-c", commandLine]);
this.withFlag("project", selectedProjectId)
// explicitly call withCommand() fn here
// it will be called implicitly inside prev withFlag() call
.withCommand(["/bin/sh", "-c", this.getCmd()]);

return new StartedPubSubEmulatorContainer(await super.start(), selectedProjectId);
}
Expand Down