From 6aaba38bea92f7ba204232abd125aa61ca346687 Mon Sep 17 00:00:00 2001 From: "E. Cooper" Date: Wed, 11 Dec 2024 09:53:39 -0800 Subject: [PATCH] Fail shell fast if you cannot query a database --- src/commands/shell.mjs | 4 ++++ src/config/setup-container.mjs | 7 ++++++- src/config/setup-test-container.mjs | 1 + src/lib/account.mjs | 4 +++- src/lib/fauna-client.mjs | 15 +++++++++++++++ test/shell.mjs | 18 +++++++++++++++++- 6 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/commands/shell.mjs b/src/commands/shell.mjs index e225e539..6b876525 100644 --- a/src/commands/shell.mjs +++ b/src/commands/shell.mjs @@ -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; diff --git a/src/config/setup-container.mjs b/src/config/setup-container.mjs index ac9da508..0da49749 100644 --- a/src/config/setup-container.mjs +++ b/src/config/setup-container.mjs @@ -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"; @@ -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), diff --git a/src/config/setup-test-container.mjs b/src/config/setup-test-container.mjs index 2ce16522..51cbaf24 100644 --- a/src/config/setup-test-container.mjs +++ b/src/config/setup-test-container.mjs @@ -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(), diff --git a/src/lib/account.mjs b/src/lib/account.mjs index b993b9ac..37846fb4 100644 --- a/src/lib/account.mjs +++ b/src/lib/account.mjs @@ -1,6 +1,6 @@ import { container } from "../cli.mjs"; +import { ValidationError } from "./command-helpers.mjs"; import { InvalidCredsError, UnauthorizedError } from "./misc.mjs"; - /** * * @param {Object} opts @@ -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); } diff --git a/src/lib/fauna-client.mjs b/src/lib/fauna-client.mjs index 1d26dfd0..e9b145f9 100644 --- a/src/lib/fauna-client.mjs +++ b/src/lib/fauna-client.mjs @@ -1,6 +1,7 @@ //@ts-check import { container } from "../cli.mjs"; +import { ValidationError } from "./command-helpers.mjs"; import { colorize, Format } from "./formatting/colorize.mjs"; const SUMMARY_FQL_REGEX = /^(\s\s\|)|(\d\s\|)/; @@ -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 diff --git a/test/shell.mjs b/test/shell.mjs index b8a82f5e..c581f48a 100644 --- a/test/shell.mjs +++ b/test/shell.mjs @@ -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/command-helpers.mjs"; import { dirExists } from "../src/lib/file-util.mjs"; import { colorize } from "../src/lib/formatting/colorize.mjs"; import { createV4QuerySuccess, createV10QuerySuccess } from "./helpers.mjs"; @@ -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`; @@ -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"); }); @@ -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";