Skip to content

Commit b5139f1

Browse files
jhuleattrussellwheatleykevinthecheungmoog16
authored
Merge pull request #485 from firebase/next
Co-authored-by: Russell Wheatley <russellwheatley85@gmail.com> Co-authored-by: Kevin Cheung <kevinthecheung@users.noreply.github.com> Co-authored-by: moog16 <moog16@users.noreply.github.com>
2 parents 3ccd5cb + 2c993d1 commit b5139f1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1290
-130
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.vscode
22
node_modules/
33
*.env
4+
!install-params.env
45
firebase-debug.log
56
.DS_Store
67
mods-test-data/key.json

delete-user-data/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Delete User Data
22

3+
**Author**: Firebase (**[https://firebase.google.com](https://firebase.google.com)**)
4+
35
**Description**: Deletes data keyed on a userId from Cloud Firestore, Realtime Database, and/or Cloud Storage when a user deletes their account.
46

57

firestore-bigquery-export/extension.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ resources:
5252
Listens for document changes in your specified Cloud Firestore collection,
5353
then exports the changes into BigQuery.
5454
properties:
55-
sourceDirectory: .
5655
location: ${param:LOCATION}
5756
runtime: nodejs10
5857
eventTrigger:

firestore-bigquery-export/firestore-bigquery-change-tracker/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"dependencies": {
2525
"@google-cloud/bigquery": "^4.7.0",
2626
"firebase-admin": "^7.1.1",
27-
"firebase-functions": "^2.2.1",
27+
"firebase-functions": "^3.9.0",
2828
"generate-schema": "^2.6.0",
2929
"inquirer": "^6.4.0",
3030
"lodash": "^4.17.14",
@@ -33,7 +33,7 @@
3333
},
3434
"devDependencies": {
3535
"@types/traverse": "^0.6.32",
36-
"typescript": "^3.4.5",
36+
"typescript": "^3.6.3",
3737
"rimraf": "^2.6.3",
3838
"nyc": "^14.0.0",
3939
"jest": "^24.9.0",

firestore-bigquery-export/firestore-bigquery-change-tracker/src/logs.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,6 @@ export const dataInsertRetried = (rowCount: number) => {
141141
);
142142
};
143143

144-
export const dataInsertRetried = (rowCount: number) => {
145-
console.log(
146-
`Retried to insert ${rowCount} row(s) of data into BigQuery (ignoring uknown columns)`
147-
);
148-
};
149-
150144
export const dataInserting = (rowCount: number) => {
151145
logger.log(`Inserting ${rowCount} row(s) of data into BigQuery`);
152146
};
Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
{
2+
"extends": "../../tsconfig.json",
23
"compilerOptions": {
3-
"lib": ["es6"],
4-
"module": "commonjs",
5-
"noImplicitReturns": true,
64
"outDir": "lib",
7-
"sourceMap": false,
8-
"target": "es6",
9-
"types": ["node", "jest", "chai"]
5+
"types": ["node", "jest", "chai"],
6+
"target": "es2018"
107
},
11-
"compileOnSave": true,
128
"include": ["src"]
139
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const mockConsoleLog = jest.spyOn(console, "log").mockImplementation();
2+
3+
export const mockConsoleError = jest
4+
.spyOn(console, "error")
5+
.mockImplementation();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as functionsTestInit from "firebase-functions-test";
2+
3+
export const snapshot = (
4+
input = { input: "hello" },
5+
path = "translations/id1"
6+
) => {
7+
let functionsTest = functionsTestInit();
8+
return functionsTest.firestore.makeDocumentSnapshot(input, path);
9+
};
10+
11+
export const mockDocumentSnapshotFactory = (documentSnapshot) => {
12+
return jest.fn().mockImplementation(() => {
13+
return {
14+
exists: true,
15+
get: documentSnapshot.get.bind(documentSnapshot),
16+
ref: { path: documentSnapshot.ref.path },
17+
};
18+
})();
19+
};
20+
21+
export const makeChange = (before, after) => {
22+
let functionsTest = functionsTestInit();
23+
return functionsTest.makeChange(before, after);
24+
};
25+
26+
export const mockFirestoreTransaction = jest.fn().mockImplementation(() => {
27+
return (transactionHandler) => {
28+
transactionHandler({
29+
update(ref, field, data) {
30+
mockFirestoreUpdate(field, data);
31+
},
32+
});
33+
};
34+
});
35+
36+
export const mockFirestoreUpdate = jest.fn();
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`extension config config loaded from environment variables 1`] = `
4+
Object {
5+
"collectionPath": undefined,
6+
"datasetId": undefined,
7+
"initialized": false,
8+
"location": "europe-west2",
9+
"tableId": undefined,
10+
}
11+
`;
12+
13+
exports[`extension config config.datasetId param exists 1`] = `
14+
Object {
15+
"default": "firestore_export",
16+
"description": "What ID would you like to use for your BigQuery dataset? This extension will create the dataset, if it doesn't already exist.",
17+
"example": "firestore_export",
18+
"label": "Dataset ID",
19+
"param": "DATASET_ID",
20+
"required": true,
21+
"type": "string",
22+
"validationErrorMessage": "BigQuery dataset IDs must be alphanumeric (plus underscores) and must be no more than 1024 characters.
23+
",
24+
"validationRegex": "^[a-zA-Z0-9_]+$",
25+
}
26+
`;
27+
28+
exports[`extension config config.tableId param exists 1`] = `
29+
Object {
30+
"default": "posts",
31+
"description": "What identifying prefix would you like to use for your table and view inside your BigQuery dataset? This extension will create the table and view, if they don't already exist.",
32+
"example": "posts",
33+
"label": "Table ID",
34+
"param": "TABLE_ID",
35+
"required": true,
36+
"type": "string",
37+
"validationErrorMessage": "BigQuery table IDs must be alphanumeric (plus underscores) and must be no more than 1024 characters.
38+
",
39+
"validationRegex": "^[a-zA-Z0-9_]+$",
40+
}
41+
`;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { readFileSync } from "fs";
2+
3+
import { resolve as pathResolve } from "path";
4+
5+
import * as yaml from "js-yaml";
6+
import mockedEnv from "mocked-env";
7+
import config from "../src/config";
8+
9+
let restoreEnv;
10+
let extensionYaml;
11+
let extensionParams;
12+
13+
const environment = {
14+
LOCATION: "us-central1",
15+
DATASET_ID: "my_dataset",
16+
TABLE_ID: "my_table",
17+
};
18+
19+
describe("extension config", () => {
20+
beforeAll(() => {
21+
extensionYaml = yaml.safeLoad(
22+
readFileSync(pathResolve(__dirname, "../../extension.yaml"), "utf8")
23+
);
24+
25+
extensionParams = extensionYaml.params.reduce((obj, param) => {
26+
obj[param.param] = param;
27+
return obj;
28+
}, {});
29+
});
30+
31+
beforeEach(() => {
32+
restoreEnv = mockedEnv(environment);
33+
});
34+
35+
afterEach(() => restoreEnv());
36+
37+
test("config loaded from environment variables", () => {
38+
const functionsConfig = config;
39+
40+
expect(functionsConfig).toMatchSnapshot({});
41+
});
42+
43+
// DATASET_ID
44+
describe("config.datasetId", () => {
45+
test("param exists", () => {
46+
const extensionParam = extensionParams["DATASET_ID"];
47+
expect(extensionParam).toMatchSnapshot();
48+
});
49+
50+
describe("validationRegex", () => {
51+
test("does not allow empty strings", () => {
52+
const { validationRegex } = extensionParams["DATASET_ID"];
53+
expect(Boolean("".match(new RegExp(validationRegex)))).toBeFalsy();
54+
});
55+
56+
test("does not allow spaces", () => {
57+
const { validationRegex } = extensionParams["DATASET_ID"];
58+
expect(
59+
Boolean("foo bar,".match(new RegExp(validationRegex)))
60+
).toBeFalsy();
61+
});
62+
63+
test("allows a alphanumeric underscore ids", () => {
64+
const { validationRegex } = extensionParams["DATASET_ID"];
65+
expect(
66+
Boolean("my_dataset".match(new RegExp(validationRegex)))
67+
).toBeTruthy();
68+
});
69+
});
70+
});
71+
72+
// TABLE_ID
73+
describe("config.tableId", () => {
74+
test("param exists", () => {
75+
const extensionParam = extensionParams["TABLE_ID"];
76+
expect(extensionParam).toMatchSnapshot();
77+
});
78+
79+
describe("validationRegex", () => {
80+
test("does not allow empty strings", () => {
81+
const { validationRegex } = extensionParams["TABLE_ID"];
82+
expect(Boolean("".match(new RegExp(validationRegex)))).toBeFalsy();
83+
});
84+
test("does not allow spaces", () => {
85+
const { validationRegex } = extensionParams["TABLE_ID"];
86+
expect(
87+
Boolean("foo bar,".match(new RegExp(validationRegex)))
88+
).toBeFalsy();
89+
});
90+
91+
test("allows a alphanumeric underscore ids", () => {
92+
const { validationRegex } = extensionParams["TABLE_ID"];
93+
expect(
94+
Boolean("my_table".match(new RegExp(validationRegex)))
95+
).toBeTruthy();
96+
});
97+
});
98+
});
99+
});
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import * as functionsTestInit from "../node_modules/firebase-functions-test";
2+
import mockedEnv from "../node_modules/mocked-env";
3+
4+
import { mockConsoleLog } from "./__mocks__/console";
5+
6+
import config from "../src/config";
7+
8+
jest.mock("@firebaseextensions/firestore-bigquery-change-tracker", () => ({
9+
FirestoreBigQueryEventHistoryTracker: jest.fn(() => {
10+
return {
11+
record: jest.fn(() => {}),
12+
};
13+
}),
14+
ChangeType: {
15+
DELETE: 2,
16+
UPDATE: 1,
17+
CREATE: 0,
18+
},
19+
}));
20+
21+
jest.mock("../src/logs", () => ({
22+
start: jest.fn(() =>
23+
console.log("Started execution of extension with configuration", config)
24+
),
25+
init: jest.fn(() => {}),
26+
error: jest.fn(() => {}),
27+
complete: jest.fn(() => console.log("Completed execution of extension")),
28+
}));
29+
30+
const defaultEnvironment = {
31+
PROJECT_ID: "fake-project",
32+
DATASET_ID: "my_ds_id",
33+
TABLE_ID: "my_id",
34+
COLLECTION_PATH: "example",
35+
};
36+
37+
export const mockExport = (document, data) => {
38+
const ref = require("../src/index").fsexportbigquery;
39+
let functionsTest = functionsTestInit();
40+
41+
const wrapped = functionsTest.wrap(ref);
42+
return wrapped(document, data);
43+
};
44+
45+
export const mockedFirestoreBigQueryEventHistoryTracker = () => {};
46+
47+
let restoreEnv;
48+
let functionsTest = functionsTestInit();
49+
50+
describe("extension", () => {
51+
beforeEach(() => {
52+
restoreEnv = mockedEnv(defaultEnvironment);
53+
});
54+
55+
test("functions are exported", () => {
56+
const exportedFunctions = jest.requireActual("../src");
57+
expect(exportedFunctions.fsexportbigquery).toBeInstanceOf(Function);
58+
});
59+
60+
describe("functions.fsexportbigquery", () => {
61+
let functionsConfig;
62+
let callResult;
63+
64+
beforeEach(async () => {
65+
jest.resetModules();
66+
functionsTest = functionsTestInit();
67+
68+
functionsConfig = config;
69+
});
70+
71+
test("functions runs with a deletion", async () => {
72+
const beforeSnapshot = { foo: "bar" };
73+
const afterSnapshot = { foo: "bars" };
74+
75+
const documentChange = functionsTest.makeChange(
76+
beforeSnapshot,
77+
afterSnapshot
78+
);
79+
80+
const callResult = await mockExport(documentChange, {
81+
resource: {
82+
name: "test",
83+
},
84+
});
85+
86+
expect(callResult).toBeUndefined();
87+
88+
expect(mockConsoleLog).toBeCalledWith(
89+
"Started execution of extension with configuration",
90+
functionsConfig
91+
);
92+
93+
expect(mockConsoleLog).toBeCalledWith("Completed execution of extension");
94+
});
95+
96+
test("function runs with updated data", async () => {
97+
const beforeSnapshot = { foo: "bar" };
98+
const afterSnapshot = { foo: "bars", exists: true };
99+
100+
const documentChange = functionsTest.makeChange(
101+
beforeSnapshot,
102+
afterSnapshot
103+
);
104+
105+
const callResult = await mockExport(documentChange, {
106+
resource: {
107+
name: "test",
108+
},
109+
});
110+
111+
expect(callResult).toBeUndefined();
112+
113+
expect(mockConsoleLog).toBeCalledWith(
114+
"Started execution of extension with configuration",
115+
functionsConfig
116+
);
117+
118+
expect(mockConsoleLog).toBeCalledWith("Completed execution of extension");
119+
});
120+
});
121+
});

0 commit comments

Comments
 (0)