Skip to content

Commit 9ddae40

Browse files
committed
fix(firestore-bigquery-export): update event types for Eventarc compatibility (#1981)
1 parent 403950c commit 9ddae40

File tree

3 files changed

+46
-52
lines changed

3 files changed

+46
-52
lines changed

firestore-bigquery-export/extension.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ params:
193193
default: posts
194194
required: true
195195

196+
- param: EXT_INSTANCE_ID
197+
label: Extension Instance ID
198+
description: >-
199+
What is the unique identifier for this instance of the extension? This ID
200+
ensures that multiple instances do not interfere with one another. It is
201+
set automatically by Firebase but can also be customized if required.
202+
required: true
203+
196204
- param: WILDCARD_IDS
197205
label: Enable Wildcard Column field with Parent Firestore Document IDs
198206
description: >-

firestore-bigquery-export/functions/__tests__/functions.test.ts

Lines changed: 32 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import * as admin from "firebase-admin";
22
import { logger } from "firebase-functions";
33
import * as functionsTestInit from "../node_modules/firebase-functions-test";
44
import mockedEnv from "../node_modules/mocked-env";
5-
import { mockConsoleLog } from "./__mocks__/console";
65
import config from "../src/config";
6+
import { mockConsoleLog } from "./__mocks__/console";
77

88
// Mock Firestore BigQuery Tracker
99
jest.mock("@firebaseextensions/firestore-bigquery-change-tracker", () => ({
@@ -18,12 +18,19 @@ jest.mock("@firebaseextensions/firestore-bigquery-change-tracker", () => ({
1818
},
1919
}));
2020

21+
jest.mock("firebase-admin/functions", () => ({
22+
getFunctions: jest.fn(() => ({
23+
taskQueue: jest.fn(() => ({
24+
enqueue: jest.fn(),
25+
})),
26+
})),
27+
}));
28+
2129
// Mock firebase-admin eventarc
30+
const channelMock = { publish: jest.fn() };
2231
jest.mock("firebase-admin/eventarc", () => ({
2332
getEventarc: jest.fn(() => ({
24-
channel: jest.fn(() => ({
25-
publish: jest.fn(),
26-
})),
33+
channel: jest.fn(() => channelMock),
2734
})),
2835
}));
2936

@@ -36,6 +43,9 @@ jest.mock("../src/logs", () => ({
3643
complete: jest.fn(() => logger.log("Completed execution of extension")),
3744
}));
3845

46+
// Mock Console
47+
console.info = jest.fn(); // Mock console.info globally
48+
3949
// Environment Variables
4050
const defaultEnvironment = {
4151
PROJECT_ID: "fake-project",
@@ -47,7 +57,7 @@ const defaultEnvironment = {
4757
};
4858

4959
let restoreEnv;
50-
let functionsTest = functionsTestInit();
60+
let functionsTest;
5161

5262
/** Helper to Mock Export */
5363
const mockExport = (document, data) => {
@@ -60,11 +70,12 @@ describe("extension", () => {
6070
beforeEach(() => {
6171
restoreEnv = mockedEnv(defaultEnvironment);
6272
jest.resetModules();
73+
functionsTest = functionsTestInit();
74+
jest.clearAllMocks();
6375
});
6476

6577
afterEach(() => {
6678
restoreEnv();
67-
jest.clearAllMocks();
6879
});
6980

7081
test("functions are exported", () => {
@@ -79,9 +90,9 @@ describe("extension", () => {
7990
functionsConfig = config;
8091
});
8192

82-
test("function runs with a CREATE event and publishes both old and new events", async () => {
93+
test("function runs with a CREATE event", async () => {
8394
const beforeSnapshot = functionsTest.firestore.makeDocumentSnapshot(
84-
{}, // Empty data to simulate no document
95+
{}, // Empty to simulate no document
8596
"example/doc1"
8697
);
8798
const afterSnapshot = functionsTest.firestore.makeDocumentSnapshot(
@@ -100,30 +111,17 @@ describe("extension", () => {
100111

101112
expect(callResult).toBeUndefined();
102113

103-
// Verify Logs
104114
expect(mockConsoleLog).toBeCalledWith(
105115
"Started execution of extension with configuration",
106-
functionsConfig
107-
);
108-
expect(mockConsoleLog).toBeCalledWith("Completed execution of extension");
109-
110-
// Verify Event Publishing
111-
const eventarcMock = require("firebase-admin/eventarc").getEventarc;
112-
const channelMock = eventarcMock().channel();
113-
114-
expect(channelMock.publish).toHaveBeenCalledTimes(2);
115-
expect(channelMock.publish).toHaveBeenCalledWith(
116116
expect.objectContaining({
117-
type: "firebase.extensions.firestore-counter.v1.onStart",
118-
data: expect.any(Object),
119-
})
120-
);
121-
expect(channelMock.publish).toHaveBeenCalledWith(
122-
expect.objectContaining({
123-
type: "firebase.extensions.firestore-bigquery-export.v1.onStart",
124-
data: expect.any(Object),
117+
backupBucketName: expect.any(String),
118+
initialized: expect.any(Boolean),
119+
maxDispatchesPerSecond: expect.any(Number),
120+
maxEnqueueAttempts: expect.any(Number),
125121
})
126122
);
123+
124+
expect(mockConsoleLog).toBeCalledWith("Completed execution of extension");
127125
});
128126

129127
test("function runs with a DELETE event", async () => {
@@ -132,7 +130,7 @@ describe("extension", () => {
132130
"example/doc1"
133131
);
134132
const afterSnapshot = functionsTest.firestore.makeDocumentSnapshot(
135-
{}, // Empty data to simulate no document
133+
{}, // Empty to simulate document deletion
136134
"example/doc1"
137135
);
138136

@@ -147,30 +145,17 @@ describe("extension", () => {
147145

148146
expect(callResult).toBeUndefined();
149147

150-
// Verify Logs
151148
expect(mockConsoleLog).toBeCalledWith(
152149
"Started execution of extension with configuration",
153-
functionsConfig
154-
);
155-
expect(mockConsoleLog).toBeCalledWith("Completed execution of extension");
156-
157-
// Verify Event Publishing for both old and new event types
158-
const eventarcMock = require("firebase-admin/eventarc").getEventarc;
159-
const channelMock = eventarcMock().channel();
160-
161-
expect(channelMock.publish).toHaveBeenCalledTimes(2);
162-
expect(channelMock.publish).toHaveBeenCalledWith(
163150
expect.objectContaining({
164-
type: "firebase.extensions.firestore-counter.v1.onCompletion",
165-
data: expect.any(Object),
166-
})
167-
);
168-
expect(channelMock.publish).toHaveBeenCalledWith(
169-
expect.objectContaining({
170-
type: "firebase.extensions.firestore-bigquery-export.v1.onCompletion",
171-
data: expect.any(Object),
151+
backupBucketName: expect.any(String),
152+
initialized: expect.any(Boolean),
153+
maxDispatchesPerSecond: expect.any(Number),
154+
maxEnqueueAttempts: expect.any(Number),
172155
})
173156
);
157+
158+
expect(mockConsoleLog).toBeCalledWith("Completed execution of extension");
174159
});
175160
});
176161
});

firestore-bigquery-export/functions/src/events.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ const { getEventarc } = eventArc;
77
* Changing this name affects the new event type generation.
88
* @constant EXTENSION_NAME
99
*/
10-
const EXTENSION_NAME = "firestore-bigquery-export";
10+
const EXTENSION_NAME =
11+
process.env.EXT_INSTANCE_ID || "firestore-bigquery-export";
1112

1213
/**
1314
* Generates both the OLD and NEW event types to maintain backward compatibility.
@@ -49,7 +50,7 @@ export const setupEventChannel = () => {
4950
* @returns A Promise resolving when both events are published.
5051
*/
5152
export const recordStartEvent = async (data: string | object) => {
52-
if (!eventChannel) return;
53+
if (!eventChannel) return Promise.resolve(); // Explicitly return a resolved Promise
5354

5455
const eventTypes = getEventTypes("onStart");
5556

@@ -72,7 +73,7 @@ export const recordStartEvent = async (data: string | object) => {
7273
* @returns A Promise resolving when both events are published.
7374
*/
7475
export const recordErrorEvent = async (err: Error, subject?: string) => {
75-
if (!eventChannel) return;
76+
if (!eventChannel) return Promise.resolve(); // Ensure consistent return type
7677

7778
const eventTypes = getEventTypes("onError");
7879

@@ -103,7 +104,7 @@ export const recordSuccessEvent = async ({
103104
subject: string;
104105
data: string | object;
105106
}) => {
106-
if (!eventChannel) return;
107+
if (!eventChannel) return Promise.resolve(); // Explicitly return a resolved Promise
107108

108109
const eventTypes = getEventTypes("onSuccess");
109110

@@ -126,7 +127,7 @@ export const recordSuccessEvent = async ({
126127
* @returns A Promise resolving when both events are published.
127128
*/
128129
export const recordCompletionEvent = async (data: string | object) => {
129-
if (!eventChannel) return;
130+
if (!eventChannel) return Promise.resolve(); // Ensure consistent return type
130131

131132
const eventTypes = getEventTypes("onCompletion");
132133

0 commit comments

Comments
 (0)