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 authored and cabljac committed Jan 6, 2025
1 parent ed34bd6 commit 26951aa
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 82 deletions.
2 changes: 2 additions & 0 deletions firestore-bigquery-export/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ To install an extension, your project must be on the [Blaze (pay as you go) plan

* Collection path: What is the path of the collection that you would like to export? You may use `{wildcard}` notation to match a subcollection of all documents in a collection (for example: `chatrooms/{chatid}/posts`). Parent Firestore Document IDs from `{wildcards}` can be returned in `path_params` as a JSON formatted string.

* Extension Instance ID: 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.

* Enable Wildcard Column field with Parent Firestore Document IDs: If enabled, creates a column containing a JSON object of all wildcard ids from a documents path.

* Dataset ID: What ID would you like to use for your BigQuery dataset? This extension will create the dataset, if it doesn't already exist.
Expand Down
35 changes: 33 additions & 2 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 Expand Up @@ -408,15 +416,16 @@ params:
default: 3

events:
# OLD event types for backward compatibility
- type: firebase.extensions.firestore-counter.v1.onStart
description:
Occurs when a trigger has been called within the Extension, and will
include data such as the context of the trigger request.

- type: firebase.extensions.firestore-counter.v1.onSuccess
description:
Occurs when image resizing completes successfully. The event will contain
further details about specific formats and sizes.
Occurs when a task completes successfully. The event will contain further
details about specific results.

- type: firebase.extensions.firestore-counter.v1.onError
description:
Expand All @@ -427,6 +436,28 @@ events:
description:
Occurs when the function is settled. Provides no customized data other
than the context.

# NEW event types following the updated naming convention
- type: firebase.extensions.firestore-bigquery-export.v1.onStart
description:
Occurs when a trigger has been called within the Extension, and will
include data such as the context of the trigger request.

- type: firebase.extensions.firestore-bigquery-export.v1.onSuccess
description:
Occurs when a task completes successfully. The event will contain further
details about specific results.

- type: firebase.extensions.firestore-bigquery-export.v1.onError
description:
Occurs when an issue has been experienced in the Extension. This will
include any error data that has been included within the Error Exception.

- type: firebase.extensions.firestore-bigquery-export.v1.onCompletion
description:
Occurs when the function is settled. Provides no customized data other
than the context.

- type: firebase.extensions.big-query-export.v1.sync.start
description: Occurs on a firestore document write event.

Expand Down
115 changes: 61 additions & 54 deletions firestore-bigquery-export/functions/__tests__/functions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ 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", () => ({
FirestoreBigQueryEventHistoryTracker: jest.fn(() => {
return {
record: jest.fn(() => {}),
serializeData: jest.fn(() => {}),
};
}),
FirestoreBigQueryEventHistoryTracker: jest.fn(() => ({
record: jest.fn(() => {}),
serializeData: jest.fn(() => {}),
})),
ChangeType: {
DELETE: 2,
UPDATE: 1,
Expand All @@ -21,54 +19,63 @@ jest.mock("@firebaseextensions/firestore-bigquery-change-tracker", () => ({
}));

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

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

// Mock Logs
jest.mock("../src/logs", () => ({
...jest.requireActual("../src/logs"),
start: jest.fn(() =>
logger.log("Started execution of extension with configuration", config)
),
init: jest.fn(() => {}),
error: jest.fn(() => {}),
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",
DATASET_ID: "my_ds_id",
TABLE_ID: "my_id",
COLLECTION_PATH: "example",
EVENTARC_CHANNEL: "test-channel", // Mock Eventarc Channel
EXT_SELECTED_EVENTS: "onStart,onSuccess,onError,onCompletion", // Allowed event types
};

export const mockExport = (document, data) => {
const ref = require("../src/index").fsexportbigquery;
let functionsTest = functionsTestInit();
let restoreEnv;
let functionsTest;

/** Helper to Mock Export */
const mockExport = (document, data) => {
const ref = require("../src/index").fsexportbigquery;
const wrapped = functionsTest.wrap(ref);
return wrapped(document, data);
};

export const mockedFirestoreBigQueryEventHistoryTracker = () => {};

let restoreEnv;
let functionsTest = functionsTestInit();

describe("extension", () => {
beforeEach(() => {
restoreEnv = mockedEnv(defaultEnvironment);
jest.resetModules();
functionsTest = functionsTestInit();
jest.clearAllMocks();
});

afterEach(() => {
restoreEnv();
});

test("functions are exported", () => {
Expand All @@ -79,21 +86,18 @@ describe("extension", () => {
describe("functions.fsexportbigquery", () => {
let functionsConfig;

beforeEach(async () => {
jest.resetModules();
functionsTest = functionsTestInit();

beforeEach(() => {
functionsConfig = config;
});

test("functions runs with a deletion", async () => {
test("function runs with a CREATE event", async () => {
const beforeSnapshot = functionsTest.firestore.makeDocumentSnapshot(
{ foo: "bar" },
"document/path"
{}, // Empty to simulate no document
"example/doc1"
);
const afterSnapshot = functionsTest.firestore.makeDocumentSnapshot(
{ foo: "bars" },
"document/path"
{ foo: "bar" },
"example/doc1"
);

const documentChange = functionsTest.makeChange(
Expand All @@ -102,32 +106,32 @@ describe("extension", () => {
);

const callResult = await mockExport(documentChange, {
resource: {
name: "test",
},
resource: { name: "example/doc1" },
});

expect(callResult).toBeUndefined();

expect(mockConsoleLog).toBeCalledWith(
"Started execution of extension with configuration",
functionsConfig
expect.objectContaining({
backupBucketName: expect.any(String),
initialized: expect.any(Boolean),
maxDispatchesPerSecond: expect.any(Number),
maxEnqueueAttempts: expect.any(Number),
})
);

// sleep for 10 seconds
await new Promise((resolve) => setTimeout(resolve, 10000));

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

test("function runs with updated data", async () => {
test("function runs with a DELETE event", async () => {
const beforeSnapshot = functionsTest.firestore.makeDocumentSnapshot(
{ foo: "bar" },
"document/path"
"example/doc1"
);
const afterSnapshot = functionsTest.firestore.makeDocumentSnapshot(
{ foo: "bars" },
"document/path"
{}, // Empty to simulate document deletion
"example/doc1"
);

const documentChange = functionsTest.makeChange(
Expand All @@ -136,16 +140,19 @@ describe("extension", () => {
);

const callResult = await mockExport(documentChange, {
resource: {
name: "test",
},
resource: { name: "example/doc1" },
});

expect(callResult).toBeUndefined();

expect(mockConsoleLog).toBeCalledWith(
"Started execution of extension with configuration",
functionsConfig
expect.objectContaining({
backupBucketName: expect.any(String),
initialized: expect.any(Boolean),
maxDispatchesPerSecond: expect.any(Number),
maxEnqueueAttempts: expect.any(Number),
})
);

expect(mockConsoleLog).toBeCalledWith("Completed execution of extension");
Expand Down
Loading

0 comments on commit 26951aa

Please sign in to comment.