diff --git a/src/commands/schema/abandon.mjs b/src/commands/schema/abandon.mjs index 7926fc4c..52df7175 100644 --- a/src/commands/schema/abandon.mjs +++ b/src/commands/schema/abandon.mjs @@ -8,15 +8,14 @@ async function doAbandon(argv) { const logger = container.resolve("logger"); const confirm = container.resolve("confirm"); - if (argv.force) { + if (!argv.input) { const params = new URLSearchParams({ force: "true", // Just abandon, don't pass a schema version through. }); await makeFaunaRequest({ - baseUrl: argv.url, + argv, path: `/schema/1/staged/abandon?${params}`, - secret: argv.secret, method: "POST", }); logger.stdout("Schema has been abandoned"); @@ -26,9 +25,8 @@ async function doAbandon(argv) { if (argv.color) params.set("color", "ansi"); const response = await makeFaunaRequest({ - baseUrl: argv.url, + argv, path: `/schema/1/staged/status?${params}`, - secret: argv.secret, method: "GET", }); @@ -46,9 +44,8 @@ async function doAbandon(argv) { const params = new URLSearchParams({ version: response.version }); await makeFaunaRequest({ - baseUrl: argv.url, + argv, path: `/schema/1/staged/abandon?${params}`, - secret: argv.secret, method: "POST", }); @@ -62,10 +59,10 @@ async function doAbandon(argv) { function buildAbandonCommand(yargs) { return yargs .options({ - force: { - description: "Push the change without a diff or schema version check", + input: { + description: "Prompt for user input (e.g., confirmations)", + default: true, type: "boolean", - default: false, }, ...commonQueryOptions, }) diff --git a/src/commands/schema/commit.mjs b/src/commands/schema/commit.mjs index 603e965d..ae68b0c0 100644 --- a/src/commands/schema/commit.mjs +++ b/src/commands/schema/commit.mjs @@ -8,15 +8,14 @@ async function doCommit(argv) { const logger = container.resolve("logger"); const confirm = container.resolve("confirm"); - if (argv.force) { + if (!argv.input) { const params = new URLSearchParams({ force: "true", // Just commit, don't pass a schema version through. }); await makeFaunaRequest({ - baseUrl: argv.url, + argv, path: `/schema/1/staged/commit?${params}`, - secret: argv.secret, method: "POST", }); @@ -27,9 +26,8 @@ async function doCommit(argv) { if (argv.color) params.set("color", "ansi"); const response = await makeFaunaRequest({ - baseUrl: argv.url, + argv, path: `/schema/1/staged/status?${params}`, - secret: argv.secret, method: "GET", }); @@ -50,9 +48,8 @@ async function doCommit(argv) { const params = new URLSearchParams({ version: response.version }); await makeFaunaRequest({ - baseUrl: argv.url, + argv, path: `/schema/1/staged/commit?${params}`, - secret: argv.secret, method: "POST", }); @@ -66,10 +63,10 @@ async function doCommit(argv) { function buildCommitCommand(yargs) { return yargs .options({ - force: { - description: "Push the change without a diff or schema version check", + input: { + description: "Prompt for user input (e.g., confirmations)", + default: true, type: "boolean", - default: false, }, ...commonQueryOptions, }) diff --git a/src/commands/schema/diff.mjs b/src/commands/schema/diff.mjs index 821e17d8..534ea7ba 100644 --- a/src/commands/schema/diff.mjs +++ b/src/commands/schema/diff.mjs @@ -6,33 +6,92 @@ import { container } from "../../cli.mjs"; import { commonQueryOptions } from "../../lib/command-helpers.mjs"; import { reformatFSL } from "../../lib/schema.mjs"; +/** + * @returns string[] + */ +function parseTarget(argv) { + if (!argv.active && !argv.staged) { + return ["staged", "local"]; + } + + if (argv.active && argv.staged) { + throw new Error("Cannot specify both --active and --staged"); + } + + if (argv.active) { + return ["active", "local"]; + } else if (argv.staged) { + return ["active", "staged"]; + } else { + throw new Error("Invalid target. Expected: active or staged"); + } +} + async function doDiff(argv) { + const [source, target] = parseTarget(argv); + const diffKind = argv.text ? "textual" : "semantic"; + const gatherFSL = container.resolve("gatherFSL"); const logger = container.resolve("logger"); const makeFaunaRequest = container.resolve("makeFaunaRequest"); const files = reformatFSL(await gatherFSL(argv.dir)); - const params = new URLSearchParams({ force: "true" }); + const params = new URLSearchParams({}); if (argv.color) params.set("color", "ansi"); - params.set("staged", argv.staged); - - const response = await makeFaunaRequest({ - baseUrl: argv.url, - path: new URL(`/schema/1/validate?${params}`, argv.url).href, - secret: argv.secret, - body: files, - method: "POST", + if (target === "staged") params.set("diff", diffKind); + + const { version, status, diff } = await makeFaunaRequest({ + argv, + path: "/schema/1/staged/status", + params, + method: "GET", }); - const bold = argv.color ? chalk.bold : (str) => str; - const description = argv.staged ? "remote, staged" : "remote, active"; - logger.stdout( - `Differences between the ${bold("local")} schema and the ${bold( - description, - )} schema:`, - ); - logger.stdout(response.diff ? response.diff : "No schema differences"); + if (target === "staged") { + logger.stdout( + `Differences from the ${chalk.bold("remote, active")} schema to the ${chalk.bold("remote, staged")} schema:`, + ); + if (status === "none") { + logger.stdout("There is no staged schema present."); + } else { + logger.stdout(diff ? diff : "No schema differences."); + } + } else { + const params = new URLSearchParams({ + diff: diffKind, + staged: String(source === "staged"), + }); + if (argv.color) params.set("color", "ansi"); + if (version) { + params.set("version", version); + } else { + params.set("force", "true"); + } + + const { diff } = await makeFaunaRequest({ + argv, + path: "/schema/1/validate", + params, + body: files, + method: "POST", + }); + + if (status === "none") { + logger.stdout( + `Differences from the ${chalk.bold("remote")} schema to the ${chalk.bold("local")} schema:`, + ); + } else if (source === "active") { + logger.stdout( + `Differences from the ${chalk.bold("remote, active")} schema to the ${chalk.bold("local")} schema:`, + ); + } else { + logger.stdout( + `Differences from the ${chalk.bold("remote, staged")} schema to the ${chalk.bold("local")} schema:`, + ); + } + logger.stdout(diff ? diff : "No schema differences."); + } } function buildDiffCommand(yargs) { @@ -40,13 +99,29 @@ function buildDiffCommand(yargs) { .options({ staged: { description: - "Compare the local schema to the staged schema instead of the active schema.", + "Show the diff between the active and staged schema, instead of the local schema.", + default: false, + type: "boolean", + }, + text: { + description: "Display the text diff instead of the semantic diff.", + default: false, + type: "boolean", + }, + active: { + description: + "Show the diff against the active schema instead of the staged schema.", default: false, type: "boolean", }, ...commonQueryOptions, }) - .example([["$0 schema diff"], ["$0 schema diff --dir schemas/myschema"]]) + .example([ + ["$0 schema diff"], + ["$0 schema diff --dir schemas/myschema"], + ["$0 schema diff --staged"], + ["$0 schema diff --active --text"], + ]) .version(false) .help("help", "show help"); } diff --git a/src/commands/schema/pull.mjs b/src/commands/schema/pull.mjs index db00987f..cec54704 100644 --- a/src/commands/schema/pull.mjs +++ b/src/commands/schema/pull.mjs @@ -2,35 +2,36 @@ import { container } from "../../cli.mjs"; import { commonQueryOptions } from "../../lib/command-helpers.mjs"; +import { makeFaunaRequest } from "../../lib/db.mjs"; async function doPull(argv) { const logger = container.resolve("logger"); const gatherFSL = container.resolve("gatherFSL"); const confirm = container.resolve("confirm"); - const getSchemaFiles = container.resolve("getSchemaFiles"); - const getStagedSchemaStatus = container.resolve("getStagedSchemaStatus"); // fetch the list of remote FSL files - const filesResponse = await getSchemaFiles({ - secret: argv.secret, - baseUrl: argv.url, + const filesResponse = await makeFaunaRequest({ + argv, + path: "/schema/1/files", + method: "GET", }); // check if there's a staged schema - const statusResponse = await getStagedSchemaStatus({ - params: { version: filesResponse.version }, - baseUrl: argv.url, - secret: argv.secret, + const statusResponse = await makeFaunaRequest({ + argv, + path: "/schema/1/staged/status", + params: new URLSearchParams({ version: filesResponse.version }), + method: "GET", }); - // if there's a staged schema, require the --staged flag. - // getting unstaged FSL while staged FSL exists is not yet + // if there's a staged schema, cannot use the --active flag. + // getting active FSL while staged FSL exists is not yet // implemented at the service level. - if (statusResponse.status !== "none" && !argv.staged) { + if (statusResponse.status !== "none" && argv.active) { throw new Error( - "There is a staged schema change. Use --staged to pull it.", + "There is a staged schema change. Remove the --active flag to pull it.", ); - } else if (statusResponse.status === "none" && argv.staged) { + } else if (statusResponse.status === "none" && !argv.active) { throw new Error("There are no staged schema changes to pull."); } @@ -85,10 +86,7 @@ async function doPull(argv) { const getAllSchemaFileContents = container.resolve( "getAllSchemaFileContents", ); - const contents = await getAllSchemaFileContents(filenames, { - secret: argv.secret, - baseUrl: argv.url, - }); + const contents = await getAllSchemaFileContents(filenames, argv); // don't start writing or deleting files until we've successfully fetched all // the remote schema files @@ -117,8 +115,8 @@ function buildPullCommand(yargs) { type: "boolean", default: false, }, - staged: { - description: "Pulls staged schema instead of the active schema", + active: { + description: "Pulls the active schema instead of the staged schema", type: "boolean", default: false, }, @@ -126,7 +124,7 @@ function buildPullCommand(yargs) { }) .example([ ["$0 schema pull"], - ["$0 schema pull --staged"], + ["$0 schema pull --active"], ["$0 schema pull --delete"], ]) .version(false) diff --git a/src/commands/schema/push.mjs b/src/commands/schema/push.mjs index 4db34d94..9445e3a4 100644 --- a/src/commands/schema/push.mjs +++ b/src/commands/schema/push.mjs @@ -11,17 +11,19 @@ async function doPush(argv) { const gatherFSL = container.resolve("gatherFSL"); const fsl = reformatFSL(await gatherFSL(argv.dir)); - if (argv.force) { + const isStagedPush = !argv.active; + + if (!argv.input) { const params = new URLSearchParams({ - force: argv.force, - staged: argv.staged ? "true" : "false", + force: "true", + staged: argv.active ? "false" : "true", }); await makeFaunaRequest({ - baseUrl: argv.url, - path: `/schema/1/update?${params}`, + argv, + path: "/schema/1/update", + params, body: fsl, - secret: argv.secret, method: "POST", }); } else { @@ -29,25 +31,29 @@ async function doPush(argv) { // need to pass the last known schema version through. const params = new URLSearchParams({ force: "true", - staged: argv.staged ? "true" : "false", + staged: argv.active ? "false" : "true", }); if (argv.color) params.set("color", "ansi"); const response = await makeFaunaRequest({ - baseUrl: argv.url, - path: `/schema/1/validate?${params}`, + argv, + path: "/schema/1/validate", + params, body: fsl, - secret: argv.secret, method: "POST", }); - let message = "Accept and push changes?"; + let message = isStagedPush + ? "Stage the above changes?" + : "Push the above changes?"; if (response.diff) { logger.stdout(`Proposed diff:\n`); logger.stdout(response.diff); } else { logger.stdout("No logical changes."); - message = "Push file contents anyway?"; + message = isStagedPush + ? "Stage the file contents anyway?" + : "Push the file contents anyway?"; } const confirm = container.resolve("confirm"); const confirmed = await confirm({ @@ -58,14 +64,14 @@ async function doPush(argv) { if (confirmed) { const params = new URLSearchParams({ version: response.version, - staged: argv.staged ? "true" : "false", + staged: argv.active ? "false" : "true", }); await makeFaunaRequest({ - baseUrl: argv.url, - path: `/schema/1/update?${params}`, + argv, + path: "/schema/1/update", + params, body: fsl, - secret: argv.secret, method: "POST", }); } else { @@ -77,14 +83,14 @@ async function doPush(argv) { function buildPushCommand(yargs) { return yargs .options({ - force: { - description: "Push the change without a diff or schema version check", + input: { + description: "Prompt for user input (e.g., confirmations)", + default: true, type: "boolean", - default: false, }, - staged: { + active: { description: - "Stages the schema change instead of applying it immediately", + "Immediately applies the schema change instead of staging it", type: "boolean", default: false, }, @@ -93,7 +99,7 @@ function buildPushCommand(yargs) { .example([ ["$0 schema push"], ["$0 schema push --dir schemas/myschema"], - ["$0 schema push --staged"], + ["$0 schema push --active"], ]) .version(false) .help("help", "show help"); diff --git a/src/commands/schema/status.mjs b/src/commands/schema/status.mjs index b07a17ae..7211c7c6 100644 --- a/src/commands/schema/status.mjs +++ b/src/commands/schema/status.mjs @@ -1,23 +1,61 @@ //@ts-check +import chalk from "chalk"; + import { container } from "../../cli.mjs"; import { commonQueryOptions } from "../../lib/command-helpers.mjs"; +import { reformatFSL } from "../../lib/schema.mjs"; async function doStatus(argv) { const logger = container.resolve("logger"); const makeFaunaRequest = container.resolve("makeFaunaRequest"); - const params = new URLSearchParams({ diff: "true" }); + let params = new URLSearchParams({ diff: "summary" }); if (argv.color) params.set("color", "ansi"); + const gatherFSL = container.resolve("gatherFSL"); + const fsl = reformatFSL(await gatherFSL(argv.dir)); - const response = await makeFaunaRequest({ - baseUrl: argv.url, - path: `/schema/1/staged/status?${params}`, - secret: argv.secret, + const statusResponse = await makeFaunaRequest({ + argv, + path: "/schema/1/staged/status", + params, method: "GET", }); - logger.stdout(response.diff); + params = new URLSearchParams({ + diff: "summary", + staged: "true", + version: statusResponse.version, + }); + if (argv.color) params.set("color", "ansi"); + const validationResponse = await makeFaunaRequest({ + argv, + path: "/schema/1/validate", + params, + method: "POST", + body: fsl, + }); + + logger.stdout(`Staged changes: ${chalk.bold(statusResponse.status)}`); + if (statusResponse.pending_summary !== "") { + logger.stdout(statusResponse.pending_summary); + } + if (statusResponse.diff) { + logger.stdout("Staged changes:\n"); + logger.stdout(statusResponse.diff.split("\n").join("\n ")); + } + + if (validationResponse.error) { + logger.stdout(`Local changes:`); + throw new Error(validationResponse.error.message); + } else if (validationResponse.diff === "") { + logger.stdout(`Local changes: ${chalk.bold("none")}\n`); + } else { + logger.stdout(`Local changes:\n`); + logger.stdout(` ${validationResponse.diff.split("\n").join("\n ")}`); + logger.stdout("(use `fauna schema diff` to display local changes)"); + logger.stdout("(use `fauna schema push` to stage local changes)"); + } } function buildStatusCommand(yargs) { diff --git a/src/config/setup-container.mjs b/src/config/setup-container.mjs index 2ddbe216..df64ec38 100644 --- a/src/config/setup-container.mjs +++ b/src/config/setup-container.mjs @@ -8,7 +8,6 @@ import { confirm } from "@inquirer/prompts"; import * as awilix from "awilix"; import { Lifetime } from "awilix"; import open from "open"; -import updateNotifier from "update-notifier"; import { parseYargs } from "../cli.mjs"; import { performQuery } from "../commands/eval.mjs"; @@ -24,9 +23,6 @@ import { gatherFSL, gatherRelativeFSLFilePaths, getAllSchemaFileContents, - getSchemaFile, - getSchemaFiles, - getStagedSchemaStatus, writeSchemaFiles, } from "../lib/schema.mjs"; @@ -64,7 +60,6 @@ export const injectables = { // third-party libraries confirm: awilix.asValue(confirm), open: awilix.asValue(open), - updateNotifier: awilix.asValue(updateNotifier), // generic lib (homemade utilities) parseYargs: awilix.asValue(parseYargs), @@ -83,11 +78,8 @@ export const injectables = { // feature-specific lib (homemade utilities) gatherFSL: awilix.asValue(gatherFSL), gatherRelativeFSLFilePaths: awilix.asValue(gatherRelativeFSLFilePaths), - getSchemaFile: awilix.asValue(getSchemaFile), - getSchemaFiles: awilix.asValue(getSchemaFiles), writeSchemaFiles: awilix.asValue(writeSchemaFiles), getAllSchemaFileContents: awilix.asValue(getAllSchemaFileContents), - getStagedSchemaStatus: awilix.asValue(getStagedSchemaStatus), deleteUnusedSchemaFiles: awilix.asValue(deleteUnusedSchemaFiles), }; diff --git a/src/lib/db.mjs b/src/lib/db.mjs index b2e11c25..75d30ae6 100644 --- a/src/lib/db.mjs +++ b/src/lib/db.mjs @@ -8,10 +8,9 @@ import { container } from "../cli.mjs"; /** * @typedef {Object} fetchParameters - * @property {string} secret - The secret to include in the AUTHORIZATION header of the request. - * @property {string} baseUrl - The base URL from the scheme up through the top level domain and optional port; defaults to "https://db.fauna.com:443". + * @property {Object} argv - The parsed argv from yargs; used to find the base url (`argv.url`), secret (`argv.secret`), and color support (`argv.color`). To overwrite, provided a modified argv to `makeFaunaRequest`. * @property {string} path - The path part of the URL. Added to the baseUrl and params to build the full URL. - * @property {Record} [params] - The parameters (and their values) to set in the query string. + * @property {URLSearchParams|undefined} [params] - The parameters (and their values) to set in the query string. * @property {method} method - The HTTP method to use when making the request. * @property {object} [body] - The body to include in the request. * @property {boolean} [shouldThrow=true] - Whether or not to throw if the network request succeeds but is not a 2XX. If this is set to false, makeFaunaRequest will return the error instead of throwing. @@ -21,8 +20,7 @@ import { container } from "../cli.mjs"; * @param {fetchParameters} args */ export async function makeFaunaRequest({ - secret, - baseUrl, + argv, path, params = undefined, method, @@ -30,13 +28,17 @@ export async function makeFaunaRequest({ shouldThrow = true, }) { const fetch = container.resolve("fetch"); - const paramsString = params ? `?${new URLSearchParams(params)}` : ""; + const routesWithColor = ["/schema/1/staged/status", "/schema/1/validate"]; + if (params && argv.color && routesWithColor.includes(path)) + params.set("color", "ansi"); + if (params && params.sort) params.sort(); + const paramsString = params && params.size ? `?${params.toString()}` : ""; let fullUrl; try { - fullUrl = new URL(`${path}${paramsString}`, baseUrl).href; + fullUrl = new URL(`${path}${paramsString}`, argv.url).href; } catch (e) { - e.message = `Could not build valid URL out of base url (${baseUrl}), path (${path}), and params string (${paramsString}) built from params (${JSON.stringify( + e.message = `Could not build valid URL out of base url (${argv.url}), path (${path}), and params string (${paramsString}) built from params (${JSON.stringify( params, )}).`; throw e; @@ -44,7 +46,7 @@ export async function makeFaunaRequest({ const fetchArgs = { method, - headers: { AUTHORIZATION: `Bearer ${secret}` }, + headers: { AUTHORIZATION: `Bearer ${argv.secret}` }, }; if (body) fetchArgs.body = body; diff --git a/src/lib/schema.mjs b/src/lib/schema.mjs index f74afb41..87b719c4 100644 --- a/src/lib/schema.mjs +++ b/src/lib/schema.mjs @@ -166,16 +166,20 @@ export async function writeSchemaFiles(dir, filenameToContentsDict) { /** * @param {string[]} filenames - A list of schema file names to fetch - * @param {Omit} overrides + * @param {object} argv * @returns {Promise>} A map of schema file names to their contents. */ -export async function getAllSchemaFileContents(filenames, { ...overrides }) { +export async function getAllSchemaFileContents(filenames, argv) { const promises = []; /** @type Record */ const fileContentCollection = {}; for (const filename of filenames) { promises.push( - getSchemaFile(filename, overrides).then(({ content }) => { + makeFaunaRequest({ + argv, + path: `/schema/1/files/${encodeURIComponent(filename)}`, + method: "GET", + }).then(({ content }) => { fileContentCollection[filename] = content; }), ); @@ -185,43 +189,3 @@ export async function getAllSchemaFileContents(filenames, { ...overrides }) { return fileContentCollection; } - -/** - * @param {Omit} overrides - */ -export async function getSchemaFiles({ ...overrides }) { - /** @type {fetchParameters} */ - const args = { - ...overrides, - path: "/schema/1/files", - method: "GET", - }; - return makeFaunaRequest({ ...args }); -} - -/** - * @param {string} filename - * @param {Omit} overrides - */ -export async function getSchemaFile(filename, { ...overrides }) { - /** @type {fetchParameters} */ - const args = { - ...overrides, - path: `/schema/1/files/${encodeURIComponent(filename)}`, - method: "GET", - }; - return makeFaunaRequest({ ...args }); -} - -/** - * @param {Omit} overrides - */ -export async function getStagedSchemaStatus({ ...overrides }) { - /** @type {fetchParameters} */ - const args = { - ...overrides, - path: "/schema/1/staged/status", - method: "GET", - }; - return makeFaunaRequest({ ...args }); -} diff --git a/test/general-cli.mjs b/test/general-cli.mjs index 064aaea3..363f3b86 100644 --- a/test/general-cli.mjs +++ b/test/general-cli.mjs @@ -9,6 +9,7 @@ import { stub } from "sinon"; import { builtYargs, run } from "../src/cli.mjs"; import { setupTestContainer as setupContainer } from "../src/config/setup-test-container.mjs"; +import { f } from "./helpers.mjs"; describe("cli operations", function () { let container; @@ -83,6 +84,22 @@ describe("cli operations", function () { }); it("should check for updates when run", async function () { + const fetch = container.resolve("fetch"); + fetch.onCall(0).resolves( + f({ + version: 0, + status: "none", + diff: "Staged schema: none", + pending_summary: "", + text_diff: "", + }), + ); + fetch.onCall(1).resolves( + f({ + version: 0, + diff: "", + }), + ); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const packagePath = path.join(__dirname, "../package.json"); diff --git a/test/helpers.mjs b/test/helpers.mjs index eddb5472..e098bda2 100644 --- a/test/helpers.mjs +++ b/test/helpers.mjs @@ -1,3 +1,5 @@ +import { join } from "node:path"; + // small helper for sinon to wrap your return value // in the shape fetch would return it from the network export function f(returnValue) { @@ -10,3 +12,20 @@ export const commonFetchParams = { AUTHORIZATION: "Bearer secret", }, }; + +/** + * this method sorts the query parameters - since makeFaunaRequest does as well, + * this allows comparing the resulting string URLs without false negatives + * from different sorting strategies. + * + * @param {string} path - the path to build a URL for + * @param {Record} paramObject - the params to include in the querystring + */ +export function buildUrl(path, paramObject) { + const params = new URLSearchParams(paramObject); + params.sort(); + let result = "db.fauna.com"; + if (path) result = join(result, path); + if (params.size) result = `${result}?${params}`; + return `https://${result}`; +} diff --git a/test/schema/abandon.mjs b/test/schema/abandon.mjs index 3106d4b1..7734182f 100644 --- a/test/schema/abandon.mjs +++ b/test/schema/abandon.mjs @@ -21,7 +21,7 @@ describe("schema abandon", function () { confirm = container.resolve("confirm"); }); - it("can force abandon a staged schema change", async function () { + it("can abandon a staged schema change without user input", async function () { fetch.onCall(0).resolves( f({ version: 1728677726190000, @@ -32,7 +32,7 @@ describe("schema abandon", function () { }), ); - await run(`schema abandon --secret "secret" --force`, container); + await run(`schema abandon --secret "secret" --no-input`, container); expect(fetch).to.have.been.calledOnce; expect(fetch).to.have.been.calledWith( diff --git a/test/schema/commit.mjs b/test/schema/commit.mjs index 7189259b..8016f36a 100644 --- a/test/schema/commit.mjs +++ b/test/schema/commit.mjs @@ -54,10 +54,10 @@ describe("schema commit", function () { ); }); - it("can force commit a schema change", async function () { + it("can commit a schema change without user input", async function () { fetch.onCall(0).resolves(f({ version: 1728684456180000 })); - await run(`schema commit --secret "secret" --force`, container); + await run(`schema commit --secret "secret" --no-input`, container); expect(fetch).to.have.been.calledOnce; expect(fetch).to.have.been.calledWith( diff --git a/test/schema/diff.mjs b/test/schema/diff.mjs index 7ad0f5fe..3474311f 100644 --- a/test/schema/diff.mjs +++ b/test/schema/diff.mjs @@ -3,7 +3,7 @@ import { expect } from "chai"; import { run } from "../../src/cli.mjs"; import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; import { reformatFSL } from "../../src/lib/schema.mjs"; -import { commonFetchParams, f } from "../helpers.mjs"; +import { buildUrl, commonFetchParams, f } from "../helpers.mjs"; describe("schema diff", function () { const colorDiffString = @@ -28,7 +28,7 @@ describe("schema diff", function () { gatherFSL.resolves(fsl); }); - it("can display the diff between local and remote schema", async function () { + it("can display the diff between local and staged remote schema", async function () { fetch.resolves( f({ version: 0, @@ -39,14 +39,23 @@ describe("schema diff", function () { await run(`schema diff --secret "secret"`, container); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/validate?force=true&color=ansi&staged=false", + buildUrl("/schema/1/staged/status", { color: "ansi" }), + { ...commonFetchParams, method: "GET" }, + ); + expect(fetch).to.have.been.calledWith( + buildUrl("/schema/1/validate", { + color: "ansi", + diff: "semantic", + force: "true", + staged: "true", + }), { ...commonFetchParams, method: "POST", body: reformatFSL(fsl) }, ); expect(logger.stdout).to.have.been.calledWith(colorDiffString); expect(logger.stderr).to.not.have.been.called; }); - it("can display the diff between local and staged remote schema", async function () { + it("can display the diff between local and active remote schema", async function () { fetch.resolves( f({ version: 0, @@ -54,10 +63,19 @@ describe("schema diff", function () { }), ); - await run(`schema diff --staged --secret "secret"`, container); + await run(`schema diff --active --secret "secret"`, container); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/validate?force=true&color=ansi&staged=true", + buildUrl("/schema/1/staged/status", { color: "ansi" }), + { ...commonFetchParams, method: "GET" }, + ); + expect(fetch).to.have.been.calledWith( + buildUrl("/schema/1/validate", { + color: "ansi", + diff: "semantic", + force: "true", + staged: "false", + }), { ...commonFetchParams, method: "POST", body: reformatFSL(fsl) }, ); expect(logger.stdout).to.have.been.calledWith(colorDiffString); @@ -74,8 +92,16 @@ describe("schema diff", function () { await run(`schema diff --secret "secret" --no-color`, container); + expect(fetch).to.have.been.calledWith(buildUrl("/schema/1/staged/status"), { + ...commonFetchParams, + method: "GET", + }); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/validate?force=true&staged=false", + buildUrl("/schema/1/validate", { + force: "true", + staged: "true", + diff: "semantic", + }), { ...commonFetchParams, method: "POST", body: reformatFSL(fsl) }, ); expect(logger.stdout).to.have.been.calledWith(noColorDiffString); @@ -93,10 +119,19 @@ describe("schema diff", function () { await run(`schema diff --secret "secret"`, container); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/validate?force=true&color=ansi&staged=false", + buildUrl("/schema/1/staged/status", { color: "ansi" }), + { ...commonFetchParams, method: "GET" }, + ); + expect(fetch).to.have.been.calledWith( + buildUrl("/schema/1/validate", { + force: "true", + color: "ansi", + staged: "true", + diff: "semantic", + }), { ...commonFetchParams, method: "POST", body: reformatFSL(fsl) }, ); - expect(logger.stdout).to.have.been.calledWith("No schema differences"); + expect(logger.stdout).to.have.been.calledWith("No schema differences."); expect(logger.stderr).to.not.have.been.called; }); @@ -117,4 +152,8 @@ describe("schema diff", function () { expect(gatherFSL).to.have.been.calledWith("/Users/test-user"); }); + + it.skip("errors if user provides both --staged and --active flags"); + it.skip("works with the --staged flag"); + it.skip("uses the correct intro string 'from ... to ...'"); }); diff --git a/test/schema/pull.mjs b/test/schema/pull.mjs index 1519c3bc..526d54ca 100644 --- a/test/schema/pull.mjs +++ b/test/schema/pull.mjs @@ -8,12 +8,9 @@ import { setupTestContainer as setupContainer } from "../../src/config/setup-tes import { deleteUnusedSchemaFiles, getAllSchemaFileContents, - getSchemaFile, - getSchemaFiles, - getStagedSchemaStatus, writeSchemaFiles, } from "../../src/lib/schema.mjs"; -import { commonFetchParams, f } from "../helpers.mjs"; +import { buildUrl, commonFetchParams, f } from "../helpers.mjs"; describe("schema pull", function () { let container, logger, confirm, fetch, fs, fsp, gatherFSL; @@ -24,9 +21,6 @@ describe("schema pull", function () { // this is a funny situation - we actually want the "real" implementations of these. // they end up calling fetch and fs, which is what we'll verify against in the tests. container.register({ - getSchemaFile: awilix.asValue(sinon.spy(getSchemaFile)), - getSchemaFiles: awilix.asValue(sinon.spy(getSchemaFiles)), - getStagedSchemaStatus: awilix.asValue(sinon.spy(getStagedSchemaStatus)), getAllSchemaFileContents: awilix.asValue( sinon.spy(getAllSchemaFileContents), ), @@ -73,7 +67,7 @@ describe("schema pull", function () { fetch.onCall(1).resolves( f({ version: "194838274939473", - status: "none", + status: "ready", }), ); fetch.onCall(2).resolves( @@ -102,24 +96,27 @@ describe("schema pull", function () { expect(gatherFSL).to.have.been.calledWith("."); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/files", + buildUrl("/schema/1/files"), commonFetchParams, ); // the version param in the URL is important - we use it for optimistic locking expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/staged/status?version=194838274939473", + buildUrl("/schema/1/staged/status", { + version: "194838274939473", + color: "ansi", + }), commonFetchParams, ); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/files/main.fsl", + buildUrl("/schema/1/files/main.fsl"), commonFetchParams, ); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/files/second.fsl", + buildUrl("/schema/1/files/second.fsl"), commonFetchParams, ); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/files/third.fsl", + buildUrl("/schema/1/files/third.fsl"), commonFetchParams, ); @@ -179,7 +176,7 @@ describe("schema pull", function () { fetch.onCall(1).resolves( f({ version: "194838274939473", - status: "none", + status: "ready", }), ); @@ -221,7 +218,7 @@ describe("schema pull", function () { fetch.onCall(1).resolves( f({ version: "194838274939473", - status: "none", + status: "ready", }), ); fetch.onCall(2).resolves( @@ -255,7 +252,7 @@ describe("schema pull", function () { it.skip("does not modify the filesystem if it fails to read file contents", async function () {}); - it("requires the --staged flag if a schema change is staged", async function () { + it("errors if called with the --active flag while a schema change is staged", async function () { fetch.onCall(0).resolves( f({ version: "194838274939473", @@ -269,14 +266,16 @@ describe("schema pull", function () { fetch.onCall(1).resolves( f({ version: "194838274939473", - status: "staged", + status: "ready", }), ); const [error] = await tryToCatch(() => - run(`schema pull --secret "secret"`, container), + run(`schema pull --secret "secret" --active`, container), ); expect(error).to.have.property("code", 1); expect(container.resolve("gatherFSL")).to.not.have.been.called; }); + + it.skip("errors if there are no staged schema changes to pull"); }); diff --git a/test/schema/push.mjs b/test/schema/push.mjs index 4a5c90f4..3dc63d9b 100644 --- a/test/schema/push.mjs +++ b/test/schema/push.mjs @@ -4,7 +4,7 @@ import sinon from "sinon"; import { run } from "../../src/cli.mjs"; import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; import { reformatFSL } from "../../src/lib/schema.mjs"; -import { f } from "../helpers.mjs"; +import { buildUrl, f } from "../helpers.mjs"; describe("schema push", function () { const diffString = @@ -29,13 +29,13 @@ describe("schema push", function () { gatherFSL.resolves(fsl); }); - it("can force push schema", async function () { - await run(`schema push --secret "secret" --force`, container); + it("can push a schema without user input", async function () { + await run(`schema push --secret "secret" --no-input`, container); expect(gatherFSL).to.have.been.calledWith("."); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/update?force=true&staged=false", + buildUrl("/schema/1/update", { force: "true", staged: "true" }), { method: "POST", headers: { AUTHORIZATION: "Bearer secret" }, @@ -47,7 +47,7 @@ describe("schema push", function () { expect(logger.stderr).to.not.be.called; }); - it("can push schema by version to active (default)", async function () { + it("can push schema by version to staged (default)", async function () { // user accepts the changes in the interactive prompt confirm.resolves(true); @@ -69,7 +69,11 @@ describe("schema push", function () { await run(`schema push --secret "secret"`, container); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/validate?force=true&staged=false&color=ansi", + buildUrl("/schema/1/validate", { + force: "true", + staged: "true", + color: "ansi", + }), { method: "POST", headers: { AUTHORIZATION: "Bearer secret" }, @@ -78,7 +82,10 @@ describe("schema push", function () { ); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/update?version=1728675598430000&staged=false", + buildUrl("/schema/1/update", { + version: "1728675598430000", + staged: "true", + }), { method: "POST", headers: { AUTHORIZATION: "Bearer secret" }, @@ -88,10 +95,13 @@ describe("schema push", function () { expect(logger.stderr).to.not.be.called; expect(logger.stdout).to.have.been.calledWith("Proposed diff:\n"); + expect(confirm).to.have.been.calledWith( + sinon.match.has("message", "Stage the above changes?"), + ); expect(logger.stdout).to.have.been.calledWith(diffString); }); - it("can push schema by version to staging", async function () { + it("can push schema by version to active", async function () { // user accepts the changes in the interactive prompt confirm.resolves(true); @@ -110,10 +120,14 @@ describe("schema push", function () { }), ); - await run(`schema push --secret "secret" --staged`, container); + await run(`schema push --secret "secret" --active`, container); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/validate?force=true&staged=true&color=ansi", + buildUrl("/schema/1/validate", { + force: "true", + staged: "false", + color: "ansi", + }), { method: "POST", headers: { AUTHORIZATION: "Bearer secret" }, @@ -122,7 +136,10 @@ describe("schema push", function () { ); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/update?version=1728675598430000&staged=true", + buildUrl("/schema/1/update", { + version: "1728675598430000", + staged: "false", + }), { method: "POST", headers: { AUTHORIZATION: "Bearer secret" }, @@ -132,6 +149,9 @@ describe("schema push", function () { expect(logger.stderr).to.not.be.called; expect(logger.stdout).to.have.been.calledWith("Proposed diff:\n"); + expect(confirm).to.have.been.calledWith( + sinon.match.has("message", "Push the above changes?"), + ); expect(logger.stdout).to.have.been.calledWith(diffString); }); @@ -150,7 +170,11 @@ describe("schema push", function () { await run(`schema push --secret "secret"`, container); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/validate?force=true&staged=false&color=ansi", + buildUrl("/schema/1/validate", { + force: "true", + staged: "true", + color: "ansi", + }), { method: "POST", headers: { AUTHORIZATION: "Bearer secret" }, @@ -167,14 +191,14 @@ describe("schema push", function () { it("can push schema from another directory", async function () { await run( - `schema push --secret "secret" --force --dir "/absolute/path/elsewhere"`, + `schema push --secret "secret" --no-input --dir "/absolute/path/elsewhere"`, container, ); expect(gatherFSL).to.have.been.calledWith("/absolute/path/elsewhere"); }); - it("warns when attempting to push an empty diff", async function () { + it("warns when attempting to stage an empty diff", async function () { // user accepts the changes in the interactive prompt confirm.resolves(true); @@ -189,7 +213,57 @@ describe("schema push", function () { await run(`schema push --secret "secret"`, container); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/validate?force=true&staged=false&color=ansi", + buildUrl("/schema/1/validate", { + force: "true", + staged: "true", + color: "ansi", + }), + { + method: "POST", + headers: { AUTHORIZATION: "Bearer secret" }, + body: reformatFSL(fsl), + }, + ); + + expect(fetch).to.have.been.calledWith( + buildUrl("/schema/1/update", { + version: "1728675598430000", + staged: "true", + }), + { + method: "POST", + headers: { AUTHORIZATION: "Bearer secret" }, + body: reformatFSL(fsl), + }, + ); + + expect(logger.stderr).to.not.be.called; + expect(logger.stdout).to.have.been.calledWith("No logical changes."); + expect(confirm).to.have.been.calledWith( + sinon.match.has("message", "Stage the file contents anyway?"), + ); + }); + + it("warns when attempting to push an empty diff", async function () { + // user accepts the changes in the interactive prompt + confirm.resolves(true); + + fetch.onCall(0).resolves( + f({ + // this is the version we provide when we mutate the resource + version: 1728675598430000, + // note: no diff + }), + ); + + await run(`schema push --secret "secret" --active`, container); + + expect(fetch).to.have.been.calledWith( + buildUrl("/schema/1/validate", { + force: "true", + staged: "false", + color: "ansi", + }), { method: "POST", headers: { AUTHORIZATION: "Bearer secret" }, @@ -198,7 +272,10 @@ describe("schema push", function () { ); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/update?version=1728675598430000&staged=false", + buildUrl("/schema/1/update", { + version: "1728675598430000", + staged: "false", + }), { method: "POST", headers: { AUTHORIZATION: "Bearer secret" }, @@ -209,7 +286,9 @@ describe("schema push", function () { expect(logger.stderr).to.not.be.called; expect(logger.stdout).to.have.been.calledWith("No logical changes."); expect(confirm).to.have.been.calledWith( - sinon.match.has("message", "Push file contents anyway?"), + sinon.match.has("message", "Push the file contents anyway?"), ); }); + + it.skip("correctly URI encodes file paths"); }); diff --git a/test/schema/status.mjs b/test/schema/status.mjs index 055317b7..cf60c878 100644 --- a/test/schema/status.mjs +++ b/test/schema/status.mjs @@ -1,35 +1,265 @@ +//@ts-check + import { expect } from "chai"; +import chalk from "chalk"; import { run } from "../../src/cli.mjs"; import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; -import { commonFetchParams, f } from "../helpers.mjs"; +import { buildUrl, commonFetchParams, f } from "../helpers.mjs"; describe("schema status", function () { - let container; + let container, fetch, logger; + + let summaryDiff = + "\x1B[1;34m* Adding collection `NewCollection`\x1B[0m to collections.fsl:2:1\n" + + "\x1B[1;34m* Modifying collection `OrderItem`\x1B[0m at collections.fsl:125:1\n" + + "\x1B[1;34m* Modifying function `createOrUpdateCartItem`\x1B[0m at functions.fsl:2:1\n"; + + let textDiff = + "\x1B[1mcollections.fsl\x1B[22m\n" + + "\x1B[36m@ line 1 to 7\x1B[0m\n" + + "\n" + + "\x1B[32m+ collection NewCollection {\x1B[0m\n" + + "\x1B[32m+ }\x1B[0m\n" + + "\x1B[32m+\x1B[0m\n" + + " collection Customer {\n" + + " name: String\n" + + " email: String\n" + + "\x1B[36m@ line 134 to 139\x1B[0m\n" + + " terms [.order]\n" + + " values [.product, .quantity]\n" + + " }\n" + + "\x1B[31m-\x1B[0m\n" + + "\x1B[31m- index byOrderAndProduct {\x1B[0m\n" + + "\x1B[31m- terms [.order, .product]\x1B[0m\n" + + "\x1B[31m- }\x1B[0m\n" + + " }\n" + + "\n" + + "\n" + + "\n" + + "\x1B[1mfunctions.fsl\x1B[22m\n" + + "\x1B[36m@ line 30 to 35\x1B[0m\n" + + " if (product!.stock < quantity) {\n" + + ' abort("Product does not have the requested quantity in stock.")\n' + + " }\n" + + "\x1B[31m-\x1B[0m\n" + + "\x1B[31m- // Attempt to find an existing order item for the order, product pair.\x1B[0m\n" + + "\x1B[31m- // There is a unique constraint on [.order, .product] so this will return at most one result.\x1B[0m\n" + + "\x1B[31m- let orderItem = OrderItem.byOrderAndProduct(customer!.cart, product).first()\x1B[0m\n" + + "\x1B[31m-\x1B[0m\n" + + "\x1B[31m- if (orderItem == null) {\x1B[0m\n" + + "\x1B[31m- // If the order item does not exist, create a new one.\x1B[0m\n" + + "\x1B[31m- OrderItem.create({\x1B[0m\n" + + "\x1B[31m- order: Order(customer!.cart!.id),\x1B[0m\n" + + "\x1B[31m- product: product,\x1B[0m\n" + + "\x1B[31m- quantity: quantity,\x1B[0m\n" + + "\x1B[31m- })\x1B[0m\n" + + "\x1B[31m- } else {\x1B[0m\n" + + "\x1B[31m- // If the order item exists, update the quantity.\x1B[0m\n" + + "\x1B[31m- orderItem!.update({ quantity: quantity })\x1B[0m\n" + + "\x1B[31m- }\x1B[0m\n" + + " }\n" + + "\n" + + " function getOrCreateCart(id) {\n"; beforeEach(() => { container = setupContainer(); + fetch = container.resolve("fetch"); + logger = container.resolve("logger"); }); - it("fetches the current status", async function () { - const fetch = container.resolve("fetch"); - fetch.resolves( + it("fetches the current status when there are no changes", async function () { + fetch.onCall(0).resolves( f({ version: 0, status: "none", diff: "Staged schema: none", + pending_summary: "", + text_diff: "", + }), + ); + fetch.onCall(1).resolves( + f({ + version: 0, + diff: "", + }), + ); + + await run(`schema status --secret "secret"`, container); + + expect(fetch).to.have.been.calledWith( + buildUrl("/schema/1/staged/status", { diff: "summary", color: "ansi" }), + commonFetchParams, + ); + expect(fetch).to.have.been.calledWith( + buildUrl("/schema/1/validate", { + diff: "summary", + staged: "true", + version: "0", + color: "ansi", + }), + { ...commonFetchParams, method: "POST", body: new FormData() }, + ); + expect(logger.stdout).to.have.been.calledWith( + `Staged changes: ${chalk.bold("none")}`, + ); + expect(logger.stdout).to.have.been.calledWith( + `Local changes: ${chalk.bold("none")}\n`, + ); + }); + + it("fetches the current status when there are only local changes", async function () { + fetch.onCall(0).resolves( + f({ + version: 0, + status: "none", + diff: "Staged schema: none", + pending_summary: "", + text_diff: "", + }), + ); + fetch.onCall(1).resolves( + f({ + version: 0, + diff: + "* Adding collection `NewCollection` to collections.fsl:2:1\n" + + "* Modifying collection `OrderItem` at collections.fsl:125:1\n" + + "* Modifying function `createOrUpdateCartItem` at functions.fsl:2:1\n", + }), + ); + + await run(`schema status --secret "secret"`, container); + + expect(fetch).to.have.been.calledWith( + buildUrl("/schema/1/staged/status", { diff: "summary", color: "ansi" }), + commonFetchParams, + ); + expect(fetch).to.have.been.calledWith( + buildUrl("/schema/1/validate", { + diff: "summary", + staged: "true", + version: "0", + color: "ansi", + }), + { ...commonFetchParams, method: "POST", body: new FormData() }, + ); + expect(logger.stdout).to.have.been.calledWith( + `Staged changes: ${chalk.bold("none")}`, + ); + expect(logger.stdout).to.have.been.calledWith(`Local changes:\n`); + expect(logger.stdout).to.have.been.calledWith( + ` * Adding collection \`NewCollection\` to collections.fsl:2:1\n * Modifying collection \`OrderItem\` at collections.fsl:125:1\n * Modifying function \`createOrUpdateCartItem\` at functions.fsl:2:1\n `, + ); + expect(logger.stdout).to.have.been.calledWith( + "(use `fauna schema diff` to display local changes)", + ); + expect(logger.stdout).to.have.been.calledWith( + "(use `fauna schema push` to stage local changes)", + ); + expect(logger.stderr).not.to.have.been.called; + }); + + it("fetches the current status when there are only staged changes", async function () { + fetch.onCall(0).resolves( + f({ + version: 0, + status: "ready", + diff: summaryDiff, + pending_summary: "", + text_diff: textDiff, + }), + ); + fetch.onCall(1).resolves( + f({ + version: 0, + diff: "", }), ); + await run(`schema status --secret "secret"`, container); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/staged/status?diff=true&color=ansi", + buildUrl("/schema/1/staged/status", { diff: "summary", color: "ansi" }), commonFetchParams, ); + + expect(fetch).to.have.been.calledWith( + buildUrl("/schema/1/validate", { + diff: "summary", + staged: "true", + version: "0", + color: "ansi", + }), + { ...commonFetchParams, method: "POST", body: new FormData() }, + ); + expect(logger.stdout).to.have.been.calledWith( + `Staged changes: ${chalk.bold("ready")}`, + ); + expect(logger.stdout).to.have.been.calledWith( + `Local changes: ${chalk.bold("none")}\n`, + ); + expect(logger.stdout).to.have.been.calledWith( + summaryDiff.split("\n").join("\n "), + ); + expect(logger.stderr).not.to.have.been.called; + }); + + it("fetches the current status when there are both local and staged changes", async function () { + fetch.onCall(0).resolves( + f({ + version: 0, + status: "ready", + diff: summaryDiff, + pending_summary: "", + text_diff: "", + }), + ); + fetch.onCall(1).resolves( + f({ + version: 0, + diff: + "* Adding function `newFunction` to functions.fsl:1:1\n" + + "* Modifying function `createOrUpdateCartItem` at functions.fsl:5:1\n", + }), + ); + + await run(`schema status --secret "secret"`, container); + + expect(fetch).to.have.been.calledWith( + buildUrl("/schema/1/staged/status", { diff: "summary", color: "ansi" }), + commonFetchParams, + ); + expect(fetch).to.have.been.calledWith( + buildUrl("/schema/1/validate", { + diff: "summary", + staged: "true", + version: "0", + color: "ansi", + }), + { ...commonFetchParams, method: "POST", body: new FormData() }, + ); + expect(logger.stdout).to.have.been.calledWith( + `Staged changes: ${chalk.bold("ready")}`, + ); + expect(logger.stdout).to.have.been.calledWith(`Staged changes:\n`); + expect(logger.stdout).to.have.been.calledWith(`Local changes:\n`); + expect(logger.stdout).to.have.been.calledWith( + summaryDiff.split("\n").join("\n "), + ); + expect(logger.stdout).to.have.been.calledWith( + " * Adding function `newFunction` to functions.fsl:1:1\n" + + " * Modifying function `createOrUpdateCartItem` at functions.fsl:5:1\n ", + ); + expect(logger.stdout).to.have.been.calledWith( + "(use `fauna schema diff` to display local changes)", + ); + expect(logger.stdout).to.have.been.calledWith( + "(use `fauna schema push` to stage local changes)", + ); + expect(logger.stderr).not.to.have.been.called; }); it("can fetch status without embedded colors (terminal escape codes)", async function () { - const fetch = container.resolve("fetch"); fetch.resolves( f({ version: 0, @@ -40,8 +270,9 @@ describe("schema status", function () { await run(`schema status --no-color --secret "secret"`, container); expect(fetch).to.have.been.calledWith( - "https://db.fauna.com/schema/1/staged/status?diff=true", + buildUrl("/schema/1/staged/status", { diff: "summary" }), commonFetchParams, ); + expect(logger.stderr).not.to.have.been.called; }); });