-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add support for setting DataAccessMode in firebase.json #10065
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9dfb189
f220264
b731efe
de01b9f
9066bb5
39e5d02
e9de984
7122e5c
99a31e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| import { expect } from "chai"; | ||
| import * as sinon from "sinon"; | ||
| import prepare from "./prepare"; | ||
| import { FirestoreApi } from "../../firestore/api"; | ||
| import * as types from "../../firestore/api-types"; | ||
| import { FirebaseError } from "../../error"; | ||
| import { Options } from "../../options"; | ||
| import * as ensureApiEnabled from "../../ensureApiEnabled"; | ||
| import * as fsConfig from "../../firestore/fsConfig"; | ||
| import * as loadCJSON from "../../loadCJSON"; | ||
| import { RulesDeploy } from "../../rulesDeploy"; | ||
|
|
||
| describe("firestore prepare", () => { | ||
| let sandbox: sinon.SinonSandbox; | ||
| let getDatabaseStub: sinon.SinonStub; | ||
| let createDatabaseStub: sinon.SinonStub; | ||
|
|
||
| beforeEach(() => { | ||
| sandbox = sinon.createSandbox(); | ||
| getDatabaseStub = sandbox.stub(FirestoreApi.prototype, "getDatabase"); | ||
| createDatabaseStub = sandbox.stub(FirestoreApi.prototype, "createDatabase"); | ||
| sandbox.stub(ensureApiEnabled, "ensure").resolves(); | ||
| sandbox.stub(loadCJSON, "loadCJSON").returns({}); | ||
| sandbox.stub(RulesDeploy.prototype, "addFile").returns(); | ||
| sandbox.stub(RulesDeploy.prototype, "compile").resolves(); | ||
| sandbox.stub(fsConfig, "getFirestoreConfig").returns([ | ||
| { | ||
| database: "test-db", | ||
| rules: "firestore.rules", | ||
| indexes: "firestore.indexes.json", | ||
| }, | ||
| ]); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| sandbox.restore(); | ||
| }); | ||
|
|
||
| describe("createDatabase", () => { | ||
| const projectId = "test-project"; | ||
| const options = { | ||
| projectId, | ||
| config: { | ||
| path: (p: string) => p, | ||
| data: { | ||
| firestore: { | ||
| database: "test-db", | ||
| }, | ||
| }, | ||
| }, | ||
| } as unknown as Options; | ||
|
|
||
| it("should create a database with default settings when dataAccessMode is missing", async () => { | ||
| getDatabaseStub.rejects({ status: 404 }); | ||
| createDatabaseStub.resolves(); | ||
|
|
||
| // We need to call the default export which calls createDatabase internally | ||
| await prepare({ projectId }, options); | ||
|
|
||
| expect(createDatabaseStub.calledOnce).to.be.true; | ||
| const args = createDatabaseStub.firstCall.args[0]; | ||
| expect(args.firestoreDataAccessMode).to.be.undefined; | ||
| expect(args.mongodbCompatibleDataAccessMode).to.be.undefined; | ||
| expect(args.databaseEdition).to.equal(types.DatabaseEdition.STANDARD); | ||
| }); | ||
|
|
||
| it("should create a database with FIRESTORE_NATIVE when specified on enterprise edition", async () => { | ||
| const enterpriseOptions = { | ||
| projectId, | ||
| config: { | ||
| path: (p: string) => p, | ||
| data: { | ||
| firestore: { | ||
| database: "test-db", | ||
| edition: "enterprise", | ||
| dataAccessMode: "FIRESTORE_NATIVE", | ||
| }, | ||
| }, | ||
| }, | ||
| } as unknown as Options; | ||
| getDatabaseStub.rejects({ status: 404 }); | ||
| createDatabaseStub.resolves(); | ||
|
|
||
| await prepare({ projectId }, enterpriseOptions); | ||
|
|
||
| expect(createDatabaseStub.calledOnce).to.be.true; | ||
| const args = createDatabaseStub.firstCall.args[0]; | ||
| expect(args.firestoreDataAccessMode).to.equal(types.DataAccessMode.ENABLED); | ||
| expect(args.mongodbCompatibleDataAccessMode).to.equal(types.DataAccessMode.DISABLED); | ||
| expect(args.databaseEdition).to.equal(types.DatabaseEdition.ENTERPRISE); | ||
| }); | ||
|
|
||
| it("should create a database with MONGODB_COMPATIBLE when specified on enterprise edition", async () => { | ||
| const enterpriseOptions = { | ||
| projectId, | ||
| config: { | ||
| path: (p: string) => p, | ||
| data: { | ||
| firestore: { | ||
| database: "test-db", | ||
| edition: "enterprise", | ||
| dataAccessMode: "MONGODB_COMPATIBLE", | ||
| }, | ||
| }, | ||
| }, | ||
| } as unknown as Options; | ||
| getDatabaseStub.rejects({ status: 404 }); | ||
| createDatabaseStub.resolves(); | ||
|
|
||
| await prepare({ projectId }, enterpriseOptions); | ||
|
|
||
| expect(createDatabaseStub.calledOnce).to.be.true; | ||
| const args = createDatabaseStub.firstCall.args[0]; | ||
| expect(args.firestoreDataAccessMode).to.equal(types.DataAccessMode.DISABLED); | ||
| expect(args.mongodbCompatibleDataAccessMode).to.equal(types.DataAccessMode.ENABLED); | ||
| expect(args.databaseEdition).to.equal(types.DatabaseEdition.ENTERPRISE); | ||
| }); | ||
|
|
||
| it("should throw an error when dataAccessMode is specified on standard edition", async () => { | ||
| const standardOptions = { | ||
| projectId, | ||
| config: { | ||
| data: { | ||
| firestore: { | ||
| database: "test-db", | ||
| edition: "standard", | ||
| dataAccessMode: "MONGODB_COMPATIBLE", | ||
| }, | ||
| }, | ||
| }, | ||
| } as unknown as Options; | ||
| getDatabaseStub.rejects({ status: 404 }); | ||
|
|
||
| await expect(prepare({ projectId }, standardOptions)).to.be.rejectedWith( | ||
| FirebaseError, | ||
| "dataAccessMode can only be specified for enterprise edition databases.", | ||
| ); | ||
| }); | ||
|
|
||
| it("should throw an error when dataAccessMode is specified without edition (defaults to standard)", async () => { | ||
| const defaultOptions = { | ||
| projectId, | ||
| config: { | ||
| data: { | ||
| firestore: { | ||
| database: "test-db", | ||
| dataAccessMode: "MONGODB_COMPATIBLE", | ||
| }, | ||
| }, | ||
| }, | ||
| } as unknown as Options; | ||
| getDatabaseStub.rejects({ status: 404 }); | ||
|
|
||
| await expect(prepare({ projectId }, defaultOptions)).to.be.rejectedWith( | ||
| FirebaseError, | ||
| "dataAccessMode can only be specified for enterprise edition databases.", | ||
| ); | ||
| }); | ||
|
joehan marked this conversation as resolved.
|
||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -100,6 +100,23 @@ async function createDatabase(context: any, options: Options): Promise<void> { | |
| edition = upperEdition as types.DatabaseEdition; | ||
| } | ||
|
|
||
| let firestoreDataAccessMode: types.DataAccessMode | undefined; | ||
| let mongodbCompatibleDataAccessMode: types.DataAccessMode | undefined; | ||
| if (firestoreCfg.dataAccessMode) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will need two dataAccessMode (firestoreDataAccessMode and mongoDataAccessMode - consistent with API design). Firestore will add support for both modes in the future (interoperability).
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking that we could simplify this into a single field dataAccessMode, and turn it into an array once we add interoperability - ie: This seemed more ergonomic to me (especially before interop is available), but I don't feel too strongly about this tho, so happy to match the backend API design if you prefer |
||
| if (edition !== types.DatabaseEdition.ENTERPRISE) { | ||
| throw new FirebaseError( | ||
| "dataAccessMode can only be specified for enterprise edition databases.", | ||
| ); | ||
| } | ||
| if (firestoreCfg.dataAccessMode === "FIRESTORE_NATIVE") { | ||
| firestoreDataAccessMode = types.DataAccessMode.ENABLED; | ||
| mongodbCompatibleDataAccessMode = types.DataAccessMode.DISABLED; | ||
| } else if (firestoreCfg.dataAccessMode === "MONGODB_COMPATIBLE") { | ||
| firestoreDataAccessMode = types.DataAccessMode.DISABLED; | ||
| mongodbCompatibleDataAccessMode = types.DataAccessMode.ENABLED; | ||
| } | ||
|
joehan marked this conversation as resolved.
Comment on lines
+111
to
+117
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This const isNative = firestoreCfg.dataAccessMode === "FIRESTORE_NATIVE";
firestoreDataAccessMode = isNative ? types.DataAccessMode.ENABLED : types.DataAccessMode.DISABLED;
mongodbCompatibleDataAccessMode = isNative ? types.DataAccessMode.DISABLED : types.DataAccessMode.ENABLED;References
|
||
| } | ||
|
|
||
| const api = new FirestoreApi(); | ||
| try { | ||
| await api.getDatabase(options.projectId, firestoreCfg.database); | ||
|
|
@@ -118,6 +135,8 @@ async function createDatabase(context: any, options: Options): Promise<void> { | |
| databaseEdition: edition, | ||
| deleteProtectionState: types.DatabaseDeleteProtectionState.DISABLED, | ||
| pointInTimeRecoveryEnablement: types.PointInTimeRecoveryEnablement.DISABLED, | ||
| firestoreDataAccessMode, | ||
| mongodbCompatibleDataAccessMode, | ||
| }; | ||
| await api.createDatabase(createDatabaseReq); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The style guide (line 38) advises against using
unknownas an escape hatch. Casting withas unknown as Optionscan make tests brittle and hide type errors. For new test files, it's a good practice to establish a pattern of creating more robust mocks. Consider creating a test utility to generate mockOptionsobjects, or at a minimum, define the mock object withPartial<Options>and then cast toOptionsto make the partial nature of the mock more explicit.References
anyorunknownas an escape hatch. Define proper interfaces/types or use type guards." (link)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little worried about this since this kind of removes type safety
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gonna leave this for now since it is a test - however, we do have a mockOptions function definite in src/emulator/controller.spec.ts that could be turned into a general utility. Making a note to do so in a follow up PR (since we'll use it in other places too)