Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
114 changes: 114 additions & 0 deletions spec/v2/providers/database.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@
// SOFTWARE.

import { expect } from "chai";
import * as sinon from "sinon";
import { PathPattern } from "../../../src/common/utilities/path-pattern";
import * as database from "../../../src/v2/providers/database";
import { expectType } from "../../common/metaprogramming";
import { MINIMAL_V2_ENDPOINT } from "../../fixtures";
import { CloudEvent, onInit } from "../../../src/v2/core";
import * as params from "../../../src/params";
import * as logger from "../../../src/logger";
import { stackToWire } from "../../../src/runtime/manifest";

const RAW_RTDB_EVENT: database.RawRTDBCloudEvent = {
data: {
Expand All @@ -46,7 +50,17 @@ const RAW_RTDB_EVENT: database.RawRTDBCloudEvent = {
authtype: "unauthenticated",
};

const TEST_RTDB_INSTANCE_ENV_VAR = "TEST_RTDB_INSTANCE_FOR_GETOPTS";
const RESOLVED_RTDB_INSTANCE = "resolved-instance";

describe("database", () => {
afterEach(() => {
params.clearParams();
sinon.restore();
delete process.env.FUNCTIONS_CONTROL_API;
delete process.env[TEST_RTDB_INSTANCE_ENV_VAR];
});

describe("makeParams", () => {
it("should make params with basic path", () => {
const event: database.RawRTDBCloudEvent = {
Expand Down Expand Up @@ -146,6 +160,15 @@ describe("database", () => {
},
});
});

it("should preserve instance Expression", () => {
const p = params.defineString(TEST_RTDB_INSTANCE_ENV_VAR);

expect(database.getOpts({ ref: "/foo", instance: p })).to.deep.include({
path: "foo",
instance: p,
});
});
});

describe("makeEndpoint", () => {
Expand Down Expand Up @@ -209,6 +232,37 @@ describe("database", () => {
},
});
});

it("should create an endpoint with an instance Expression as a path pattern", () => {
const instance = params.defineString(TEST_RTDB_INSTANCE_ENV_VAR);
const ep = database.makeEndpoint(
database.writtenEventType,
{
region: "us-central1",
labels: { 1: "2" },
},
new PathPattern("foo/bar"),
instance
);

expect(ep).to.deep.equal({
...MINIMAL_V2_ENDPOINT,
platform: "gcfv2",
labels: {
1: "2",
},
region: ["us-central1"],
eventTrigger: {
eventType: database.writtenEventType,
eventFilters: {},
eventFilterPathPatterns: {
ref: "foo/bar",
instance,
},
retry: false,
},
});
});
});

describe("onChangedOperation", () => {
Expand Down Expand Up @@ -543,6 +597,66 @@ describe("database", () => {
},
});
});

it("should create a function with instance Expression without deployment warning", () => {
process.env.FUNCTIONS_CONTROL_API = "true";
const warnSpy = sinon.spy(logger, "warn");
const instance = params.defineString(TEST_RTDB_INSTANCE_ENV_VAR);

const func = database.onValueCreated(
{
ref: "/foo/{bar}/",
instance,
},
() => 2
);

expect(warnSpy.callCount).to.equal(0);
expect(func.__endpoint.eventTrigger.eventFilters).to.deep.equal({});
expect(func.__endpoint.eventTrigger.eventFilterPathPatterns).to.deep.equal({
ref: "foo/{bar}",
instance,
});

const wire = stackToWire({
specVersion: "v1alpha1",
requiredAPIs: [],
endpoints: {
exprInstance: func.__endpoint,
},
}) as any;

expect(wire.endpoints.exprInstance.eventTrigger.eventFilterPathPatterns.instance).to.equal(
`{{ params.${TEST_RTDB_INSTANCE_ENV_VAR} }}`
);
expect(warnSpy.callCount).to.equal(0);
});

it("should resolve instance Expression at runtime", async () => {
process.env[TEST_RTDB_INSTANCE_ENV_VAR] = RESOLVED_RTDB_INSTANCE;
const instance = params.defineString(TEST_RTDB_INSTANCE_ENV_VAR);
const valueSpy = sinon.spy(instance, "value");
let capturedEvent: database.DatabaseEvent<any, { bar: string }>;

const func = database.onValueCreated(
{
ref: "/foo/{bar}/",
instance,
},
(event) => {
capturedEvent = event;
}
);

await func({
...RAW_RTDB_EVENT,
instance: RESOLVED_RTDB_INSTANCE,
ref: "foo/bar-value",
} as any);

expect(valueSpy.callCount).to.equal(1);
expect(capturedEvent.params).to.deep.equal({ bar: "bar-value" });
});
});

describe("onValueUpdated", () => {
Expand Down
36 changes: 26 additions & 10 deletions src/v2/providers/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export interface ReferenceOptions<Ref extends string = string> extends options.E
* Examples: 'my-instance-1', 'my-instance-*'
* Note: The capture syntax cannot be used for 'instance'.
*/
instance?: string;
instance?: string | Expression<string>;

/**
* If true, do not deploy or emulate this function.
Expand Down Expand Up @@ -480,7 +480,7 @@ export function onValueDeleted<Ref extends string>(
/** @internal */
export function getOpts(referenceOrOpts: string | ReferenceOptions) {
let path: string;
let instance: string;
let instance: string | Expression<string>;
let opts: options.EventHandlerOptions;
if (typeof referenceOrOpts === "string") {
path = normalizePath(referenceOrOpts);
Expand Down Expand Up @@ -598,17 +598,19 @@ export function makeEndpoint(
eventType: string,
opts: options.EventHandlerOptions,
path: PathPattern,
instance: PathPattern
instance: PathPattern | Expression<string>
): ManifestEndpoint {
const baseOpts = options.optionsToEndpoint(options.getGlobalOptions());
const specificOpts = options.optionsToEndpoint(opts);

const eventFilters: Record<string, string> = {};
const eventFilterPathPatterns: Record<string, string> = {
const eventFilters: Record<string, string | Expression<string>> = {};
const eventFilterPathPatterns: Record<string, string | Expression<string>> = {
// Note: Eventarc always treats ref as a path pattern
ref: path.getValue(),
};
if (instance.hasWildcards()) {
if (instance instanceof Expression) {
eventFilterPathPatterns.instance = instance;
} else if (instance.hasWildcards()) {
eventFilterPathPatterns.instance = instance.getValue();
} else {
eventFilters.instance = instance.getValue();
Expand All @@ -632,6 +634,10 @@ export function makeEndpoint(
};
}

function resolveInstancePattern(instance: PathPattern | Expression<string>): PathPattern {
return instance instanceof Expression ? new PathPattern(instance.value()) : instance;
}

/** @internal */
export function onChangedOperation<Ref extends string>(
eventType: string,
Expand All @@ -641,13 +647,18 @@ export function onChangedOperation<Ref extends string>(
const { path, instance, opts } = getOpts(referenceOrOpts);

const pathPattern = new PathPattern(path);
const instancePattern = new PathPattern(instance);
const instancePattern = instance instanceof Expression ? instance : new PathPattern(instance);

// wrap the handler
const func = (raw: CloudEvent<unknown>) => {
const event = raw as RawRTDBCloudEvent;
const instanceUrl = getInstance(event);
const params = makeParams(event, pathPattern, instancePattern) as unknown as ParamsOf<Ref>;
const resolvedInstancePattern = resolveInstancePattern(instancePattern);
const params = makeParams(
event,
pathPattern,
resolvedInstancePattern
) as unknown as ParamsOf<Ref>;
const databaseEvent = makeChangedDatabaseEvent(event, instanceUrl, params);

const compatEvent = addV1Compat(databaseEvent, {
Expand Down Expand Up @@ -676,13 +687,18 @@ export function onOperation<Ref extends string>(
const { path, instance, opts } = getOpts(referenceOrOpts);

const pathPattern = new PathPattern(path);
const instancePattern = new PathPattern(instance);
const instancePattern = instance instanceof Expression ? instance : new PathPattern(instance);

// wrap the handler
const func = (raw: CloudEvent<unknown>) => {
const event = raw as RawRTDBCloudEvent;
const instanceUrl = getInstance(event);
const params = makeParams(event, pathPattern, instancePattern) as unknown as ParamsOf<Ref>;
const resolvedInstancePattern = resolveInstancePattern(instancePattern);
const params = makeParams(
event,
pathPattern,
resolvedInstancePattern
) as unknown as ParamsOf<Ref>;
const data = eventType === deletedEventType ? event.data.data : event.data.delta;
const databaseEvent = makeDatabaseEvent(event, data, instanceUrl, params);

Expand Down
Loading