Skip to content

Commit c14805d

Browse files
Gustolandiacabljac
andauthored
docs(firestore-bigquery-export): improve cross-project IAM docs (#2261) (#2244)
Co-authored-by: Jacob Cable <32874567+cabljac@users.noreply.github.com>
1 parent 7fedc6c commit c14805d

File tree

4 files changed

+187
-83
lines changed

4 files changed

+187
-83
lines changed

firestore-bigquery-export/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ fix - do not add/update clustering if an invalid clustering field is present.
66

77
docs - improve cross-project IAM documentation
88

9+
fix - emit correct events to extension, backwardly compatible.
10+
911
## Version 0.1.56
1012

1113
feat - improve sync strategy by immediately writing to BQ, and using cloud tasks only as a last resort

firestore-bigquery-export/extension.yaml

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,15 +458,16 @@ params:
458458
default: 3
459459

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

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

471472
- type: firebase.extensions.firestore-counter.v1.onError
472473
description:
@@ -477,6 +478,28 @@ events:
477478
description:
478479
Occurs when the function is settled. Provides no customized data other
479480
than the context.
481+
482+
# NEW event types following the updated naming convention
483+
- type: firebase.extensions.firestore-bigquery-export.v1.onStart
484+
description:
485+
Occurs when a trigger has been called within the Extension, and will
486+
include data such as the context of the trigger request.
487+
488+
- type: firebase.extensions.firestore-bigquery-export.v1.onSuccess
489+
description:
490+
Occurs when a task completes successfully. The event will contain further
491+
details about specific results.
492+
493+
- type: firebase.extensions.firestore-bigquery-export.v1.onError
494+
description:
495+
Occurs when an issue has been experienced in the Extension. This will
496+
include any error data that has been included within the Error Exception.
497+
498+
- type: firebase.extensions.firestore-bigquery-export.v1.onCompletion
499+
description:
500+
Occurs when the function is settled. Provides no customized data other
501+
than the context.
502+
480503
- type: firebase.extensions.big-query-export.v1.sync.start
481504
description: Occurs on a firestore document write event.
482505

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

Lines changed: 61 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@ 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-
6-
import { mockConsoleLog } from "./__mocks__/console";
75
import config from "../src/config";
6+
import { mockConsoleLog } from "./__mocks__/console";
87

8+
// Mock Firestore BigQuery Tracker
99
jest.mock("@firebaseextensions/firestore-bigquery-change-tracker", () => ({
10-
FirestoreBigQueryEventHistoryTracker: jest.fn(() => {
11-
return {
12-
record: jest.fn(() => {}),
13-
serializeData: jest.fn(() => {}),
14-
};
15-
}),
10+
FirestoreBigQueryEventHistoryTracker: jest.fn(() => ({
11+
record: jest.fn(() => {}),
12+
serializeData: jest.fn(() => {}),
13+
})),
1614
ChangeType: {
1715
DELETE: 2,
1816
UPDATE: 1,
@@ -21,54 +19,63 @@ jest.mock("@firebaseextensions/firestore-bigquery-change-tracker", () => ({
2119
}));
2220

2321
jest.mock("firebase-admin/functions", () => ({
24-
getFunctions: () => {
25-
return { taskQueue: jest.fn() };
26-
},
22+
getFunctions: jest.fn(() => ({
23+
taskQueue: jest.fn(() => ({
24+
enqueue: jest.fn(),
25+
})),
26+
})),
2727
}));
2828

29-
jest.mock("firebase-admin/functions", () => ({
30-
getFunctions: () => {
31-
return {
32-
taskQueue: jest.fn(() => {
33-
return { enqueue: jest.fn() };
34-
}),
35-
};
36-
},
29+
// Mock firebase-admin eventarc
30+
const channelMock = { publish: jest.fn() };
31+
jest.mock("firebase-admin/eventarc", () => ({
32+
getEventarc: jest.fn(() => ({
33+
channel: jest.fn(() => channelMock),
34+
})),
3735
}));
3836

37+
// Mock Logs
3938
jest.mock("../src/logs", () => ({
4039
...jest.requireActual("../src/logs"),
4140
start: jest.fn(() =>
4241
logger.log("Started execution of extension with configuration", config)
4342
),
44-
init: jest.fn(() => {}),
45-
error: jest.fn(() => {}),
4643
complete: jest.fn(() => logger.log("Completed execution of extension")),
4744
}));
4845

46+
// Mock Console
47+
console.info = jest.fn(); // Mock console.info globally
48+
49+
// Environment Variables
4950
const defaultEnvironment = {
5051
PROJECT_ID: "fake-project",
5152
DATASET_ID: "my_ds_id",
5253
TABLE_ID: "my_id",
5354
COLLECTION_PATH: "example",
55+
EVENTARC_CHANNEL: "test-channel", // Mock Eventarc Channel
56+
EXT_SELECTED_EVENTS: "onStart,onSuccess,onError,onCompletion", // Allowed event types
5457
};
5558

56-
export const mockExport = (document, data) => {
57-
const ref = require("../src/index").fsexportbigquery;
58-
let functionsTest = functionsTestInit();
59+
let restoreEnv;
60+
let functionsTest;
5961

62+
/** Helper to Mock Export */
63+
const mockExport = (document, data) => {
64+
const ref = require("../src/index").fsexportbigquery;
6065
const wrapped = functionsTest.wrap(ref);
6166
return wrapped(document, data);
6267
};
6368

64-
export const mockedFirestoreBigQueryEventHistoryTracker = () => {};
65-
66-
let restoreEnv;
67-
let functionsTest = functionsTestInit();
68-
6969
describe("extension", () => {
7070
beforeEach(() => {
7171
restoreEnv = mockedEnv(defaultEnvironment);
72+
jest.resetModules();
73+
functionsTest = functionsTestInit();
74+
jest.clearAllMocks();
75+
});
76+
77+
afterEach(() => {
78+
restoreEnv();
7279
});
7380

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

82-
beforeEach(async () => {
83-
jest.resetModules();
84-
functionsTest = functionsTestInit();
85-
89+
beforeEach(() => {
8690
functionsConfig = config;
8791
});
8892

89-
test("functions runs with a deletion", async () => {
93+
test("function runs with a CREATE event", async () => {
9094
const beforeSnapshot = functionsTest.firestore.makeDocumentSnapshot(
91-
{ foo: "bar" },
92-
"document/path"
95+
{}, // Empty to simulate no document
96+
"example/doc1"
9397
);
9498
const afterSnapshot = functionsTest.firestore.makeDocumentSnapshot(
95-
{ foo: "bars" },
96-
"document/path"
99+
{ foo: "bar" },
100+
"example/doc1"
97101
);
98102

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

104108
const callResult = await mockExport(documentChange, {
105-
resource: {
106-
name: "test",
107-
},
109+
resource: { name: "example/doc1" },
108110
});
109111

110112
expect(callResult).toBeUndefined();
111113

112114
expect(mockConsoleLog).toBeCalledWith(
113115
"Started execution of extension with configuration",
114-
functionsConfig
116+
expect.objectContaining({
117+
backupBucketName: expect.any(String),
118+
initialized: expect.any(Boolean),
119+
maxDispatchesPerSecond: expect.any(Number),
120+
maxEnqueueAttempts: expect.any(Number),
121+
})
115122
);
116123

117-
// sleep for 10 seconds
118-
await new Promise((resolve) => setTimeout(resolve, 10000));
119-
120124
expect(mockConsoleLog).toBeCalledWith("Completed execution of extension");
121-
}, 20000);
125+
});
122126

123-
test("function runs with updated data", async () => {
127+
test("function runs with a DELETE event", async () => {
124128
const beforeSnapshot = functionsTest.firestore.makeDocumentSnapshot(
125129
{ foo: "bar" },
126-
"document/path"
130+
"example/doc1"
127131
);
128132
const afterSnapshot = functionsTest.firestore.makeDocumentSnapshot(
129-
{ foo: "bars" },
130-
"document/path"
133+
{}, // Empty to simulate document deletion
134+
"example/doc1"
131135
);
132136

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

138142
const callResult = await mockExport(documentChange, {
139-
resource: {
140-
name: "test",
141-
},
143+
resource: { name: "example/doc1" },
142144
});
143145

144146
expect(callResult).toBeUndefined();
145147

146148
expect(mockConsoleLog).toBeCalledWith(
147149
"Started execution of extension with configuration",
148-
functionsConfig
150+
expect.objectContaining({
151+
backupBucketName: expect.any(String),
152+
initialized: expect.any(Boolean),
153+
maxDispatchesPerSecond: expect.any(Number),
154+
maxEnqueueAttempts: expect.any(Number),
155+
})
149156
);
150157

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

0 commit comments

Comments
 (0)