Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion kits/bigquery-firestore-export/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,57 @@ import type {
} from "./export-config";

const LOG_LEVEL_OPTIONS = ["debug", "info", "warn", "error", "silent"] as const;

/**
* Mirrors the BigQuery dataset locations offered by the upstream extension. The
* query job must run in the same location as the dataset it reads, so a value
* outside this list only fails once the scheduled query runs.
*/
const BIGQUERY_DATASET_LOCATION_OPTIONS: Record<string, string> = {
"Columbus, Ohio (us-east5)": "us-east5",
"Iowa (us-central1)": "us-central1",
"Las Vegas (us-west4)": "us-west4",
"Los Angeles (us-west2)": "us-west2",
"Montréal (northamerica-northeast1)": "northamerica-northeast1",
"Northern Virginia (us-east4)": "us-east4",
"Oregon (us-west1)": "us-west1",
"Salt Lake City (us-west3)": "us-west3",
"São Paulo (southamerica-east1)": "southamerica-east1",
"Santiago (southamerica-west1)": "southamerica-west1",
"South Carolina (us-east1)": "us-east1",
"Toronto (northamerica-northeast2)": "northamerica-northeast2",
"Delhi (asia-south2)": "asia-south2",
"Hong Kong (asia-east2)": "asia-east2",
"Jakarta (asia-southeast2)": "asia-southeast2",
"Melbourne (australia-southeast2)": "australia-southeast2",
"Mumbai (asia-south1)": "asia-south1",
"Osaka (asia-northeast2)": "asia-northeast2",
"Seoul (asia-northeast3)": "asia-northeast3",
"Singapore (asia-southeast1)": "asia-southeast1",
"Sydney (australia-southeast1)": "australia-southeast1",
"Taiwan (asia-east1)": "asia-east1",
"Tokyo (asia-northeast1)": "asia-northeast1",
"Belgium (europe-west1)": "europe-west1",
"Finland (europe-north1)": "europe-north1",
"Frankfurt (europe-west3)": "europe-west3",
"London (europe-west2)": "europe-west2",
"Madrid (europe-southwest1)": "europe-southwest1",
"Milan (europe-west8)": "europe-west8",
"Netherlands (europe-west4)": "europe-west4",
"Paris (europe-west9)": "europe-west9",
"Warsaw (europe-central2)": "europe-central2",
"Zürich (europe-west6)": "europe-west6",
"US Multi-Region (US)": "US",
"EU Multi-Region (EU)": "EU",
};

const instanceId = defineString("INSTANCE_ID");

const params = {
export const params = {
instanceId,
bigqueryDatasetLocation: defineString("BIGQUERY_DATASET_LOCATION", {
default: "US",
input: select(BIGQUERY_DATASET_LOCATION_OPTIONS),
}),
transferConfigName: defineString("TRANSFER_CONFIG_NAME", { default: "" }),
datasetId: defineString("DATASET_ID"),
Expand All @@ -45,6 +90,7 @@ const params = {
default: "transferConfigs",
input: {
text: {
example: "transferConfigs",
validationRegex: /^[^\/]+(\/[^\/]+\/[^\/]+)*$/,
validationErrorMessage: "Must be a valid Cloud Firestore Collection",
},
Expand Down
105 changes: 103 additions & 2 deletions kits/bigquery-firestore-export/tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
*/

import { Expression } from "firebase-functions/params";
import type { SelectInput, TextInput } from "firebase-functions/params";
import { afterEach, describe, expect, test, vi } from "vitest";
import { CONFIG_EXPRESSIONS, configFromEnv } from "../src/config";
import { CONFIG_EXPRESSIONS, configFromEnv, params } from "../src/config";

afterEach(() => {
vi.unstubAllEnvs();
Expand Down Expand Up @@ -47,15 +48,115 @@ describe("configFromEnv", () => {
vi.stubEnv("COLLECTION_PATH", "transferConfigs");
vi.stubEnv("LOG_LEVEL", "info");

expect(configFromEnv()).toMatchObject({
expect(configFromEnv()).toEqual({
projectId: "test-project",
instanceId: "users-export",
bigqueryDatasetLocation: "EU",
datasetId: "analytics",
tableName: "users",
queryString: "SELECT * FROM source.users",
displayName: "Users export",
schedule: "every 24 hours",
transferConfigName: undefined,
partitioningField: undefined,
pubSubTopic: "kit-users-export-processMessages",
firestoreCollection: "transferConfigs",
logLevel: "info",
});
});
});

const EXPECTED_DATASET_LOCATIONS = [
"us-east5",
"us-central1",
"us-west4",
"us-west2",
"northamerica-northeast1",
"us-east4",
"us-west1",
"us-west3",
"southamerica-east1",
"southamerica-west1",
"us-east1",
"northamerica-northeast2",
"asia-south2",
"asia-east2",
"asia-southeast2",
"australia-southeast2",
"asia-south1",
"asia-northeast2",
"asia-northeast3",
"asia-southeast1",
"australia-southeast1",
"asia-east1",
"asia-northeast1",
"europe-west1",
"europe-north1",
"europe-west3",
"europe-west2",
"europe-southwest1",
"europe-west8",
"europe-west4",
"europe-west9",
"europe-central2",
"europe-west6",
"US",
"EU",
];

describe("COLLECTION_PATH input", () => {
const input = params.firestoreCollection.options.input as TextInput<string>;

test("declares the collection path validator and its error message", () => {
expect(input.text.validationRegex).toEqual(/^[^\/]+(\/[^\/]+\/[^\/]+)*$/);
expect(input.text.validationErrorMessage).toBe(
"Must be a valid Cloud Firestore Collection"
);
expect(params.firestoreCollection.options.default).toBe("transferConfigs");
});

test.each([
["transferConfigs", true],
["a", true],
["a/b/c", true],
["a/b/c/d/e", true],
["a/b", false],
["a/b/c/d", false],
["/a", false],
["a/", false],
["", false],
])("%s is accepted: %s", (path, accepted) => {
const regex = new RegExp(input.text.validationRegex!);
expect(regex.test(path)).toBe(accepted);
});
});

describe("BIGQUERY_DATASET_LOCATION input", () => {
const input = params.bigqueryDatasetLocation.options
.input as SelectInput<string>;

test("offers the upstream regions and both multi-regions", () => {
expect(input.select.options.map((option) => option.value)).toEqual(
EXPECTED_DATASET_LOCATIONS
);
expect(params.bigqueryDatasetLocation.options.default).toBe("US");
});

test("labels every option", () => {
for (const option of input.select.options) {
expect(option.label).toMatch(new RegExp(`\\(${option.value}\\)$`));
}
});
});

describe("params without upstream validation", () => {
test.each([
"datasetId",
"tableName",
"queryString",
"displayName",
"schedule",
] as const)("%s stays free-form", (key) => {
expect(params[key].options.input).toBeUndefined();
});
});
Loading