From eab23cad11b6365f8199278777fecff65132d876 Mon Sep 17 00:00:00 2001 From: Henry Ball Date: Mon, 25 Nov 2024 14:16:44 -0800 Subject: [PATCH 1/3] setup delete database --- src/commands/database/create.mjs | 5 +- src/commands/database/database.mjs | 6 +- src/commands/database/delete.mjs | 49 ++++++++++++++++ src/lib/fauna.mjs | 5 +- test/database/create.mjs | 2 +- test/database/delete.mjs | 90 ++++++++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 src/commands/database/delete.mjs create mode 100644 test/database/delete.mjs diff --git a/src/commands/database/create.mjs b/src/commands/database/create.mjs index 5e14c7bf..8a112785 100644 --- a/src/commands/database/create.mjs +++ b/src/commands/database/create.mjs @@ -1,10 +1,9 @@ //@ts-check import { FaunaError, fql } from "fauna"; - import { container } from "../../cli.mjs"; -import { commonQueryOptions } from "../../lib/command-helpers.mjs"; import { throwForV10Error } from "../../lib/fauna.mjs"; +import { commonQueryOptions } from "../../lib/command-helpers.mjs"; async function createDatabase(argv) { const logger = container.resolve("logger"); @@ -21,7 +20,7 @@ async function createDatabase(argv) { priority: ${argv.priority ?? null}, })`, }); - logger.stdout(`Database ${argv.name} created`); + logger.stdout(`Database '${argv.name}' was successfully created.`); } catch (e) { if (e instanceof FaunaError) { throwForV10Error(e, { diff --git a/src/commands/database/database.mjs b/src/commands/database/database.mjs index 4e45acc1..5a0e9b1e 100644 --- a/src/commands/database/database.mjs +++ b/src/commands/database/database.mjs @@ -1,7 +1,8 @@ //@ts-check -import createCommand from "./create.mjs"; import listCommand from "./list.mjs"; +import createCommand from "./create.mjs"; +import deleteCommand from "./delete.mjs"; function buildDatabase(yargs) { return yargs @@ -12,8 +13,9 @@ function buildDatabase(yargs) { default: "default", }, }) - .command(createCommand) .command(listCommand) + .command(createCommand) + .command(deleteCommand) .demandCommand() .version(false) .help("help", "show help"); diff --git a/src/commands/database/delete.mjs b/src/commands/database/delete.mjs new file mode 100644 index 00000000..6ca8dc02 --- /dev/null +++ b/src/commands/database/delete.mjs @@ -0,0 +1,49 @@ +//@ts-check + +import { FaunaError, fql } from "fauna"; +import { container } from "../../cli.mjs"; +import { throwForV10Error } from "../../lib/fauna.mjs"; +import { commonQueryOptions } from "../../lib/command-helpers.mjs"; + +async function deleteDatabase(argv) { + const logger = container.resolve("logger"); + const runV10Query = container.resolve("runV10Query"); + + try { + await runV10Query({ + url: argv.url, + secret: argv.secret, + query: fql`Database.byName(${argv.name}).delete()`, + }); + logger.stdout(`Database '${argv.name}' was successfully deleted.`); + } catch (e) { + if (e instanceof FaunaError) { + throwForV10Error(e, { + onDocumentNotFound: () => + `Not found: Database '${argv.name}' not found. Please check the database name and try again.`, + }); + } + throw e; + } +} + +function buildDeleteCommand(yargs) { + return yargs + .options({ + name: { + type: "string", + description: "the name of the database to delete", + }, + ...commonQueryOptions, + }) + .demandOption("name") + .version(false) + .help("help", "show help"); +} + +export default { + command: "delete", + description: "Deletes a database", + builder: buildDeleteCommand, + handler: deleteDatabase, +}; diff --git a/src/lib/fauna.mjs b/src/lib/fauna.mjs index d8814c2a..5e1404e1 100644 --- a/src/lib/fauna.mjs +++ b/src/lib/fauna.mjs @@ -100,6 +100,7 @@ export const runV10Query = async ({ * @param {(e: ServiceError) => string} [handlers.onLimitExceeded] - Handler for rate/resource limit errors * @param {(e: ServiceError) => string} [handlers.onTimeOut] - Handler for timeout errors * @param {(e: ServiceError) => string} [handlers.onInternalError] - Handler for internal server errors + * @param {(e: ServiceError) => string} [handlers.onDocumentNotFound] - Handler for document not found errors * @param {(e: ClientError) => string} [handlers.onClientError] - Handler for general client errors * @param {(e: ClientClosedError) => string} [handlers.onClientClosedError] - Handler for closed client errors * @param {(e: NetworkError) => string} [handlers.onNetworkError] - Handler for network-related errors @@ -121,7 +122,7 @@ export const throwForV10Error = (e, handlers = {}) => { case "unauthorized": throw new Error( handlers.onUnauthorized?.(e) ?? - "Authentication failed: Please either log in using 'fauna login' or provide a valid database secret with '--secret'", + "Authentication failed: Please either log in using 'fauna login' or provide a valid database secret with '--secret'.", ); case "forbidden": throw new Error(handlers.onForbidden?.(e) ?? e.message); @@ -133,6 +134,8 @@ export const throwForV10Error = (e, handlers = {}) => { throw new Error(handlers.onTimeOut?.(e) ?? e.message); case "internal_error": throw new Error(handlers.onInternalError?.(e) ?? e.message); + case "document_not_found": + throw new Error(handlers.onDocumentNotFound?.(e) ?? e.message); default: throw e; } diff --git a/test/database/create.mjs b/test/database/create.mjs index 1b51bcfe..4376f1b8 100644 --- a/test/database/create.mjs +++ b/test/database/create.mjs @@ -84,7 +84,7 @@ describe("database create", () => { error: { code: "unauthorized", message: "whatever" }, }), expectedMessage: - "Authentication failed: Please either log in using 'fauna login' or provide a valid database secret with '--secret'", + "Authentication failed: Please either log in using 'fauna login' or provide a valid database secret with '--secret'.", }, ].forEach(({ error, expectedMessage }) => { it(`handles ${error.code} errors when calling fauna`, async () => { diff --git a/test/database/delete.mjs b/test/database/delete.mjs new file mode 100644 index 00000000..e115f5c6 --- /dev/null +++ b/test/database/delete.mjs @@ -0,0 +1,90 @@ +//@ts-check + +import * as awilix from "awilix"; +import { expect } from "chai"; +import chalk from "chalk"; +import { fql, ServiceError } from "fauna"; +import sinon from "sinon"; + +import { builtYargs, run } from "../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; + +describe("database delete", () => { + let container, logger, runV10Query; + + beforeEach(() => { + // reset the container before each test + container = setupContainer(); + logger = container.resolve("logger"); + runV10Query = container.resolve("runV10Query"); + }); + + [ + { missing: "name", command: "database delete --secret 'secret'" }, + { missing: "secret", command: "database delete --name 'name'" }, + ].forEach(({ missing, command }) => { + it(`requires a ${missing}`, async () => { + try { + await run(command, container); + } catch (e) {} + + const message = `${chalk.reset(await builtYargs.getHelp())}\n\n${chalk.red( + `Missing required argument: ${missing}`, + )}`; + expect(logger.stderr).to.have.been.calledWith(message); + expect(container.resolve("parseYargs")).to.have.been.calledOnce; + }); + }); + + [ + { + args: "--name 'testdb' --secret 'secret'", + expected: { name: "testdb", secret: "secret" }, + }, + ].forEach(({ args, expected }) => { + describe("calls fauna with the user specified arguments", () => { + it(`${args}`, async () => { + await run(`database create ${args}`, container); + expect(runV10Query).to.have.been.calledOnceWith({ + url: sinon.match.string, + secret: expected.secret, + query: fql`Database.byName(${expected.name}).delete()`, + }); + }); + }); + }); + + [ + { + error: new ServiceError({ + error: { code: "unauthorized", message: "whatever" }, + }), + expectedMessage: + "Authentication failed: Please either log in using 'fauna login' or provide a valid database secret with '--secret'.", + }, + { + error: new ServiceError({ + error: { code: "document_not_found", message: "whatever" }, + }), + expectedMessage: + "Not found: Database 'testdb' not found. Please check the database name and try again.", + }, + ].forEach(({ error, expectedMessage }) => { + it(`handles ${error.code} errors when calling fauna`, async () => { + const runV10QueryStub = sinon.stub().rejects(error); + container.register({ + runV10Query: awilix.asValue(runV10QueryStub), + }); + + try { + await run( + `database delete --name 'testdb' --secret 'secret'`, + container, + ); + } catch (e) {} + + const message = `${chalk.reset(await builtYargs.getHelp())}\n\n${chalk.red(expectedMessage)}`; + expect(logger.stderr).to.have.been.calledWith(message); + }); + }); +}); From c7c8ea88d7d095c95bacae71c4b9247c385e0b57 Mon Sep 17 00:00:00 2001 From: Henry Ball Date: Mon, 25 Nov 2024 14:28:58 -0800 Subject: [PATCH 2/3] fixup --- test/database/create.mjs | 7 +++---- test/database/delete.mjs | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/test/database/create.mjs b/test/database/create.mjs index 4376f1b8..05c8d33c 100644 --- a/test/database/create.mjs +++ b/test/database/create.mjs @@ -1,11 +1,10 @@ //@ts-check -import * as awilix from "awilix"; -import { expect } from "chai"; +import sinon from "sinon"; import chalk from "chalk"; +import { expect } from "chai"; +import * as awilix from "awilix"; import { fql, ServiceError } from "fauna"; -import sinon from "sinon"; - import { builtYargs, run } from "../../src/cli.mjs"; import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; diff --git a/test/database/delete.mjs b/test/database/delete.mjs index e115f5c6..d1076ac6 100644 --- a/test/database/delete.mjs +++ b/test/database/delete.mjs @@ -1,11 +1,10 @@ //@ts-check -import * as awilix from "awilix"; -import { expect } from "chai"; import chalk from "chalk"; -import { fql, ServiceError } from "fauna"; import sinon from "sinon"; - +import { expect } from "chai"; +import * as awilix from "awilix"; +import { fql, ServiceError } from "fauna"; import { builtYargs, run } from "../../src/cli.mjs"; import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; From 7624fddab4d24b9bbe6ed8b0247a36ee2c8f6130 Mon Sep 17 00:00:00 2001 From: Henry Ball Date: Mon, 25 Nov 2024 14:55:46 -0800 Subject: [PATCH 3/3] address comments --- src/commands/database/create.mjs | 2 +- src/commands/database/delete.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/database/create.mjs b/src/commands/database/create.mjs index 8a112785..8a7ab5ab 100644 --- a/src/commands/database/create.mjs +++ b/src/commands/database/create.mjs @@ -37,6 +37,7 @@ function buildCreateCommand(yargs) { .options({ name: { type: "string", + required: true, description: "the name of the database to create", }, typechecked: { @@ -53,7 +54,6 @@ function buildCreateCommand(yargs) { }, ...commonQueryOptions, }) - .demandOption("name") .version(false) .help("help", "show help"); } diff --git a/src/commands/database/delete.mjs b/src/commands/database/delete.mjs index 6ca8dc02..e6de7614 100644 --- a/src/commands/database/delete.mjs +++ b/src/commands/database/delete.mjs @@ -32,11 +32,11 @@ function buildDeleteCommand(yargs) { .options({ name: { type: "string", + required: true, description: "the name of the database to delete", }, ...commonQueryOptions, }) - .demandOption("name") .version(false) .help("help", "show help"); }