Skip to content

Commit

Permalink
Add tests to project, storage, and firestore init flows (#1381)
Browse files Browse the repository at this point in the history
* convert firebaseApi and init/features/project to typescript

* change to use async/await

* add jsdoc on the interfaces

* fix default export

* format

* remove default function, ts-ify storage

* add cast to Question to keep validate function

* delete project.d.ts and move interfaces in with source code

* fix interfaces, remote lint disables, refactor

* Error => FirebaseError

* add tests to project init and storage init

* add firestore tests - refactor and ts-ifying proved necessary to do this

* use index.ts to remove extra /firestore in imports

* review comment fixes, remove validate function

* small format and type fixes

* sinon-chai changes

* fix bug in stoage initialization

* remove only

* fix storage unit test

* format changes
  • Loading branch information
thechenky committed Jun 11, 2019
1 parent b6d1936 commit 9cffe5a
Show file tree
Hide file tree
Showing 11 changed files with 398 additions and 202 deletions.
171 changes: 0 additions & 171 deletions src/init/features/firestore.ts

This file was deleted.

11 changes: 11 additions & 0 deletions src/init/features/firestore/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import requireAccess = require("../../../requireAccess");
import * as rules from "./rules";
import * as indexes from "./indexes";

export async function doSetup(setup: any, config: any): Promise<void> {
setup.config.firestore = {};

await requireAccess.requireAccess({ project: setup.projectId });
await rules.initRules(setup, config);
await indexes.initIndexes(setup, config);
}
77 changes: 77 additions & 0 deletions src/init/features/firestore/indexes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import clc = require("cli-color");
import fs = require("fs");

import FirebaseError = require("../../../error");
import iv2 = require("../../../firestore/indexes");
import fsutils = require("../../../fsutils");
import { prompt, promptOnce } from "../../../prompt";
import logger = require("../../../logger");

const indexes = new iv2.FirestoreIndexes();

const INDEXES_TEMPLATE = fs.readFileSync(
__dirname + "/../../../../templates/init/firestore/firestore.indexes.json",
"utf8"
);

export async function initIndexes(setup: any, config: any): Promise<any> {
logger.info();
logger.info("Firestore indexes allow you to perform complex queries while");
logger.info("maintaining performance that scales with the size of the result");
logger.info("set. You can keep index definitions in your project directory");
logger.info("and publish them with " + clc.bold("firebase deploy") + ".");
logger.info();

return prompt(setup.config.firestore, [
{
type: "input",
name: "indexes",
message: "What file should be used for Firestore indexes?",
default: "firestore.indexes.json",
},
])
.then(() => {
const filename = setup.config.firestore.indexes;
if (fsutils.fileExistsSync(filename)) {
const msg =
"File " +
clc.bold(filename) +
" already exists." +
" Do you want to overwrite it with the Firestore Indexes from the Firebase Console?";
return promptOnce({
type: "confirm",
message: msg,
default: false,
});
}
return Promise.resolve(true);
})
.then((overwrite) => {
if (!overwrite) {
return Promise.resolve();
}

return getIndexesFromConsole(setup.projectId).then((contents: any) => {
return config.writeProjectFile(setup.config.firestore.indexes, contents);
});
});
}

async function getIndexesFromConsole(projectId: any): Promise<any> {
const indexesPromise = indexes.listIndexes(projectId);
const fieldOverridesPromise = indexes.listFieldOverrides(projectId);

return Promise.all([indexesPromise, fieldOverridesPromise])
.then((res) => {
return indexes.makeIndexSpec(res[0], res[1]);
})
.catch((e) => {
if (e.message.indexOf("is not a Cloud Firestore enabled project") >= 0) {
return INDEXES_TEMPLATE;
}

throw new FirebaseError("Error fetching Firestore indexes", {
original: e,
});
});
}
87 changes: 87 additions & 0 deletions src/init/features/firestore/rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import clc = require("cli-color");
import fs = require("fs");

import gcp = require("../../../gcp");
import fsutils = require("../../../fsutils");
import { prompt, promptOnce } from "../../../prompt";
import logger = require("../../../logger");
import utils = require("../../../utils");

const DEFAULT_RULES_FILE = "firestore.rules";

const RULES_TEMPLATE = fs.readFileSync(
__dirname + "/../../../../templates/init/firestore/firestore.rules",
"utf8"
);

export async function initRules(setup: any, config: any): Promise<any> {
logger.info();
logger.info("Firestore Security Rules allow you to define how and when to allow");
logger.info("requests. You can keep these rules in your project directory");
logger.info("and publish them with " + clc.bold("firebase deploy") + ".");
logger.info();

return prompt(setup.config.firestore, [
{
type: "input",
name: "rules",
message: "What file should be used for Firestore Rules?",
default: DEFAULT_RULES_FILE,
},
])
.then(() => {
const filename = setup.config.firestore.rules;

if (fsutils.fileExistsSync(filename)) {
const msg =
"File " +
clc.bold(filename) +
" already exists." +
" Do you want to overwrite it with the Firestore Rules from the Firebase Console?";
return promptOnce({
type: "confirm",
message: msg,
default: false,
});
}

return Promise.resolve(true);
})
.then((overwrite) => {
if (!overwrite) {
return Promise.resolve();
}

return getRulesFromConsole(setup.projectId).then((contents: any) => {
return config.writeProjectFile(setup.config.firestore.rules, contents);
});
});
}

async function getRulesFromConsole(projectId: string): Promise<any> {
return gcp.rules
.getLatestRulesetName(projectId, "cloud.firestore")
.then((name) => {
if (!name) {
logger.debug("No rulesets found, using default.");
return [{ name: DEFAULT_RULES_FILE, content: RULES_TEMPLATE }];
}

logger.debug("Found ruleset: " + name);
return gcp.rules.getRulesetContent(name);
})
.then((rules: any[]) => {
if (rules.length <= 0) {
return utils.reject("Ruleset has no files", { exit: 1 });
}

if (rules.length > 1) {
return utils.reject("Ruleset has too many files: " + rules.length, { exit: 1 });
}

// Even though the rules API allows for multi-file rulesets, right
// now both the console and the CLI are built on the single-file
// assumption.
return rules[0].content;
});
}
2 changes: 1 addition & 1 deletion src/init/features/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ var _ = require("lodash");
var logger = require("../../../logger");
var { prompt } = require("../../../prompt");
var enableApi = require("../../../ensureApiEnabled").enable;
var requireAccess = require("../../../requireAccess");
var requireAccess = require("../../../requireAccess").requireAccess;
var scopes = require("../../../scopes");

module.exports = function(setup, config) {
Expand Down
2 changes: 1 addition & 1 deletion src/init/features/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface ProjectInfo {
* Get the user's desired project, prompting if necessary.
* @returns A {@link ProjectInfo} object.
*/
async function getProjectInfo(options: any): Promise<ProjectInfo> {
export async function getProjectInfo(options: any): Promise<ProjectInfo> {
if (options.project) {
return selectProjectFromOptions(options);
}
Expand Down
17 changes: 8 additions & 9 deletions src/init/features/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as clc from "cli-color";
import * as fs from "fs";

import * as logger from "../../logger";
import { prompt } from "../../prompt";
import { promptOnce } from "../../prompt";

const RULES_TEMPLATE = fs.readFileSync(
__dirname + "/../../../templates/init/storage/storage.rules",
Expand All @@ -18,13 +18,12 @@ export async function doSetup(setup: any, config: any): Promise<void> {
logger.info("and publish them with " + clc.bold("firebase deploy") + ".");
logger.info();

await prompt(setup.config.storage, [
{
type: "input",
name: "rules",
message: "What file should be used for Storage Rules?",
default: "storage.rules",
},
]);
const storageRulesFile = await promptOnce({
type: "input",
name: "rules",
message: "What file should be used for Storage Rules?",
default: "storage.rules",
});
setup.config.storage.rules = storageRulesFile;
config.writeProjectFile(setup.config.storage.rules, RULES_TEMPLATE);
}
Loading

0 comments on commit 9cffe5a

Please sign in to comment.