Skip to content
Open
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
221 changes: 79 additions & 142 deletions src/deploy/firestore/prepare.spec.ts
Original file line number Diff line number Diff line change
@@ -1,160 +1,97 @@
import { expect } from "chai";
import * as sinon from "sinon";
import prepare from "./prepare";
import { RulesDeploy } from "../../rulesDeploy";
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";
import { FirebaseError } from "../../error";

describe("firestore prepare", () => {
let sandbox: sinon.SinonSandbox;
describe("deploy/firestore/prepare", () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The refactoring of this test file has resulted in a significant loss of test coverage. Specifically, the following scenarios from the original file are no longer tested:\n- Default database creation settings.\n- Enterprise edition with FIRESTORE_NATIVE data access mode.\n- Enterprise edition with MONGODB_COMPATIBLE data access mode.\n- Validation errors when dataAccessMode is used with the standard edition.\n\nWhile the new tests for filtering flags are valuable, they should not replace the existing logic tests for createDatabase. Please restore the missing test cases to ensure the robustness of the Firestore deployment preparation logic.

let getFirestoreConfigStub: sinon.SinonStub;
let compileStub: sinon.SinonStub;
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",
},
]);
sinon.stub(ensureApiEnabled, "ensure").resolves();
getFirestoreConfigStub = sinon.stub(fsConfig, "getFirestoreConfig").returns([]);
compileStub = sinon.stub(RulesDeploy.prototype, "compile").resolves();
sinon.stub(RulesDeploy.prototype, "addFile").returns();
getDatabaseStub = sinon.stub(FirestoreApi.prototype, "getDatabase").resolves();
createDatabaseStub = sinon.stub(FirestoreApi.prototype, "createDatabase").resolves();
});

afterEach(() => {
sandbox.restore();
sinon.restore();
});

it("should exit early if no firestore configs are found", async () => {
const context: any = { projectId: "my-project" };

Check warning on line 30 in src/deploy/firestore/prepare.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of any as a type is prohibited by the repository style guide. Consider defining a proper interface or using a more specific type for the context and options objects to improve type safety and maintainability.

References
  1. Never use any or unknown as an escape hatch. Define proper interfaces/types or use type guards. (link)

const options: any = { only: "" };

Check warning on line 31 in src/deploy/firestore/prepare.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type

await prepare(context, options);

Check warning on line 33 in src/deploy/firestore/prepare.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe argument of type `any` assigned to a parameter of type `DeployOptions`

expect(context.firestore).to.be.undefined;

Check warning on line 35 in src/deploy/firestore/prepare.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .firestore on an `any` value
});

it("should prepare filtering flags correctly for --only firestore", async () => {
getFirestoreConfigStub.returns([{ database: "(default)", rules: "firestore.rules" }]);
const context: any = { projectId: "my-project" };

Check warning on line 40 in src/deploy/firestore/prepare.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
const options: any = {

Check warning on line 41 in src/deploy/firestore/prepare.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
only: "firestore",
config: { data: { firestore: {} }, path: () => "path" },
projectId: "my-project",
};

await prepare(context, options);

Check warning on line 47 in src/deploy/firestore/prepare.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe argument of type `any` assigned to a parameter of type `DeployOptions`

expect(context.firestoreRules).to.be.true;

Check warning on line 49 in src/deploy/firestore/prepare.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .firestoreRules on an `any` value
expect(context.firestoreIndexes).to.be.true;

Check warning on line 50 in src/deploy/firestore/prepare.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .firestoreIndexes on an `any` value
});

it("should prepare filtering flags correctly for --only firestore:rules", async () => {
getFirestoreConfigStub.returns([{ database: "(default)", rules: "firestore.rules" }]);
const context: any = { projectId: "my-project" };

Check warning on line 55 in src/deploy/firestore/prepare.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
const options: any = {
only: "firestore:rules",
config: { data: { firestore: {} }, path: () => "path" },
projectId: "my-project",
};

await prepare(context, options);

expect(context.firestoreRules).to.be.true;
expect(context.firestoreIndexes).to.be.false;
});

it("should create a missing database on preparation", async () => {
getFirestoreConfigStub.returns([{ database: "(default)", rules: "firestore.rules" }]);
getDatabaseStub.rejects({ status: 404 } as unknown as Error);

const context: any = { projectId: "my-project" };
const options: any = {
config: { data: { firestore: { database: "(default)" } }, path: () => "path" },
projectId: "my-project",
};

await prepare(context, options);

expect(createDatabaseStub).to.have.been.calledOnce;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This assertion only checks that the stub was called, but it doesn't verify the configuration passed to it. It is recommended to verify that createDatabase is called with the correct parameters (e.g., databaseId, databaseEdition, locationId) to ensure the logic in prepare.ts is functioning as expected.

expect(compileStub).to.have.been.calledOnce;
});

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.",
);
});
it("should throw if invalid edition is specified", async () => {
getFirestoreConfigStub.returns([{ database: "(default)" }]);
const context: any = { projectId: "my-project" };
const options: any = {
config: { data: { firestore: { edition: "INVALID_EDITION" } }, path: () => "path" },
projectId: "my-project",
};

await expect(prepare(context, options)).to.be.rejectedWith(
FirebaseError,
/Invalid edition specified for database/,
);
});
});
Loading