Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test additions #390

Merged
merged 6 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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).
Comment on lines -7 to +15
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly useful notes here

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