Skip to content

Commit

Permalink
Test additions (#390)
Browse files Browse the repository at this point in the history
* rename files

* fixes to schema push and pull

* bug fixes and tests for schema push

* fix strict mode problems

* write tests for schema abandon and commit

* improve schema pull tests
  • Loading branch information
echo-bravo-yahoo authored Oct 11, 2024
1 parent e20c08d commit ffb9854
Show file tree
Hide file tree
Showing 38 changed files with 956 additions and 481 deletions.
9 changes: 8 additions & 1 deletion DEV-README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,12 @@

#### Testing guidelines
- Prefer to mock the "far" edges of the application - methods on `fs`, complex async libraries (e.g., `http#Server`), `fetch`. This results in the test code traversing all of the CLI's business logic, but not interacting with error-prone external resources like disk, network, port availability, etc. `sinon` records all calls to a mock, and allows asserting against them. Use this if, e.g., your business logic calls `fetch` multiple times.
- Prefer to run local tests in watch mode with (e.g., with `yarn local-test`) while developing.
- ~~Prefer to run local tests in watch mode with (e.g., with `yarn local-test`) while developing.~~ This is currently broken.
- Use debug logs to output the shape of objects (especially network responses) to determine how to structure mocks. For instance, to get a quick mock for a network request caused by `fauna schema status ...`, set the situation up and run `fauna schema status ... --verbosity 5`. You can then use the logged objects as mocks.

#### Debugging strategies
- Fetch is not particularly amenable to debugging, but if you need to see the raw requests being made, open a netcat server locally (`nc -l 8080`) and then change the code to call the netcat server, either by passing the flag `--url http://localhost:8080` or by editing the code.
- This project has debug logging with a somewhat configurable logging strategy. To use it, provide either:
- `--verbose-component foo`, which logs all debug info for the component `foo`. Because it's an array, you can specify it multiple times. To see the available components, look at the help or tab completion.
- `--verbosity level`, where `level` is a number from 0 (no debug logging) to 5 (all debug logging) for all components.
- To investigate why a sinon stub is failing in a way you don't expect, you can log out `stub.args`, which is an array of the arguments provided each time the stub was called. This can be particularly helpful for assertions where the error message is hard to read or involves objects that don't stringify correctly (like FormData).
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default [
},
},
{
files: ["yargs-test/**/*.mjs"],
files: ["test/**/*.mjs"],

rules: {
"no-unused-expressions": "off",
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"sinon": "^19.0.2",
"sinon-called-with-diff": "^3.1.1",
"sinon-chai": "^4.0.0",
"try-to-catch": "^3.0.1"
"try-to-catch": "^3.0.1",
"typescript": "^5.6.3"
},
"engines": {
"node": ">=18.0.0"
Expand All @@ -62,10 +63,10 @@
"scripts": {
"postpack": "rm -f oclif.manifest.json",
"pretest": "yarn fixlint",
"local-test": "mocha --recursive ./yargs-test --require ./yargs-test/mocha-root-hooks.mjs",
"local-test": "mocha --recursive ./test --require ./test/mocha-root-hooks.mjs",
"lint": "eslint .",
"fixlint": "eslint . --fix",
"test": "mocha --recursive ./yargs-test --require ./yargs-test/mocha-root-hooks.mjs --reporter spec --reporter mocha-junit-reporter",
"test": "mocha --recursive ./test --require ./test/mocha-root-hooks.mjs --reporter spec --reporter mocha-junit-reporter",
"fmt": "prettier -w src"
},
"husky": {
Expand Down
115 changes: 56 additions & 59 deletions src/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import yargs from "yargs";
import chalk from "chalk";

import evalCommand from "./yargs-commands/eval.mjs";
import loginCommand from "./yargs-commands/login.mjs";
import schemaCommand from "./yargs-commands/schema/schema.mjs";
import databaseCommand from "./yargs-commands/database.mjs";
import keyCommand from "./yargs-commands/key.mjs";
import evalCommand from "./commands/eval.mjs";
import loginCommand from "./commands/login.mjs";
import schemaCommand from "./commands/schema/schema.mjs";
import databaseCommand from "./commands/database.mjs";
import keyCommand from "./commands/key.mjs";
import { logArgv, fixPaths } from "./lib/middleware.mjs";

/** @typedef {import('awilix').AwilixContainer<import('./config/setup-container.mjs').modifiedInjectables>} cliContainer */
Expand All @@ -31,7 +31,7 @@ export async function run(argvInput, _container) {
await parseYargs(builtYargs);
} catch (e) {
const message = `${chalk.reset(await builtYargs.getHelp())}\n\n${chalk.red(
e.message
e.message,
)}`;
logger.stderr(message);
logger.fatal(e.stack, "error");
Expand Down Expand Up @@ -59,57 +59,54 @@ function buildYargs(argvInput) {
// https://github.com/yargs/yargs/blob/main/docs/typescript.md?plain=1#L124
const yargsInstance = yargs(argvInput);

return (
yargsInstance
.scriptName("fauna")
.middleware([logArgv], true)
.middleware([fixPaths], false)
.command("eval", "evaluate a query", evalCommand)
.command("login", "login via website", loginCommand)
.command(keyCommand)
.command(schemaCommand)
.command(databaseCommand)
.command("throw", false, {
handler: () => {
throw new Error("this is a test error");
},
builder: {},
})
.command("reject", false, {
handler: async () => {
throw new Error("this is a rejected promise");
},
builder: {},
})
.demandCommand()
// TODO .strictCommands(true) blows up... why?
.strictOptions(true)
.options({
color: {
description:
"whether or not to emit escape codes for multi-color terminal output.",
type: "boolean",
// https://github.com/chalk/chalk?tab=readme-ov-file#chalklevel
default: chalk.level > 0,
},
verbosity: {
description: "the lowest level diagnostic logs to emit",
type: "number",
default: 0,
},
verboseComponent: {
description:
"components to emit diagnostic logs for; this takes precedence over the 'verbosity' flag",
type: "array",
default: [],
choices: ["fetch", "error", "argv"],
},
})
.wrap(yargsInstance.terminalWidth())
.help("help", "show help")
.fail(false)
.exitProcess(false)
.version(false)
.completion()
);
return yargsInstance
.scriptName("fauna")
.middleware([logArgv], true)
.middleware([fixPaths], false)
.command("eval", "evaluate a query", evalCommand)
.command("login", "login via website", loginCommand)
.command(keyCommand)
.command(schemaCommand)
.command(databaseCommand)
.command("throw", false, {
handler: () => {
throw new Error("this is a test error");
},
builder: {},
})
.command("reject", false, {
handler: async () => {
throw new Error("this is a rejected promise");
},
builder: {},
})
.demandCommand()
.strict(true)
.options({
color: {
description:
"whether or not to emit escape codes for multi-color terminal output.",
type: "boolean",
// https://github.com/chalk/chalk?tab=readme-ov-file#chalklevel
default: chalk.level > 0,
},
verbosity: {
description: "the lowest level diagnostic logs to emit",
type: "number",
default: 0,
},
verboseComponent: {
description:
"components to emit diagnostic logs for; this takes precedence over the 'verbosity' flag",
type: "array",
default: [],
choices: ["fetch", "error", "argv"],
},
})
.wrap(yargsInstance.terminalWidth())
.help("help", "show help")
.fail(false)
.exitProcess(false)
.version(false)
.completion();
}
File renamed without changes.
6 changes: 3 additions & 3 deletions src/yargs-commands/eval.mjs → src/commands/eval.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ async function performV4Query(client, fqlQuery, outputFile, flags) {
{
depth: null,
compact: false,
}
},
);
} else {
error.message = error.faunaError.message;
Expand Down Expand Up @@ -167,7 +167,7 @@ async function doEval(argv) {
!argv.stdin && argv.query === undefined && argv.file === undefined;
if (noSourceSet) {
throw new Error(
"No source set. Pass --stdin to read from stdin or --file."
"No source set. Pass --stdin to read from stdin or --file.",
);
}

Expand Down Expand Up @@ -214,7 +214,7 @@ async function doEval(argv) {
format: format,
version: argv.version,
typecheck: argv.typecheck,
}
},
);

if (result) {
Expand Down
File renamed without changes.
10 changes: 4 additions & 6 deletions src/yargs-commands/login.mjs → src/commands/login.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,17 @@ async function doLogin(argv) {
const accountCreds = container.resolve("accountCreds");
oAuth.server.on("ready", async () => {
const authCodeParams = oAuth.getOAuthParams();
const dashboardOAuthURL = await accountClient.startOAuthRequest(
authCodeParams
);
const dashboardOAuthURL =
await accountClient.startOAuthRequest(authCodeParams);
open(dashboardOAuthURL);
logger.stdout(`To login, open your browser to:\n ${dashboardOAuthURL}`);
});
oAuth.server.on("auth_code_received", async () => {
try {
const tokenParams = oAuth.getTokenParams();
const accessToken = await accountClient.getToken(tokenParams);
const { account_key, refresh_token } = await accountClient.getSession(
accessToken
);
const { account_key, refresh_token } =
await accountClient.getSession(accessToken);
accountCreds.save({
creds: { account_key, refresh_token },
profile: argv.profile,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
//@ts-check

import { confirm } from "@inquirer/prompts";

import { commonQueryOptions } from "../../lib/command-helpers.mjs";
import { container } from "../../cli.mjs";

async function doAbandon(argv) {
const makeFaunaRequest = container.resolve("makeFaunaRequest");
const logger = container.resolve("logger");
const confirm = container.resolve("confirm");

if (argv.force) {
const params = new URLSearchParams({
Expand All @@ -20,7 +19,7 @@ async function doAbandon(argv) {
secret: argv.secret,
method: "POST",
});
logger.stdout("Schema has been abandonded");
logger.stdout("Schema has been abandoned");
} else {
// Show status to confirm.
const params = new URLSearchParams({ diff: "true" });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
//@ts-check

import { confirm } from "@inquirer/prompts";

import { commonQueryOptions } from "../../lib/command-helpers.mjs";
import { container } from "../../cli.mjs";

async function doCommit(argv) {
const makeFaunaRequest = container.resolve("makeFaunaRequest");
const logger = container.resolve("logger");
const confirm = container.resolve("confirm");

if (argv.force) {
const params = new URLSearchParams({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import chalk from "chalk";

import { container } from "../../cli.mjs";
import { commonQueryOptions } from "../../lib/command-helpers.mjs";
import { reformatFSL } from "../../lib/schema.mjs";

async function doDiff(argv) {
const gatherFSL = container.resolve("gatherFSL");
const logger = container.resolve("logger");
const makeFaunaRequest = container.resolve("makeFaunaRequest");

const files = await gatherFSL(argv.dir);
const files = reformatFSL(await gatherFSL(argv.dir));

const params = new URLSearchParams({ force: "true" });
if (argv.color) params.set("color", "ansii");
if (argv.color) params.set("color", "ansi");
params.set("staged", argv.staged);

const response = await makeFaunaRequest({
Expand All @@ -28,8 +29,8 @@ async function doDiff(argv) {
const description = argv.staged ? "remote, staged" : "remote, active";
logger.stdout(
`Differences between the ${bold("local")} schema and the ${bold(
description
)} schema:`
description,
)} schema:`,
);
logger.stdout(response.diff ? response.diff : "No schema differences");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ async function doPull(argv) {
// implemented at the service level.
if (statusResponse.status !== "none" && !argv.staged) {
throw new Error(
"There is a staged schema change. Use --staged to pull it."
"There is a staged schema change. Use --staged to pull it.",
);
} else if (statusResponse.status === "none" && argv.staged) {
throw new Error("There are no staged schema changes to pull.");
Expand Down Expand Up @@ -83,7 +83,7 @@ async function doPull(argv) {
if (confirmed) {
const writeSchemaFiles = container.resolve("writeSchemaFiles");
const getAllSchemaFileContents = container.resolve(
"getAllSchemaFileContents"
"getAllSchemaFileContents",
);
const contents = await getAllSchemaFileContents(filenames, {
secret: argv.secret,
Expand All @@ -96,7 +96,7 @@ async function doPull(argv) {
promises.push(writeSchemaFiles(argv.dir, contents));
if (argv.delete) {
const deleteUnusedSchemaFiles = container.resolve(
"deleteUnusedSchemaFiles"
"deleteUnusedSchemaFiles",
);
promises.push(deleteUnusedSchemaFiles(argv.dir, deletes));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
//@ts-check

import { container } from "../../cli.mjs";
import { confirm } from "@inquirer/prompts";
import { commonQueryOptions } from "../../lib/command-helpers.mjs";
import { reformatFSL } from "../../lib/schema.mjs";

async function doPush(argv) {
const logger = container.resolve("logger");
const makeFaunaRequest = container.resolve("makeFaunaRequest");

const gatherFSL = container.resolve("gatherFSL");
const fsl = await gatherFSL(argv.dir);
const fsl = reformatFSL(await gatherFSL(argv.dir));

if (argv.force) {
const params = new URLSearchParams({
force: argv.force,
staged: argv.staged,
staged: argv.staged ? "true" : "false",
});

await makeFaunaRequest({
Expand All @@ -26,7 +27,10 @@ async function doPush(argv) {
} else {
// Confirm diff, then push it. `force` is set on `validate` so we don't
// need to pass the last known schema version through.
const params = new URLSearchParams({ force: "true" });
const params = new URLSearchParams({
force: "true",
staged: argv.staged ? "true" : "false",
});
if (argv.color) params.set("color", "ansi");

const response = await makeFaunaRequest({
Expand All @@ -45,6 +49,7 @@ async function doPush(argv) {
logger.stdout("No logical changes.");
message = "Push file contents anyway?";
}
const confirm = container.resolve("confirm");
const confirmed = await confirm({
message,
default: false,
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ async function doStatus(argv) {
const makeFaunaRequest = container.resolve("makeFaunaRequest");

const params = new URLSearchParams({ diff: "true" });
if (argv.color) params.set("color", "ansii");
if (argv.color) params.set("color", "ansi");

const response = await makeFaunaRequest({
baseUrl: argv.url,
Expand Down
2 changes: 1 addition & 1 deletion src/config/setup-container.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { exit } from "node:process";

import * as awilix from "awilix";

import { performQuery } from "../yargs-commands/eval.mjs";
import { performQuery } from "../commands/eval.mjs";
import logger from "../lib/logger.mjs";
import { getSimpleClient } from "../lib/command-helpers.mjs";
import {
Expand Down
Loading

0 comments on commit ffb9854

Please sign in to comment.