Skip to content

Commit 6ee1a34

Browse files
TheIronDevtaeold
andauthored
Update Rtdb test sdk to allow json for CloudEvent (#159)
This commit updates the test sdk api for the Database CloudEventPartial to support end-users submitting JSON that gets transformed into `DataSnapshot` instead of forcing users to import and use `Change` and `DataSnapshot` to mock out the CloudEvent resopnses. Co-authored-by: Daniel Lee <danielylee@google.com>
1 parent 7ca7e22 commit 6ee1a34

File tree

4 files changed

+167
-15
lines changed

4 files changed

+167
-15
lines changed

spec/v2.spec.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,19 @@ describe('v2', () => {
522522

523523
expect(cloudEvent.data.val()).deep.equal(dataVal);
524524
});
525+
526+
it('should accept json data', () => {
527+
const referenceOptions = {
528+
ref: 'foo/bar',
529+
instance: 'instance-1',
530+
};
531+
const cloudFn = database.onValueCreated(referenceOptions, handler);
532+
const cloudFnWrap = wrapV2(cloudFn);
533+
const dataVal = { snapshot: 'override' };
534+
const cloudEvent = cloudFnWrap({ data: dataVal }).cloudEvent;
535+
536+
expect(cloudEvent.data.val()).deep.equal(dataVal);
537+
});
525538
});
526539

527540
describe('database.onValueDeleted()', () => {
@@ -563,6 +576,19 @@ describe('v2', () => {
563576

564577
expect(cloudEvent.data.val()).deep.equal(dataVal);
565578
});
579+
580+
it('should accept json data', () => {
581+
const referenceOptions = {
582+
ref: 'foo/bar',
583+
instance: 'instance-1',
584+
};
585+
const cloudFn = database.onValueDeleted(referenceOptions, handler);
586+
const cloudFnWrap = wrapV2(cloudFn);
587+
const dataVal = { snapshot: 'override' };
588+
const cloudEvent = cloudFnWrap({ data: dataVal }).cloudEvent;
589+
590+
expect(cloudEvent.data.val()).deep.equal(dataVal);
591+
});
566592
});
567593

568594
describe('database.onValueUpdated()', () => {
@@ -610,6 +636,23 @@ describe('v2', () => {
610636
expect(cloudEvent.data.before.val()).deep.equal(beforeDataVal);
611637
expect(cloudEvent.data.after.val()).deep.equal(afterDataVal);
612638
});
639+
640+
it('should accept json data', () => {
641+
const referenceOptions = {
642+
ref: 'foo/bar',
643+
instance: 'instance-1',
644+
};
645+
const cloudFn = database.onValueUpdated(referenceOptions, handler);
646+
const cloudFnWrap = wrapV2(cloudFn);
647+
const afterDataVal = { snapshot: 'after' };
648+
const beforeDataVal = { snapshot: 'before' };
649+
const data = { before: beforeDataVal, after: afterDataVal };
650+
651+
const cloudEvent = cloudFnWrap({ data }).cloudEvent;
652+
653+
expect(cloudEvent.data.before.val()).deep.equal(beforeDataVal);
654+
expect(cloudEvent.data.after.val()).deep.equal(afterDataVal);
655+
});
613656
});
614657

615658
describe('database.onValueWritten()', () => {
@@ -657,6 +700,24 @@ describe('v2', () => {
657700
expect(cloudEvent.data.before.val()).deep.equal(beforeDataVal);
658701
expect(cloudEvent.data.after.val()).deep.equal(afterDataVal);
659702
});
703+
704+
it('should accept json data', () => {
705+
const referenceOptions = {
706+
ref: 'foo/bar',
707+
instance: 'instance-1',
708+
};
709+
const cloudFn = database.onValueWritten(referenceOptions, handler);
710+
const cloudFnWrap = wrapV2(cloudFn);
711+
const afterDataVal = { snapshot: 'after' };
712+
713+
const beforeDataVal = { snapshot: 'before' };
714+
715+
const data = { before: beforeDataVal, after: afterDataVal };
716+
const cloudEvent = cloudFnWrap({ data }).cloudEvent;
717+
718+
expect(cloudEvent.data.before.val()).deep.equal(beforeDataVal);
719+
expect(cloudEvent.data.after.val()).deep.equal(afterDataVal);
720+
});
660721
});
661722
});
662723

src/cloudevent/generate.ts

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import { CloudEvent } from 'firebase-functions/v2';
2-
import { CloudFunction } from 'firebase-functions/v2';
1+
import {
2+
CloudEvent,
3+
CloudFunction,
4+
database,
5+
pubsub,
6+
} from 'firebase-functions/v2';
37
import { LIST_OF_MOCK_CLOUD_EVENT_PARTIALS } from './mocks/partials';
4-
import { DeepPartial, MockCloudEventAbstractFactory } from './types';
8+
import { DeepPartial } from './types';
9+
import { Change } from 'firebase-functions';
510
import merge from 'ts-deepmerge';
611

712
/**
@@ -17,9 +22,7 @@ export function generateCombinedCloudEvent<
1722
cloudFunction,
1823
cloudEventPartial
1924
);
20-
return cloudEventPartial
21-
? (merge(generatedCloudEvent, cloudEventPartial) as EventType)
22-
: generatedCloudEvent;
25+
return mergeCloudEvents(generatedCloudEvent, cloudEventPartial);
2326
}
2427

2528
export function generateMockCloudEvent<EventType extends CloudEvent<unknown>>(
@@ -37,3 +40,59 @@ export function generateMockCloudEvent<EventType extends CloudEvent<unknown>>(
3740
// No matches were found
3841
return null;
3942
}
43+
44+
const IMMUTABLE_DATA_TYPES = [database.DataSnapshot, Change, pubsub.Message];
45+
46+
function mergeCloudEvents<EventType extends CloudEvent<unknown>>(
47+
generatedCloudEvent: EventType,
48+
cloudEventPartial: DeepPartial<EventType>
49+
) {
50+
/**
51+
* There are several CloudEvent.data types that can not be overridden with json.
52+
* In these circumstances, we generate the CloudEvent.data given the user supplies
53+
* in the DeepPartial<CloudEvent>.
54+
*
55+
* Because we have already extracted the user supplied data, we don't want to overwrite
56+
* the CloudEvent.data with an incompatible type.
57+
*
58+
* An example of this is a user supplying JSON for the data of the DatabaseEvents.
59+
* The returned CloudEvent should be returning DataSnapshot that uses the supplied json,
60+
* NOT the supplied JSON.
61+
*/
62+
if (shouldDeleteUserSuppliedData(generatedCloudEvent, cloudEventPartial)) {
63+
delete cloudEventPartial.data;
64+
}
65+
return cloudEventPartial
66+
? (merge(generatedCloudEvent, cloudEventPartial) as EventType)
67+
: generatedCloudEvent;
68+
}
69+
70+
function shouldDeleteUserSuppliedData<EventType extends CloudEvent<unknown>>(
71+
generatedCloudEvent: EventType,
72+
cloudEventPartial: DeepPartial<EventType>
73+
) {
74+
// Don't attempt to delete the data if there is no data.
75+
if (cloudEventPartial?.data === undefined) {
76+
return false;
77+
}
78+
// If the user intentionally provides one of the IMMUTABLE DataTypes, DON'T delete it!
79+
if (
80+
IMMUTABLE_DATA_TYPES.some((type) => cloudEventPartial?.data instanceof type)
81+
) {
82+
return false;
83+
}
84+
85+
/** If the generated CloudEvent.data is an IMMUTABLE DataTypes, then use the generated data and
86+
* delete the user supplied CloudEvent.data.
87+
*/
88+
if (
89+
IMMUTABLE_DATA_TYPES.some(
90+
(type) => generatedCloudEvent?.data instanceof type
91+
)
92+
) {
93+
return true;
94+
}
95+
96+
// Otherwise, don't delete the data and allow ts-merge to handle merging the data.
97+
return false;
98+
}

src/cloudevent/mocks/database/helpers.ts

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,46 @@ import {
66
} from '../../../providers/database';
77
import { getBaseCloudEvent } from '../helpers';
88
import { Change } from 'firebase-functions';
9+
import { makeDataSnapshot } from '../../../providers/database';
10+
11+
type ChangeLike = {
12+
before: database.DataSnapshot | object;
13+
after: database.DataSnapshot | object;
14+
};
15+
16+
function getOrCreateDataSnapshot(
17+
data: database.DataSnapshot | object,
18+
ref: string
19+
) {
20+
if (data instanceof database.DataSnapshot) {
21+
return data;
22+
}
23+
if (data instanceof Object) {
24+
return makeDataSnapshot(data, ref);
25+
}
26+
return exampleDataSnapshot(ref);
27+
}
28+
29+
function getOrCreateDataSnapshotChange(
30+
data: DeepPartial<Change<database.DataSnapshot> | ChangeLike>,
31+
ref: string
32+
) {
33+
if (data instanceof Change) {
34+
return data;
35+
}
36+
if (data instanceof Object && data?.before && data?.after) {
37+
const beforeDataSnapshot = getOrCreateDataSnapshot(data!.before, ref);
38+
const afterDataSnapshot = getOrCreateDataSnapshot(data!.after, ref);
39+
return new Change(beforeDataSnapshot, afterDataSnapshot);
40+
}
41+
return exampleDataSnapshotChange(ref);
42+
}
943

1044
export function getDatabaseSnapshotCloudEvent(
1145
cloudFunction: CloudFunction<database.DatabaseEvent<database.DataSnapshot>>,
12-
cloudEventPartial?: DeepPartial<database.DatabaseEvent<database.DataSnapshot>>
46+
cloudEventPartial?: DeepPartial<
47+
database.DatabaseEvent<database.DataSnapshot | object>
48+
>
1349
) {
1450
const {
1551
instance,
@@ -19,9 +55,7 @@ export function getDatabaseSnapshotCloudEvent(
1955
params,
2056
} = getCommonDatabaseFields(cloudFunction, cloudEventPartial);
2157

22-
const data =
23-
(cloudEventPartial?.data as database.DataSnapshot) ||
24-
exampleDataSnapshot(ref);
58+
const data = getOrCreateDataSnapshot(cloudEventPartial?.data, ref);
2559

2660
return {
2761
// Spread common fields
@@ -43,7 +77,7 @@ export function getDatabaseChangeSnapshotCloudEvent(
4377
database.DatabaseEvent<Change<database.DataSnapshot>>
4478
>,
4579
cloudEventPartial?: DeepPartial<
46-
database.DatabaseEvent<Change<database.DataSnapshot>>
80+
database.DatabaseEvent<Change<database.DataSnapshot> | ChangeLike>
4781
>
4882
): database.DatabaseEvent<Change<database.DataSnapshot>> {
4983
const {
@@ -54,9 +88,7 @@ export function getDatabaseChangeSnapshotCloudEvent(
5488
params,
5589
} = getCommonDatabaseFields(cloudFunction, cloudEventPartial);
5690

57-
const data =
58-
(cloudEventPartial?.data as Change<database.DataSnapshot>) ||
59-
exampleDataSnapshotChange(ref);
91+
const data = getOrCreateDataSnapshotChange(cloudEventPartial?.data, ref);
6092

6193
return {
6294
// Spread common fields

src/v2.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { DeepPartial } from './cloudevent/types';
2929
* It will subsequently invoke the cloud function it wraps with the provided {@link CloudEvent}
3030
*/
3131
export type WrappedV2Function<T extends CloudEvent<unknown>> = (
32-
cloudEventPartial?: DeepPartial<T>
32+
cloudEventPartial?: DeepPartial<T | object>
3333
) => any | Promise<any>;
3434

3535
/**

0 commit comments

Comments
 (0)