From 4edb083083814dc4e06679e3f8ca9f246f97cfc7 Mon Sep 17 00:00:00 2001 From: Paul Paterson Date: Tue, 17 Dec 2024 13:22:43 -0500 Subject: [PATCH] containerize fauna-client --- src/commands/database/create.mjs | 3 +- src/commands/database/delete.mjs | 3 +- src/commands/query.mjs | 16 ++++++----- src/commands/schema/abandon.mjs | 7 +++-- src/commands/schema/commit.mjs | 7 +++-- src/commands/schema/diff.mjs | 6 ++-- src/commands/schema/pull.mjs | 5 ++-- src/commands/schema/push.mjs | 4 +-- src/commands/schema/status.mjs | 4 +-- src/commands/shell.mjs | 13 +++++---- src/config/setup-container.mjs | 12 ++------ src/config/setup-test-container.mjs | 12 ++++---- src/lib/db.mjs | 3 +- src/lib/fauna-client.mjs | 4 ++- src/lib/schema.mjs | 3 +- test/credentials.mjs | 6 ++-- test/query.mjs | 43 +++++++++++++++++++++++++++-- test/shell.mjs | 19 +++++++------ 18 files changed, 108 insertions(+), 62 deletions(-) diff --git a/src/commands/database/create.mjs b/src/commands/database/create.mjs index 42c6cb93..ffc410d6 100644 --- a/src/commands/database/create.mjs +++ b/src/commands/database/create.mjs @@ -5,7 +5,6 @@ import { container } from "../../cli.mjs"; import { validateDatabaseOrSecret } from "../../lib/command-helpers.mjs"; import { CommandError } from "../../lib/errors.mjs"; import { faunaToCommandError } from "../../lib/fauna.mjs"; -import { getSecret, retryInvalidCredsOnce } from "../../lib/fauna-client.mjs"; import { colorize, Format } from "../../lib/formatting/colorize.mjs"; async function runCreateQuery(secret, argv) { @@ -25,6 +24,8 @@ async function runCreateQuery(secret, argv) { } async function createDatabase(argv) { + const { getSecret, retryInvalidCredsOnce } = container.resolve("faunaClient"); + const secret = await getSecret(); const logger = container.resolve("logger"); diff --git a/src/commands/database/delete.mjs b/src/commands/database/delete.mjs index 07293101..28655726 100644 --- a/src/commands/database/delete.mjs +++ b/src/commands/database/delete.mjs @@ -6,7 +6,6 @@ import { container } from "../../cli.mjs"; import { validateDatabaseOrSecret } from "../../lib/command-helpers.mjs"; import { CommandError } from "../../lib/errors.mjs"; import { faunaToCommandError } from "../../lib/fauna.mjs"; -import { getSecret, retryInvalidCredsOnce } from "../../lib/fauna-client.mjs"; async function runDeleteQuery(secret, argv) { const { fql } = container.resolve("fauna"); @@ -19,6 +18,8 @@ async function runDeleteQuery(secret, argv) { } async function deleteDatabase(argv) { + const { getSecret, retryInvalidCredsOnce } = container.resolve("faunaClient"); + const secret = await getSecret(); const logger = container.resolve("logger"); diff --git a/src/commands/query.mjs b/src/commands/query.mjs index 50717eef..50691567 100644 --- a/src/commands/query.mjs +++ b/src/commands/query.mjs @@ -11,11 +11,6 @@ import { isUnknownError, ValidationError, } from "../lib/errors.mjs"; -import { - formatError, - formatQueryResponse, - getSecret, -} from "../lib/fauna-client.mjs"; import { isTTY } from "../lib/misc.mjs"; function validate(argv) { @@ -70,7 +65,14 @@ const resolveInput = (argv) => { }; async function queryCommand(argv) { - const formatQueryInfo = container.resolve("formatQueryInfo"); + const { + formatError, + formatQueryInfo, + formatQueryResponse, + getSecret, + runQueryFromString, + } = container.resolve("faunaClient"); + const logger = container.resolve("logger"); // Run validation here instead of via check for more control over output @@ -99,7 +101,7 @@ async function queryCommand(argv) { // Using --json takes precedence over --format const outputFormat = resolveFormat(argv); - const results = await container.resolve("runQueryFromString")(expression, { + const results = await runQueryFromString(expression, { apiVersion, secret, url, diff --git a/src/commands/schema/abandon.mjs b/src/commands/schema/abandon.mjs index c4304cc5..c14cf50d 100644 --- a/src/commands/schema/abandon.mjs +++ b/src/commands/schema/abandon.mjs @@ -3,12 +3,13 @@ import { container } from "../../cli.mjs"; import { yargsWithCommonQueryOptions } from "../../lib/command-helpers.mjs"; import { CommandError } from "../../lib/errors.mjs"; -import { getSecret } from "../../lib/fauna-client.mjs"; async function doAbandon(argv) { - const makeFaunaRequest = container.resolve("makeFaunaRequest"); - const logger = container.resolve("logger"); const confirm = container.resolve("confirm"); + const { getSecret } = container.resolve("faunaClient"); + const logger = container.resolve("logger"); + const makeFaunaRequest = container.resolve("makeFaunaRequest"); + const secret = await getSecret(); if (!argv.input) { diff --git a/src/commands/schema/commit.mjs b/src/commands/schema/commit.mjs index 0bf93eed..660d7029 100644 --- a/src/commands/schema/commit.mjs +++ b/src/commands/schema/commit.mjs @@ -3,12 +3,13 @@ import { container } from "../../cli.mjs"; import { yargsWithCommonQueryOptions } from "../../lib/command-helpers.mjs"; import { CommandError } from "../../lib/errors.mjs"; -import { getSecret } from "../../lib/fauna-client.mjs"; async function doCommit(argv) { - const makeFaunaRequest = container.resolve("makeFaunaRequest"); - const logger = container.resolve("logger"); const confirm = container.resolve("confirm"); + const { getSecret } = container.resolve("faunaClient"); + const logger = container.resolve("logger"); + const makeFaunaRequest = container.resolve("makeFaunaRequest"); + const secret = await getSecret(); if (!argv.input) { diff --git a/src/commands/schema/diff.mjs b/src/commands/schema/diff.mjs index 572846c5..205c7e8f 100644 --- a/src/commands/schema/diff.mjs +++ b/src/commands/schema/diff.mjs @@ -5,7 +5,6 @@ import chalk from "chalk"; import { container } from "../../cli.mjs"; import { yargsWithCommonQueryOptions } from "../../lib/command-helpers.mjs"; import { ValidationError } from "../../lib/errors.mjs"; -import { getSecret } from "../../lib/fauna-client.mjs"; import { reformatFSL } from "../../lib/schema.mjs"; import { localSchemaOptions } from "./schema.mjs"; @@ -57,11 +56,12 @@ function buildValidateParams(argv, version) { } async function doDiff(argv) { - const [source, target] = parseTarget(argv); - + const { getSecret } = container.resolve("faunaClient"); const gatherFSL = container.resolve("gatherFSL"); const logger = container.resolve("logger"); const makeFaunaRequest = container.resolve("makeFaunaRequest"); + + const [source, target] = parseTarget(argv); const secret = await getSecret(); const files = reformatFSL(await gatherFSL(argv.dir)); diff --git a/src/commands/schema/pull.mjs b/src/commands/schema/pull.mjs index a9ca1c43..27c8e490 100644 --- a/src/commands/schema/pull.mjs +++ b/src/commands/schema/pull.mjs @@ -2,7 +2,6 @@ import { container } from "../../cli.mjs"; import { yargsWithCommonQueryOptions } from "../../lib/command-helpers.mjs"; -import { getSecret } from "../../lib/fauna-client.mjs"; import { localSchemaOptions } from "./schema.mjs"; async function determineFileState(argv, filenames) { @@ -50,9 +49,11 @@ function logDiff({ argv, adds, overwrites, deletes, source }) { } async function doPull(argv) { - const logger = container.resolve("logger"); const confirm = container.resolve("confirm"); + const { getSecret } = container.resolve("faunaClient"); + const logger = container.resolve("logger"); const makeFaunaRequest = container.resolve("makeFaunaRequest"); + const secret = await getSecret(); // Get the staged schema status diff --git a/src/commands/schema/push.mjs b/src/commands/schema/push.mjs index 2b07c5f7..c258cdb3 100644 --- a/src/commands/schema/push.mjs +++ b/src/commands/schema/push.mjs @@ -5,7 +5,6 @@ import path from "path"; import { container } from "../../cli.mjs"; import { yargsWithCommonQueryOptions } from "../../lib/command-helpers.mjs"; import { ValidationError } from "../../lib/errors.mjs"; -import { getSecret } from "../../lib/fauna-client.mjs"; import { reformatFSL } from "../../lib/schema.mjs"; import { localSchemaOptions } from "./schema.mjs"; @@ -14,9 +13,10 @@ import { localSchemaOptions } from "./schema.mjs"; * @param {import("yargs").Argv & {dir: string, active: boolean, input: boolean}} argv */ export async function pushSchema(argv) { + const { getSecret } = container.resolve("faunaClient"); + const gatherFSL = container.resolve("gatherFSL"); const logger = container.resolve("logger"); const makeFaunaRequest = container.resolve("makeFaunaRequest"); - const gatherFSL = container.resolve("gatherFSL"); const isStagedPush = !argv.active; const secret = await getSecret(); diff --git a/src/commands/schema/status.mjs b/src/commands/schema/status.mjs index 48fa1dfb..157a821d 100644 --- a/src/commands/schema/status.mjs +++ b/src/commands/schema/status.mjs @@ -6,17 +6,17 @@ import path from "path"; import { container } from "../../cli.mjs"; import { yargsWithCommonQueryOptions } from "../../lib/command-helpers.mjs"; import { CommandError } from "../../lib/errors.mjs"; -import { getSecret } from "../../lib/fauna-client.mjs"; import { reformatFSL } from "../../lib/schema.mjs"; import { localSchemaOptions } from "./schema.mjs"; async function doStatus(argv) { + const { getSecret } = container.resolve("faunaClient"); + const gatherFSL = container.resolve("gatherFSL"); const logger = container.resolve("logger"); const makeFaunaRequest = container.resolve("makeFaunaRequest"); const secret = await getSecret(); const absoluteDirPath = path.resolve(argv.dir); - const gatherFSL = container.resolve("gatherFSL"); const fslFiles = await gatherFSL(argv.dir); const hasLocalSchema = fslFiles.length > 0; const fsl = reformatFSL(fslFiles); diff --git a/src/commands/shell.mjs b/src/commands/shell.mjs index 549dd4f9..a4647c96 100644 --- a/src/commands/shell.mjs +++ b/src/commands/shell.mjs @@ -12,16 +12,15 @@ import { validateDatabaseOrSecret, yargsWithCommonConfigurableQueryOptions, } from "../lib/command-helpers.mjs"; -import { formatQueryResponse, getSecret } from "../lib/fauna-client.mjs"; import { clearHistoryStorage, initHistoryStorage } from "../lib/file-util.mjs"; async function shellCommand(argv) { + const { getSecret, isQueryable } = container.resolve("faunaClient"); const { query: v4Query } = container.resolve("faunadb"); 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"); @@ -146,9 +145,13 @@ const getArgvOrCtx = (key, argv, ctx) => { // caches the logger, client, and performQuery for subsequent shell calls async function buildCustomEval(argv) { - const formatError = container.resolve("formatError"); - const formatQueryInfo = container.resolve("formatQueryInfo"); - const runQueryFromString = container.resolve("runQueryFromString"); + const { + formatError, + getSecret, + formatQueryInfo, + formatQueryResponse, + runQueryFromString, + } = container.resolve("faunaClient"); return async (cmd, ctx, _filename, cb) => { try { diff --git a/src/config/setup-container.mjs b/src/config/setup-container.mjs index 600ffffe..3e648931 100644 --- a/src/config/setup-container.mjs +++ b/src/config/setup-container.mjs @@ -20,12 +20,7 @@ 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, - formatQueryInfo, - isQueryable, - runQueryFromString, -} from "../lib/fauna-client.mjs"; +import * as faunaClient 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"; @@ -97,12 +92,9 @@ export const injectables = { lifetime: Lifetime.SINGLETON, }), // utilities for interacting with Fauna - runQueryFromString: awilix.asValue(runQueryFromString), - formatError: awilix.asValue(formatError), - formatQueryInfo: awilix.asValue(formatQueryInfo), + faunaClient: awilix.asValue(faunaClient), 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 aa33630b..1e3c5897 100644 --- a/src/config/setup-test-container.mjs +++ b/src/config/setup-test-container.mjs @@ -11,7 +11,7 @@ import { f, InMemoryWritableStream } from "../../test/helpers.mjs"; import { parseYargs } from "../cli.mjs"; import { makeRetryableFaunaRequest } from "../lib/db.mjs"; import * as faunaClientV10 from "../lib/fauna.mjs"; -import { formatQueryInfo } from "../lib/fauna-client.mjs"; +import * as faunaClient from "../lib/fauna-client.mjs"; import * as faunaClientV4 from "../lib/faunadb.mjs"; import buildLogger from "../lib/logger.mjs"; import { injectables, setupCommonContainer } from "./setup-container.mjs"; @@ -104,10 +104,12 @@ export function setupTestContainer() { gatherFSL: awilix.asValue(stub().resolves([])), makeFaunaRequest: awilix.asValue(spy(makeRetryableFaunaRequest)), makeAccountRequest: awilix.asValue(stub()), - runQueryFromString: awilix.asValue(stub().resolves({})), - isQueryable: awilix.asValue(stub().resolves()), - formatError: awilix.asValue(stub()), - formatQueryInfo: awilix.asValue(spy(formatQueryInfo)), + faunaClient: awilix.asValue({ + ...faunaClient, + formatQueryInfo: spy(faunaClient.formatQueryInfo), + runQueryFromString: stub().resolves({}), + isQueryable: stub().resolves(), + }), faunaClientV10: awilix.asValue({ getClient: stub(), runQuery: stub(), diff --git a/src/lib/db.mjs b/src/lib/db.mjs index 1814fe6e..212cf3b6 100644 --- a/src/lib/db.mjs +++ b/src/lib/db.mjs @@ -6,7 +6,6 @@ import { CommandError, NETWORK_ERROR_MESSAGE, } from "./errors.mjs"; -import { retryInvalidCredsOnce } from "./fauna-client.mjs"; function buildParamsString({ argv, params, path }) { const routesWithColor = ["/schema/1/staged/status", "/schema/1/diff"]; @@ -96,6 +95,8 @@ export async function makeFaunaRequest({ * @returns {Promise} */ export function makeRetryableFaunaRequest(opts) { + const { retryInvalidCredsOnce } = container.resolve("faunaClient"); + return retryInvalidCredsOnce(opts.secret, (secret) => makeFaunaRequest({ ...opts, secret }), ); diff --git a/src/lib/fauna-client.mjs b/src/lib/fauna-client.mjs index 59a756cc..1ffcdc83 100644 --- a/src/lib/fauna-client.mjs +++ b/src/lib/fauna-client.mjs @@ -115,7 +115,9 @@ export const formatError = (err, { apiVersion, color }) => { * @param {*} argv */ export const isQueryable = async (argv) => { - const runQueryFromString = container.resolve("runQueryFromString"); + // resolve runQueryFromString at runtime, not necessarily from this module. + const runQueryFromString = + container.resolve("faunaClient").runQueryFromString; try { await runQueryFromString("1+1", argv); } catch (err) { diff --git a/src/lib/schema.mjs b/src/lib/schema.mjs index ab02ea03..8160203f 100644 --- a/src/lib/schema.mjs +++ b/src/lib/schema.mjs @@ -4,7 +4,6 @@ import * as path from "path"; import { container } from "../cli.mjs"; import { makeFaunaRequest } from "../lib/db.mjs"; -import { getSecret } from "./fauna-client.mjs"; import { dirExists, dirIsWriteable } from "./file-util.mjs"; /** @@ -178,6 +177,8 @@ export async function getAllSchemaFileContents( version, argv, ) { + const { getSecret } = container.resolve("faunaClient"); + const promises = []; /** @type Record */ const fileContentCollection = {}; diff --git a/test/credentials.mjs b/test/credentials.mjs index f1305ca9..1a540063 100644 --- a/test/credentials.mjs +++ b/test/credentials.mjs @@ -275,9 +275,9 @@ describe("credentials", function () { // We need to use the original implementation of runQueryFromString to ensure it hits // faunaClientV10.runQueryFromString which is where we force the 401 and test the refresh // logic. - container.register({ - runQueryFromString: awilix.asValue(originalRunQueryFromString), - }); + const faunaClient = container.resolve("faunaClient"); + faunaClient.runQueryFromString = originalRunQueryFromString; + v10runQueryFromString = container.resolve("faunaClientV10").runQueryFromString; }); diff --git a/test/query.mjs b/test/query.mjs index 40a54ce2..9d0ea1bd 100644 --- a/test/query.mjs +++ b/test/query.mjs @@ -17,12 +17,15 @@ import { } from "./helpers.mjs"; describe("query", function () { - let container, logger, runQueryFromString; + let container, formatQueryInfo, logger, runQueryFromString; beforeEach(() => { container = setupContainer(); logger = container.resolve("logger"); - runQueryFromString = container.resolve("runQueryFromString"); + + const faunaClient = container.resolve("faunaClient"); + runQueryFromString = faunaClient.runQueryFromString; + formatQueryInfo = faunaClient.formatQueryInfo; // Set a default empty response for all queries runQueryFromString.resolves({ data: "test" }); @@ -321,7 +324,41 @@ describe("query", function () { ); }); - describe("query info", function () { + describe("--include usage", function () { + it("can set the include option to '[summary]' by default", async function () { + await run(`query "foo" --secret=foo`, container); + + expect(formatQueryInfo.getCall(0).args[1].include).to.deep.equal([ + "summary", + ]); + }); + + it("can set the include option to an array", async function () { + await run( + `query "foo" --secret=foo --include summary stats`, + container, + ); + + expect(formatQueryInfo.getCall(0).args[1].include).to.deep.equal([ + "summary", + "stats", + ]); + }); + + it("can specify '--include all' to set all include options", async function () { + await run(`query "foo" --secret=foo --include all`, container); + + expect(formatQueryInfo.getCall(0).args[1].include).to.deep.equal( + QUERY_INFO_CHOICES, + ); + }); + + it("can specify '--include none' to set no include options", async function () { + await run(`query "foo" --secret=foo --include none`, container); + + expect(formatQueryInfo).to.not.be.called; + }); + it("displays summary by default", async function () { runQueryFromString.resolves({ summary: "info at *query*:1: hello world", diff --git a/test/shell.mjs b/test/shell.mjs index 22acfc17..20a00972 100644 --- a/test/shell.mjs +++ b/test/shell.mjs @@ -12,7 +12,7 @@ import sinon, { stub } from "sinon"; import { run } from "../src/cli.mjs"; import { setupTestContainer as setupContainer } from "../src/config/setup-test-container.mjs"; import { NETWORK_ERROR_MESSAGE, ValidationError } from "../src/lib/errors.mjs"; -import { isQueryable } from "../src/lib/fauna-client.mjs"; +import { isQueryable as originalIsQueryable } from "../src/lib/fauna-client.mjs"; import { dirExists } from "../src/lib/file-util.mjs"; import { colorize } from "../src/lib/formatting/colorize.mjs"; import { createV4QuerySuccess, createV10QuerySuccess } from "./helpers.mjs"; @@ -64,7 +64,7 @@ const sleep = async (ms) => }); describe("shell", function () { - let container, stdin, stdout, stderr, logger, runQueryFromString; + let container, stdin, stdout, stderr, logger, faunaClient, runQueryFromString; const promptReset = "\x1B[1G\x1B[0J> "; const prompt = `${EOL}${promptReset}\x1B[3G`; @@ -83,7 +83,9 @@ describe("shell", function () { stdin = container.resolve("stdinStream"); stdout = container.resolve("stdoutStream"); stderr = container.resolve("stderrStream"); - runQueryFromString = container.resolve("runQueryFromString"); + + faunaClient = container.resolve("faunaClient"); + runQueryFromString = faunaClient.runQueryFromString; }); describe("common", function () { @@ -96,9 +98,9 @@ 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")); + faunaClient.isQueryable.rejects( + new ValidationError("Database not found: us/bad"), + ); const runPromise = run(`shell --format json -d us/bad`, container); try { @@ -111,9 +113,8 @@ describe("shell", function () { it("can handle network errors", async function () { runQueryFromString.rejects(new NetworkError("test error", { cause: {} })); - container.register({ - isQueryable: awilix.asValue(isQueryable), - }); + faunaClient.isQueryable = originalIsQueryable; + const runPromise = run(`shell --format json -d us/bad`, container); try {