forked from firebase/firebase-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
revamp triggers end-to-end functional test (firebase#2178)
* rewrite triggers-end-to-end tests in new style * a little cleanup * use FBTOOLS_TARGET_PROJECT * link the CLI before running tests * fixing up tests * remove only * linting cleanup * save process for more safety * add better handling for starting with a custom function
- Loading branch information
Showing
9 changed files
with
520 additions
and
547 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import * as subprocess from "child_process"; | ||
|
||
export class CLIProcess { | ||
process?: subprocess.ChildProcess; | ||
|
||
constructor(private readonly name: string, private readonly workdir: string) {} | ||
|
||
start( | ||
cmd: string, | ||
project: string, | ||
additionalArgs: string[], | ||
logDoneFn?: (d: unknown) => unknown | ||
): Promise<void> { | ||
const args = [cmd, "--project", project]; | ||
|
||
if (additionalArgs) { | ||
args.push(...additionalArgs); | ||
} | ||
|
||
const p = subprocess.spawn("firebase", args, { cwd: this.workdir }); | ||
if (!p) { | ||
throw new Error("Failed to start firebase CLI"); | ||
} | ||
this.process = p; | ||
|
||
this.process.stdout.on("data", (data: unknown) => { | ||
process.stdout.write(`[${this.name} stdout] ` + data); | ||
}); | ||
|
||
this.process.stderr.on("data", (data: unknown) => { | ||
console.log(`[${this.name} stderr] ` + data); | ||
}); | ||
|
||
let started: Promise<void>; | ||
if (logDoneFn) { | ||
started = new Promise((resolve, reject) => { | ||
const customCallback = (data: unknown): void => { | ||
if (logDoneFn(data)) { | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
p.stdout.removeListener("close", customFailure); | ||
resolve(); | ||
} | ||
}; | ||
const customFailure = (): void => { | ||
p.stdout.removeListener("data", customCallback); | ||
reject(new Error("failed to resolve startup before process.stdout closed")); | ||
}; | ||
p.stdout.on("data", customCallback); | ||
p.stdout.on("close", customFailure); | ||
}); | ||
} else { | ||
started = new Promise((resolve) => { | ||
p.once("close", () => { | ||
this.process = undefined; | ||
resolve(); | ||
}); | ||
}); | ||
} | ||
|
||
return started; | ||
} | ||
|
||
stop(): Promise<void> { | ||
const p = this.process; | ||
if (!p) { | ||
return Promise.resolve(); | ||
} | ||
|
||
const stopped = new Promise((resolve) => { | ||
p.once("exit", (/* exitCode, signal */) => { | ||
this.process = undefined; | ||
resolve(); | ||
}); | ||
}).then(() => undefined); // Fixes return type. | ||
|
||
p.kill("SIGINT"); | ||
return stopped; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import * as request from "request"; | ||
|
||
import { CLIProcess } from "./cli"; | ||
|
||
const FIREBASE_PROJECT_ZONE = "us-central1"; | ||
|
||
/* | ||
* Markers this test looks for in the emulator process stdout | ||
* as one test for whether a cloud function was triggered. | ||
*/ | ||
const RTDB_FUNCTION_LOG = "========== RTDB FUNCTION =========="; | ||
const FIRESTORE_FUNCTION_LOG = "========== FIRESTORE FUNCTION =========="; | ||
const PUBSUB_FUNCTION_LOG = "========== PUBSUB FUNCTION =========="; | ||
const ALL_EMULATORS_STARTED_LOG = "All emulators started, it is now safe to connect."; | ||
|
||
interface ConnectionInfo { | ||
host: string; | ||
port: number; | ||
} | ||
|
||
export interface FrameworkOptions { | ||
emulators: { | ||
database: ConnectionInfo; | ||
firestore: ConnectionInfo; | ||
functions: ConnectionInfo; | ||
pubsub: ConnectionInfo; | ||
}; | ||
} | ||
|
||
export class TriggerEndToEndTest { | ||
rtdbEmulatorHost = "localhost"; | ||
rtdbEmulatorPort: number; | ||
firestoreEmulatorHost = "localhost"; | ||
firestoreEmulatorPort: number; | ||
functionsEmulatorHost = "localhost"; | ||
functionsEmulatorPort: number; | ||
pubsubEmulatorHost = "localhost"; | ||
pubsubEmulatorPort: number; | ||
allEmulatorsStarted = false; | ||
rtdbTriggerCount = 0; | ||
firestoreTriggerCount = 0; | ||
pubsubTriggerCount = 0; | ||
rtdbFromFirestore = false; | ||
firestoreFromRtdb = false; | ||
rtdbFromRtdb = false; | ||
firestoreFromFirestore = false; | ||
cliProcess?: CLIProcess; | ||
|
||
constructor(public project: string, private readonly workdir: string, config: FrameworkOptions) { | ||
this.rtdbEmulatorPort = config.emulators.database.port; | ||
this.firestoreEmulatorPort = config.emulators.firestore.port; | ||
this.functionsEmulatorPort = config.emulators.functions.port; | ||
this.pubsubEmulatorPort = config.emulators.pubsub.port; | ||
} | ||
|
||
/* | ||
* Check that all directions of database <-> functions <-> firestore | ||
* worked. | ||
*/ | ||
success(): boolean { | ||
return ( | ||
this.rtdbFromFirestore && | ||
this.rtdbFromRtdb && | ||
this.firestoreFromFirestore && | ||
this.firestoreFromRtdb | ||
); | ||
} | ||
|
||
startEmulators(additionalArgs: string[] = []): Promise<void> { | ||
const cli = new CLIProcess("default", this.workdir); | ||
const started = cli.start("emulators:start", this.project, additionalArgs, (data: unknown) => { | ||
if (typeof data != "string" && !Buffer.isBuffer(data)) { | ||
throw new Error(`data is not a string or buffer (${typeof data})`); | ||
} | ||
return data.includes(ALL_EMULATORS_STARTED_LOG); | ||
}); | ||
|
||
cli.process?.stdout.on("data", (data) => { | ||
if (data.includes(RTDB_FUNCTION_LOG)) { | ||
this.rtdbTriggerCount++; | ||
} | ||
if (data.includes(FIRESTORE_FUNCTION_LOG)) { | ||
this.firestoreTriggerCount++; | ||
} | ||
if (data.includes(PUBSUB_FUNCTION_LOG)) { | ||
this.pubsubTriggerCount++; | ||
} | ||
}); | ||
|
||
this.cliProcess = cli; | ||
return started; | ||
} | ||
|
||
startEmulatorsAndWait(additionalArgs: string[], done: (_: unknown) => void): void { | ||
this.startEmulators(additionalArgs).then(done); | ||
} | ||
|
||
stopEmulators(): Promise<void> { | ||
return this.cliProcess ? this.cliProcess.stop() : Promise.resolve(); | ||
} | ||
|
||
invokeHttpFunction(name: string): Promise<request.Response> { | ||
return new Promise((resolve, reject) => { | ||
const url = `http://localhost:${[ | ||
this.functionsEmulatorPort, | ||
this.project, | ||
FIREBASE_PROJECT_ZONE, | ||
name, | ||
].join("/")}`; | ||
|
||
console.log(`URL: ${url}`); | ||
const req = request.get(url); | ||
req.once("response", resolve); | ||
req.once("error", reject); | ||
}); | ||
} | ||
|
||
writeToRtdb(): Promise<request.Response> { | ||
return this.invokeHttpFunction("writeToRtdb"); | ||
} | ||
|
||
writeToFirestore(): Promise<request.Response> { | ||
return this.invokeHttpFunction("writeToFirestore"); | ||
} | ||
|
||
writeToPubsub(): Promise<request.Response> { | ||
return this.invokeHttpFunction("writeToPubsub"); | ||
} | ||
|
||
writeToScheduledPubsub(): Promise<request.Response> { | ||
return this.invokeHttpFunction("writeToScheduledPubsub"); | ||
} | ||
|
||
waitForCondition( | ||
conditionFn: () => boolean, | ||
timeout: number, | ||
callback: (err?: Error) => void | ||
): void { | ||
let elapsed = 0; | ||
const interval = 10; | ||
const id = setInterval(() => { | ||
elapsed += interval; | ||
if (elapsed > timeout) { | ||
clearInterval(id); | ||
callback(new Error(`Timed out waiting for condition: ${conditionFn.toString()}}`)); | ||
return; | ||
} | ||
|
||
if (conditionFn()) { | ||
clearInterval(id); | ||
callback(); | ||
} | ||
}, interval); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#!/bin/bash | ||
|
||
source scripts/set-default-credentials.sh | ||
|
||
npm link | ||
|
||
mocha \ | ||
--require ts-node/register \ | ||
--require source-map-support/register \ | ||
--require src/test/helpers/mocha-bootstrap.js \ | ||
--exit \ | ||
scripts/triggers-end-to-end-tests/tests.ts |
Oops, something went wrong.