Skip to content

Commit

Permalink
fix(firestore-bigquery-export): update event types for Eventarc compa…
Browse files Browse the repository at this point in the history
…tibility (#1981)
  • Loading branch information
Gustolandia committed Dec 28, 2024
1 parent 403950c commit 9ddae40
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 52 deletions.
8 changes: 8 additions & 0 deletions firestore-bigquery-export/extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ params:
default: posts
required: true

- param: EXT_INSTANCE_ID
label: Extension Instance ID
description: >-
What is the unique identifier for this instance of the extension? This ID
ensures that multiple instances do not interfere with one another. It is
set automatically by Firebase but can also be customized if required.
required: true

- param: WILDCARD_IDS
label: Enable Wildcard Column field with Parent Firestore Document IDs
description: >-
Expand Down
79 changes: 32 additions & 47 deletions firestore-bigquery-export/functions/__tests__/functions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import * as admin from "firebase-admin";
import { logger } from "firebase-functions";
import * as functionsTestInit from "../node_modules/firebase-functions-test";
import mockedEnv from "../node_modules/mocked-env";
import { mockConsoleLog } from "./__mocks__/console";
import config from "../src/config";
import { mockConsoleLog } from "./__mocks__/console";

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

jest.mock("firebase-admin/functions", () => ({
getFunctions: jest.fn(() => ({
taskQueue: jest.fn(() => ({
enqueue: jest.fn(),
})),
})),
}));

// Mock firebase-admin eventarc
const channelMock = { publish: jest.fn() };
jest.mock("firebase-admin/eventarc", () => ({
getEventarc: jest.fn(() => ({
channel: jest.fn(() => ({
publish: jest.fn(),
})),
channel: jest.fn(() => channelMock),
})),
}));

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

// Mock Console
console.info = jest.fn(); // Mock console.info globally

// Environment Variables
const defaultEnvironment = {
PROJECT_ID: "fake-project",
Expand All @@ -47,7 +57,7 @@ const defaultEnvironment = {
};

let restoreEnv;
let functionsTest = functionsTestInit();
let functionsTest;

/** Helper to Mock Export */
const mockExport = (document, data) => {
Expand All @@ -60,11 +70,12 @@ describe("extension", () => {
beforeEach(() => {
restoreEnv = mockedEnv(defaultEnvironment);
jest.resetModules();
functionsTest = functionsTestInit();
jest.clearAllMocks();
});

afterEach(() => {
restoreEnv();
jest.clearAllMocks();
});

test("functions are exported", () => {
Expand All @@ -79,9 +90,9 @@ describe("extension", () => {
functionsConfig = config;
});

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

expect(callResult).toBeUndefined();

// Verify Logs
expect(mockConsoleLog).toBeCalledWith(
"Started execution of extension with configuration",
functionsConfig
);
expect(mockConsoleLog).toBeCalledWith("Completed execution of extension");

// Verify Event Publishing
const eventarcMock = require("firebase-admin/eventarc").getEventarc;
const channelMock = eventarcMock().channel();

expect(channelMock.publish).toHaveBeenCalledTimes(2);
expect(channelMock.publish).toHaveBeenCalledWith(
expect.objectContaining({
type: "firebase.extensions.firestore-counter.v1.onStart",
data: expect.any(Object),
})
);
expect(channelMock.publish).toHaveBeenCalledWith(
expect.objectContaining({
type: "firebase.extensions.firestore-bigquery-export.v1.onStart",
data: expect.any(Object),
backupBucketName: expect.any(String),
initialized: expect.any(Boolean),
maxDispatchesPerSecond: expect.any(Number),
maxEnqueueAttempts: expect.any(Number),
})
);

expect(mockConsoleLog).toBeCalledWith("Completed execution of extension");
});

test("function runs with a DELETE event", async () => {
Expand All @@ -132,7 +130,7 @@ describe("extension", () => {
"example/doc1"
);
const afterSnapshot = functionsTest.firestore.makeDocumentSnapshot(
{}, // Empty data to simulate no document
{}, // Empty to simulate document deletion
"example/doc1"
);

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

expect(callResult).toBeUndefined();

// Verify Logs
expect(mockConsoleLog).toBeCalledWith(
"Started execution of extension with configuration",
functionsConfig
);
expect(mockConsoleLog).toBeCalledWith("Completed execution of extension");

// Verify Event Publishing for both old and new event types
const eventarcMock = require("firebase-admin/eventarc").getEventarc;
const channelMock = eventarcMock().channel();

expect(channelMock.publish).toHaveBeenCalledTimes(2);
expect(channelMock.publish).toHaveBeenCalledWith(
expect.objectContaining({
type: "firebase.extensions.firestore-counter.v1.onCompletion",
data: expect.any(Object),
})
);
expect(channelMock.publish).toHaveBeenCalledWith(
expect.objectContaining({
type: "firebase.extensions.firestore-bigquery-export.v1.onCompletion",
data: expect.any(Object),
backupBucketName: expect.any(String),
initialized: expect.any(Boolean),
maxDispatchesPerSecond: expect.any(Number),
maxEnqueueAttempts: expect.any(Number),
})
);

expect(mockConsoleLog).toBeCalledWith("Completed execution of extension");
});
});
});
11 changes: 6 additions & 5 deletions firestore-bigquery-export/functions/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const { getEventarc } = eventArc;
* Changing this name affects the new event type generation.
* @constant EXTENSION_NAME
*/
const EXTENSION_NAME = "firestore-bigquery-export";
const EXTENSION_NAME =
process.env.EXT_INSTANCE_ID || "firestore-bigquery-export";

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

const eventTypes = getEventTypes("onStart");

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

const eventTypes = getEventTypes("onError");

Expand Down Expand Up @@ -103,7 +104,7 @@ export const recordSuccessEvent = async ({
subject: string;
data: string | object;
}) => {
if (!eventChannel) return;
if (!eventChannel) return Promise.resolve(); // Explicitly return a resolved Promise

const eventTypes = getEventTypes("onSuccess");

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

const eventTypes = getEventTypes("onCompletion");

Expand Down

0 comments on commit 9ddae40

Please sign in to comment.