Skip to content

Commit

Permalink
Fail the shell fast if you cannot query the database (#509)
Browse files Browse the repository at this point in the history
  • Loading branch information
ecooper authored Dec 11, 2024
1 parent 82fd6d4 commit 02b5166
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 3 deletions.
4 changes: 4 additions & 0 deletions src/commands/shell.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ async function shellCommand(argv) {

validateDatabaseOrSecret(argv);

// Fast fail if the database is not queryable
const isQueryable = container.resolve("isQueryable");
await isQueryable({ ...argv, secret: await getSecret() });

const logger = container.resolve("logger");
let completionPromise;

Expand Down
7 changes: 6 additions & 1 deletion src/config/setup-container.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import { Credentials } from "../lib/auth/credentials.mjs";
import OAuthClient from "../lib/auth/oauth-client.mjs";
import { makeRetryableFaunaRequest } from "../lib/db.mjs";
import * as faunaV10 from "../lib/fauna.mjs";
import { formatError, runQueryFromString } from "../lib/fauna-client.mjs";
import {
formatError,
isQueryable,
runQueryFromString,
} from "../lib/fauna-client.mjs";
import * as faunaV4 from "../lib/faunadb.mjs";
import fetchWrapper from "../lib/fetch-wrapper.mjs";
import { codeToAnsi } from "../lib/formatting/codeToAnsi.mjs";
Expand Down Expand Up @@ -85,6 +89,7 @@ export const injectables = {
formatError: awilix.asValue(formatError),
faunaClientV10: awilix.asValue(faunaV10),
faunaClientV4: awilix.asValue(faunaV4),
isQueryable: awilix.asValue(isQueryable),

// feature-specific lib (homemade utilities)
gatherFSL: awilix.asValue(gatherFSL),
Expand Down
1 change: 1 addition & 0 deletions src/config/setup-test-container.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export function setupTestContainer() {
makeFaunaRequest: awilix.asValue(spy(makeRetryableFaunaRequest)),
makeAccountRequest: awilix.asValue(stub()),
runQueryFromString: awilix.asValue(stub().resolves({})),
isQueryable: awilix.asValue(stub().resolves()),
formatError: awilix.asValue(stub()),
faunaClientV10: awilix.asValue({
getClient: stub(),
Expand Down
4 changes: 3 additions & 1 deletion src/lib/account.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { container } from "../cli.mjs";
import { ValidationError } from "./errors.mjs";
import { InvalidCredsError, UnauthorizedError } from "./misc.mjs";

/**
*
* @param {Object} opts
Expand Down Expand Up @@ -82,6 +82,8 @@ async function parseResponse(response, shouldThrow) {
throw new InvalidCredsError(message);
case 403:
throw new UnauthorizedError(message);
case 404:
throw new ValidationError(message);
default:
throw new Error(message);
}
Expand Down
15 changes: 15 additions & 0 deletions src/lib/fauna-client.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//@ts-check

import { container } from "../cli.mjs";
import { ValidationError } from "./errors.mjs";
import { colorize, Format } from "./formatting/colorize.mjs";

const SUMMARY_FQL_REGEX = /^(\s\s\|)|(\d\s\|)/;
Expand Down Expand Up @@ -87,6 +88,20 @@ export const runQueryFromString = (expression, argv) => {
}
};

/**
* Check if a database can be queried based on the current arguments.
* If it can't, it will throw an error.
* @param {*} argv
*/
export const isQueryable = async (argv) => {
const runQueryFromString = container.resolve("runQueryFromString");
try {
await runQueryFromString("1+1", argv);
} catch (err) {
throw new ValidationError(err.message, { cause: err });
}
};

/**
* Formats an error.
* @param {object} err - The error to format
Expand Down
18 changes: 17 additions & 1 deletion test/shell.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import sinon, { stub } from "sinon";

import { run } from "../src/cli.mjs";
import { setupTestContainer as setupContainer } from "../src/config/setup-test-container.mjs";
import { ValidationError } from "../src/lib/errors.mjs";
import { dirExists } from "../src/lib/file-util.mjs";
import { colorize } from "../src/lib/formatting/colorize.mjs";
import { createV4QuerySuccess, createV10QuerySuccess } from "./helpers.mjs";
Expand Down Expand Up @@ -61,7 +62,7 @@ const sleep = async (ms) =>
});

describe("shell", function () {
let container, stdin, stdout, logger, runQueryFromString;
let container, stdin, stdout, stderr, logger, runQueryFromString;

const promptReset = "\x1B[1G\x1B[0J> ";
const prompt = `${EOL}${promptReset}\x1B[3G`;
Expand All @@ -79,6 +80,7 @@ describe("shell", function () {
logger = container.resolve("logger");
stdin = container.resolve("stdinStream");
stdout = container.resolve("stdoutStream");
stderr = container.resolve("stderrStream");
runQueryFromString = container.resolve("runQueryFromString");
});

Expand All @@ -91,6 +93,20 @@ describe("shell", function () {

it.skip("can set a connection timeout", async function () {});

it("can fail before getting to shell if the database is not queryable", async function () {
container
.resolve("isQueryable")
.rejects(new ValidationError("Database not found: us/bad"));
const runPromise = run(`shell --format json -d us/bad`, container);

try {
await runPromise;
} catch {}

await stderr.waitForWritten();
expect(stderr.getWritten()).to.match(/Database not found: us\/bad/);
});

describe("history", function () {
const upArrow = "\x1b[A";
const downArrow = "\x1b[B";
Expand Down

0 comments on commit 02b5166

Please sign in to comment.