Skip to content

Commit ec73433

Browse files
committed
fix(database): preserve instance param expressions in v2 RTDB triggers
1 parent 4542780 commit ec73433

2 files changed

Lines changed: 134 additions & 26 deletions

File tree

spec/v2/providers/database.spec.ts

Lines changed: 108 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@
2121
// SOFTWARE.
2222

2323
import { expect } from "chai";
24+
import * as sinon from "sinon";
2425
import { PathPattern } from "../../../src/common/utilities/path-pattern";
2526
import * as database from "../../../src/v2/providers/database";
2627
import { expectType } from "../../common/metaprogramming";
2728
import { MINIMAL_V2_ENDPOINT } from "../../fixtures";
2829
import { CloudEvent, onInit } from "../../../src/v2/core";
2930
import * as params from "../../../src/params";
31+
import * as logger from "../../../src/logger";
32+
import { stackToWire } from "../../../src/runtime/manifest";
3033

3134
const RAW_RTDB_EVENT: database.RawRTDBCloudEvent = {
3235
data: {
@@ -51,6 +54,13 @@ const TEST_RTDB_INSTANCE_ENV_VAR = "TEST_RTDB_INSTANCE_FOR_GETOPTS";
5154
const RESOLVED_RTDB_INSTANCE = "resolved-instance";
5255

5356
describe("database", () => {
57+
afterEach(() => {
58+
params.clearParams();
59+
sinon.restore();
60+
delete process.env.FUNCTIONS_CONTROL_API;
61+
delete process.env[TEST_RTDB_INSTANCE_ENV_VAR];
62+
});
63+
5464
describe("makeParams", () => {
5565
it("should make params with basic path", () => {
5666
const event: database.RawRTDBCloudEvent = {
@@ -151,19 +161,13 @@ describe("database", () => {
151161
});
152162
});
153163

154-
it("should resolve instance Expression to runtime string", () => {
155-
const prev = process.env[TEST_RTDB_INSTANCE_ENV_VAR];
156-
process.env[TEST_RTDB_INSTANCE_ENV_VAR] = RESOLVED_RTDB_INSTANCE;
157-
try {
158-
const p = params.defineString(TEST_RTDB_INSTANCE_ENV_VAR);
159-
expect(database.getOpts({ ref: "/foo", instance: p })).to.deep.include({
160-
path: "foo",
161-
instance: RESOLVED_RTDB_INSTANCE,
162-
});
163-
} finally {
164-
if (prev === undefined) delete process.env[TEST_RTDB_INSTANCE_ENV_VAR];
165-
else process.env[TEST_RTDB_INSTANCE_ENV_VAR] = prev;
166-
}
164+
it("should preserve instance Expression", () => {
165+
const p = params.defineString(TEST_RTDB_INSTANCE_ENV_VAR);
166+
167+
expect(database.getOpts({ ref: "/foo", instance: p })).to.deep.include({
168+
path: "foo",
169+
instance: p,
170+
});
167171
});
168172
});
169173

@@ -228,6 +232,37 @@ describe("database", () => {
228232
},
229233
});
230234
});
235+
236+
it("should create an endpoint with an instance Expression as a path pattern", () => {
237+
const instance = params.defineString(TEST_RTDB_INSTANCE_ENV_VAR);
238+
const ep = database.makeEndpoint(
239+
database.writtenEventType,
240+
{
241+
region: "us-central1",
242+
labels: { 1: "2" },
243+
},
244+
new PathPattern("foo/bar"),
245+
instance
246+
);
247+
248+
expect(ep).to.deep.equal({
249+
...MINIMAL_V2_ENDPOINT,
250+
platform: "gcfv2",
251+
labels: {
252+
1: "2",
253+
},
254+
region: ["us-central1"],
255+
eventTrigger: {
256+
eventType: database.writtenEventType,
257+
eventFilters: {},
258+
eventFilterPathPatterns: {
259+
ref: "foo/bar",
260+
instance,
261+
},
262+
retry: false,
263+
},
264+
});
265+
});
231266
});
232267

233268
describe("onChangedOperation", () => {
@@ -562,6 +597,66 @@ describe("database", () => {
562597
},
563598
});
564599
});
600+
601+
it("should create a function with instance Expression without deployment warning", () => {
602+
process.env.FUNCTIONS_CONTROL_API = "true";
603+
const warnSpy = sinon.spy(logger, "warn");
604+
const instance = params.defineString(TEST_RTDB_INSTANCE_ENV_VAR);
605+
606+
const func = database.onValueCreated(
607+
{
608+
ref: "/foo/{bar}/",
609+
instance,
610+
},
611+
() => 2
612+
);
613+
614+
expect(warnSpy.callCount).to.equal(0);
615+
expect(func.__endpoint.eventTrigger.eventFilters).to.deep.equal({});
616+
expect(func.__endpoint.eventTrigger.eventFilterPathPatterns).to.deep.equal({
617+
ref: "foo/{bar}",
618+
instance,
619+
});
620+
621+
const wire = stackToWire({
622+
specVersion: "v1alpha1",
623+
requiredAPIs: [],
624+
endpoints: {
625+
exprInstance: func.__endpoint,
626+
},
627+
}) as any;
628+
629+
expect(wire.endpoints.exprInstance.eventTrigger.eventFilterPathPatterns.instance).to.equal(
630+
`{{ params.${TEST_RTDB_INSTANCE_ENV_VAR} }}`
631+
);
632+
expect(warnSpy.callCount).to.equal(0);
633+
});
634+
635+
it("should resolve instance Expression at runtime", async () => {
636+
process.env[TEST_RTDB_INSTANCE_ENV_VAR] = RESOLVED_RTDB_INSTANCE;
637+
const instance = params.defineString(TEST_RTDB_INSTANCE_ENV_VAR);
638+
const valueSpy = sinon.spy(instance, "value");
639+
let capturedEvent: database.DatabaseEvent<any, { bar: string }>;
640+
641+
const func = database.onValueCreated(
642+
{
643+
ref: "/foo/{bar}/",
644+
instance,
645+
},
646+
(event) => {
647+
capturedEvent = event;
648+
}
649+
);
650+
651+
await func({
652+
...RAW_RTDB_EVENT,
653+
instance: RESOLVED_RTDB_INSTANCE,
654+
ref: "foo/bar-value",
655+
} as any);
656+
657+
expect(valueSpy.callCount).to.equal(1);
658+
expect(capturedEvent.params).to.deep.equal({ bar: "bar-value" });
659+
});
565660
});
566661

567662
describe("onValueUpdated", () => {

src/v2/providers/database.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -480,18 +480,15 @@ export function onValueDeleted<Ref extends string>(
480480
/** @internal */
481481
export function getOpts(referenceOrOpts: string | ReferenceOptions) {
482482
let path: string;
483-
let instance: string;
483+
let instance: string | Expression<string>;
484484
let opts: options.EventHandlerOptions;
485485
if (typeof referenceOrOpts === "string") {
486486
path = normalizePath(referenceOrOpts);
487487
instance = "*";
488488
opts = {};
489489
} else {
490490
path = normalizePath(referenceOrOpts.ref);
491-
instance =
492-
referenceOrOpts.instance instanceof Expression
493-
? referenceOrOpts.instance.value()
494-
: referenceOrOpts.instance || "*";
491+
instance = referenceOrOpts.instance || "*";
495492
opts = { ...referenceOrOpts };
496493
delete (opts as any).ref;
497494
delete (opts as any).instance;
@@ -601,17 +598,19 @@ export function makeEndpoint(
601598
eventType: string,
602599
opts: options.EventHandlerOptions,
603600
path: PathPattern,
604-
instance: PathPattern
601+
instance: PathPattern | Expression<string>
605602
): ManifestEndpoint {
606603
const baseOpts = options.optionsToEndpoint(options.getGlobalOptions());
607604
const specificOpts = options.optionsToEndpoint(opts);
608605

609-
const eventFilters: Record<string, string> = {};
610-
const eventFilterPathPatterns: Record<string, string> = {
606+
const eventFilters: Record<string, string | Expression<string>> = {};
607+
const eventFilterPathPatterns: Record<string, string | Expression<string>> = {
611608
// Note: Eventarc always treats ref as a path pattern
612609
ref: path.getValue(),
613610
};
614-
if (instance.hasWildcards()) {
611+
if (instance instanceof Expression) {
612+
eventFilterPathPatterns.instance = instance;
613+
} else if (instance.hasWildcards()) {
615614
eventFilterPathPatterns.instance = instance.getValue();
616615
} else {
617616
eventFilters.instance = instance.getValue();
@@ -635,6 +634,10 @@ export function makeEndpoint(
635634
};
636635
}
637636

637+
function resolveInstancePattern(instance: PathPattern | Expression<string>): PathPattern {
638+
return instance instanceof Expression ? new PathPattern(instance.value()) : instance;
639+
}
640+
638641
/** @internal */
639642
export function onChangedOperation<Ref extends string>(
640643
eventType: string,
@@ -644,13 +647,18 @@ export function onChangedOperation<Ref extends string>(
644647
const { path, instance, opts } = getOpts(referenceOrOpts);
645648

646649
const pathPattern = new PathPattern(path);
647-
const instancePattern = new PathPattern(instance);
650+
const instancePattern = instance instanceof Expression ? instance : new PathPattern(instance);
648651

649652
// wrap the handler
650653
const func = (raw: CloudEvent<unknown>) => {
651654
const event = raw as RawRTDBCloudEvent;
652655
const instanceUrl = getInstance(event);
653-
const params = makeParams(event, pathPattern, instancePattern) as unknown as ParamsOf<Ref>;
656+
const resolvedInstancePattern = resolveInstancePattern(instancePattern);
657+
const params = makeParams(
658+
event,
659+
pathPattern,
660+
resolvedInstancePattern
661+
) as unknown as ParamsOf<Ref>;
654662
const databaseEvent = makeChangedDatabaseEvent(event, instanceUrl, params);
655663

656664
const compatEvent = addV1Compat(databaseEvent, {
@@ -679,13 +687,18 @@ export function onOperation<Ref extends string>(
679687
const { path, instance, opts } = getOpts(referenceOrOpts);
680688

681689
const pathPattern = new PathPattern(path);
682-
const instancePattern = new PathPattern(instance);
690+
const instancePattern = instance instanceof Expression ? instance : new PathPattern(instance);
683691

684692
// wrap the handler
685693
const func = (raw: CloudEvent<unknown>) => {
686694
const event = raw as RawRTDBCloudEvent;
687695
const instanceUrl = getInstance(event);
688-
const params = makeParams(event, pathPattern, instancePattern) as unknown as ParamsOf<Ref>;
696+
const resolvedInstancePattern = resolveInstancePattern(instancePattern);
697+
const params = makeParams(
698+
event,
699+
pathPattern,
700+
resolvedInstancePattern
701+
) as unknown as ParamsOf<Ref>;
689702
const data = eventType === deletedEventType ? event.data.data : event.data.delta;
690703
const databaseEvent = makeDatabaseEvent(event, data, instanceUrl, params);
691704

0 commit comments

Comments
 (0)