Skip to content
Merged
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
37 changes: 37 additions & 0 deletions spec/v2/providers/database.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,43 @@ describe("database", () => {
},
});
});

describe("v1-compatible getters", () => {
it("should provide v1-compatible getters on the event object", async () => {
let capturedEvent: any;
const func = database.onValueWritten("/foo/{bar}/", (e) => {
capturedEvent = e;
});

const raw = {
...RAW_RTDB_EVENT,
ref: "foo/bar-value",
data: {
["@type"]: "type.googleapis.com/google.events.firebase.database.v1.ReferenceEventData",
data: { key: "old_val" },
delta: { key: "new_val" },
},
};

await func(raw as any);

expect(capturedEvent.context).to.deep.equal({
eventId: "id",
timestamp: "time",
eventType: "providers/google.firebase.database/eventTypes/ref.write",
authType: "UNAUTHENTICATED",
resource: {
service: "firebaseio.com",
name: "projects/_/instances/my-instance/refs/foo/bar-value",
},
params: { bar: "bar-value" },
});

expect(capturedEvent.change).to.be.an("object");
expect(capturedEvent.change.before.val()).to.deep.equal({ key: "old_val" });
expect(capturedEvent.change.after.val()).to.deep.equal({ key: "new_val" });
});
});
});

describe("onValueCreated", () => {
Expand Down
51 changes: 51 additions & 0 deletions spec/v2/providers/firestore.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,29 @@ describe("firestore", () => {
expect(func.run(true as any)).to.eq(2);
expect(func.__endpoint).to.deep.eq(expectedEp);
});

it("should provide v1 compat properties", () => {
const func = firestore.onOperation(firestore.deletedEventType, "foo/{bar}", (event) => {
expect(event.snapshot.data()).to.deep.eq({ hello: "delete world" });
expect(event.context).to.deep.eq({
eventId: "379ad868-5ef9-4c84-a8ba-f75f1b056663",
timestamp: "2023-03-10T18:20:43.677647Z",
eventType: "providers/cloud.firestore/eventTypes/document.delete",
resource: {
service: "firestore.googleapis.com",
name: "projects/my-project/databases/my-db/documents/foo/fGRodw71mHutZ4wGDuT8",
},
params: { bar: "fGRodw71mHutZ4wGDuT8" },
authType: undefined,
authId: undefined,
});
});

const rawEvent: firestore.RawFirestoreEvent = makeEvent(makeEncodedProtobuf(deletedProto));
rawEvent.type = firestore.deletedEventType;
rawEvent.subject = "documents/foo/fGRodw71mHutZ4wGDuT8";
return func(rawEvent as any);
});
});

describe("onChangedOperation", () => {
Expand Down Expand Up @@ -1282,5 +1305,33 @@ describe("firestore", () => {
expect(func.run(true as any)).to.eq(2);
expect(func.__endpoint).to.deep.eq(expectedEp);
});

it("should provide v1 compat properties", () => {
const func = firestore.onChangedOperation(
firestore.updatedEventType,
"foo/{bar}",
(event) => {
expect(event.change.after.data()).to.deep.eq({ hello: "new world" });
expect(event.change.before.data()).to.deep.eq({ hello: "old world" });
expect(event.context).to.deep.eq({
eventId: "379ad868-5ef9-4c84-a8ba-f75f1b056663",
timestamp: "2023-03-10T18:20:43.677647Z",
eventType: "providers/cloud.firestore/eventTypes/document.update",
resource: {
service: "firestore.googleapis.com",
name: "projects/my-project/databases/my-db/documents/foo/fGRodw71mHutZ4wGDuT8",
},
params: { bar: "fGRodw71mHutZ4wGDuT8" },
authType: undefined,
authId: undefined,
});
}
);

const rawEvent: firestore.RawFirestoreEvent = makeEvent(makeEncodedProtobuf(updatedProto));
rawEvent.type = firestore.updatedEventType;
rawEvent.subject = "documents/foo/fGRodw71mHutZ4wGDuT8";
return func(rawEvent as any);
});
});
});
46 changes: 44 additions & 2 deletions spec/v2/providers/remoteConfig.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe("onConfigUpdated", () => {
retry: false,
},
});
await expect(fn(1 as any)).to.eventually.eq(2);
await expect(fn({} as any)).to.eventually.eq(2);
});

it("should create a function with opts and a handler", async () => {
Expand Down Expand Up @@ -73,7 +73,7 @@ describe("onConfigUpdated", () => {
retry: true,
},
});
await expect(fn(1 as any)).to.eventually.eq(2);
await expect(fn({} as any)).to.eventually.eq(2);
});

it("calls init function", async () => {
Expand All @@ -92,4 +92,46 @@ describe("onConfigUpdated", () => {
await remoteConfig.onConfigUpdated(() => null)(event);
expect(hello).to.equal("world");
});

describe("v1-compatible getters", () => {
beforeEach(() => {
process.env.GCLOUD_PROJECT = "aProject";
});

afterEach(() => {
delete process.env.GCLOUD_PROJECT;
});

it("should provide v1-compatible getters on the event object", async () => {
let capturedEvent: any;
const func = remoteConfig.onConfigUpdated((e) => {
capturedEvent = e;
});

const raw = {
specversion: "1.0",
id: "id",
source: "//firebaseremoteconfig.googleapis.com/projects/aProject/config",
subject: "projects/aProject/config",
type: remoteConfig.eventType,
time: "now",
data: { versionNumber: 1, updateTime: "now" } as any,
};

await func(raw as any);

expect(capturedEvent.context).to.deep.equal({
eventId: "id",
timestamp: "now",
eventType: "google.firebase.remoteconfig.update",
resource: {
service: "firebaseremoteconfig.googleapis.com",
name: "projects/aProject/config",
},
params: {},
});

expect(capturedEvent.version).to.deep.equal({ versionNumber: 1, updateTime: "now" });
});
});
});
56 changes: 56 additions & 0 deletions spec/v2/providers/scheduler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,5 +213,61 @@ describe("schedule", () => {
await runHandler(func, req as any);
expect(hello).to.equal("world");
});

describe("v1-compatible getters", () => {
beforeEach(() => {
process.env.GCLOUD_PROJECT = "aProject";
});

afterEach(() => {
delete process.env.GCLOUD_PROJECT;
});

it("should provide a v1-compatible context", async () => {
let capturedEvent: any;
const schfn = schedule.onSchedule("* * * * *", (e) => {
capturedEvent = e;
});

const req = new MockRequest(
{},
{
"x-cloudscheduler-jobname": "my-job",
"x-cloudscheduler-scheduletime": "2023-01-01T00:00:00Z",
}
);
req.method = "POST";

await runHandler(schfn, req as any);

expect(capturedEvent.context).to.deep.equal({
eventId: "my-job",
timestamp: "2023-01-01T00:00:00Z",
eventType: "google.pubsub.topic.publish",
resource: {
service: "pubsub.googleapis.com",
name: `projects/aProject/topics/my-job`,
},
params: {},
});
});

it("should provide a default v1-compatible context when headers are missing", async () => {
let capturedEvent: any;
const schfn = schedule.onSchedule("* * * * *", (e) => {
capturedEvent = e;
});

const req = new MockRequest({}, {});
req.method = "POST";

await runHandler(schfn, req as any);

expect(capturedEvent.context.eventId).to.equal("unknown-job");
expect(capturedEvent.context.resource.name).to.equal(
"projects/aProject/topics/unknown-job"
);
});
});
});
});
39 changes: 39 additions & 0 deletions spec/v2/providers/storage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,45 @@ describe("v2/storage", () => {
await storage.onObjectArchived("bucket", () => null)(event);
expect(hello).to.equal("world");
});

describe("v1-compatible getters", () => {
it("should provide v1-compatible getters on the event object", async () => {
let capturedEvent: any;
const func = storage.onObjectArchived("bucket", (e) => {
capturedEvent = e;
});

const raw = {
specversion: "1.0",
id: "id",
source: "//storage.googleapis.com/projects/_/buckets/bucket",
subject: "file.txt",
bucket: "bucket",
type: storage.archivedEvent,
time: "now",
data: { name: "file.txt", bucket: "bucket", generation: "123" },
};

await func(raw as any);

expect(capturedEvent.context).to.deep.equal({
eventId: "id",
timestamp: "now",
eventType: "google.storage.object.archive",
resource: {
service: "storage.googleapis.com",
name: "projects/_/buckets/bucket/objects/file.txt#123",
},
params: {},
});

expect(capturedEvent.object).to.deep.equal({
name: "file.txt",
bucket: "bucket",
generation: "123",
});
});
});
});

describe("onObjectFinalized", () => {
Expand Down
44 changes: 44 additions & 0 deletions spec/v2/providers/tasks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ describe("onTaskDispatched", () => {
beforeEach(() => {
options.setGlobalOptions({});
process.env.GCLOUD_PROJECT = "aProject";
process.env.FUNCTIONS_EMULATOR = "true";
});

afterEach(() => {
delete process.env.GCLOUD_PROJECT;
delete process.env.FUNCTIONS_EMULATOR;
});

it("should return a minimal trigger/endpoint with appropriate values", () => {
Expand Down Expand Up @@ -321,4 +323,46 @@ describe("onTaskDispatched", () => {
await runHandler(func, req as any);
expect(hello).to.equal("world");
});

describe("v1-compatible getters", () => {
it("should provide v1-compatible context on the request object", async () => {
let capturedRequest: any;
const func = onTaskDispatched((req) => {
capturedRequest = req;
});

const req = new MockRequest(
{ data: "test-data" },
{
"content-type": "application/json",
"x-cloudtasks-queuename": "my-queue",
"x-cloudtasks-taskname": "my-task",
"x-cloudtasks-taskretrycount": "2",
"x-cloudtasks-taskexecutioncount": "3",
"x-cloudtasks-tasketa": "2023-01-01T00:00:00Z",
}
);
req.method = "POST";

await runHandler(func, req as any);

expect(capturedRequest.context).to.deep.equal({
queueName: "my-queue",
id: "my-task",
retryCount: 2,
executionCount: 3,
scheduledTime: "2023-01-01T00:00:00Z",
previousResponse: undefined,
retryReason: undefined,
headers: {
"content-type": "application/json",
"x-cloudtasks-queuename": "my-queue",
"x-cloudtasks-taskname": "my-task",
"x-cloudtasks-taskretrycount": "2",
"x-cloudtasks-taskexecutioncount": "3",
"x-cloudtasks-tasketa": "2023-01-01T00:00:00Z",
},
});
});
});
});
3 changes: 3 additions & 0 deletions src/common/providers/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ export function onDispatchHandler<Req = any>(
...context,
data,
};
Object.defineProperty(arg, "context", {
get: () => context,
});
// For some reason the type system isn't picking up that the handler
// is a one argument function.
await (handler as v2TaskHandler<Req>)(arg);
Expand Down
5 changes: 5 additions & 0 deletions src/v2/compat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ interface PatchedEvent {
* @param getters A map of getters to attach to the event object.
* @returns The patched CloudEvent with V1 compatibility properties.
*/
// N.B. The complex typing of U, Recorfd, ReturnType<T> etc basically says we're goingt
// to have a strongly typed object where each value is a function to something.
// Since that objectg is all the getter functions, the return type is the same object
// shape but with what that getter returns. Types that extend types, map type defintiions,
// and built-ins like ReturnType are a bit advanced. Sorry.
export function addV1Compat<T extends CloudEvent<unknown>, U extends Record<string, () => any>>(
event: T,
getters: U
Expand Down
Loading
Loading