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

Add path per line output for db list #530

Merged
merged 5 commits into from
Dec 16, 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
51 changes: 21 additions & 30 deletions src/commands/database/list.mjs
Original file line number Diff line number Diff line change
@@ -1,75 +1,66 @@
//@ts-check
import chalk from "chalk";

import { container } from "../../cli.mjs";
import { faunaToCommandError } from "../../lib/fauna.mjs";
import { FaunaAccountClient } from "../../lib/fauna-account-client.mjs";
import { colorize, Format } from "../../lib/formatting/colorize.mjs";

// Narrow the output fields based on the provided flags.
const getOutputFields = (argv) => {
if (!argv.secret && !argv.database) {
// If we are listing top level databases the region group
// needs to be included as database names can be re-used across
// regions.
return ["name", "region_group"];
}
return ["name"];
};

function pickOutputFields(databases, argv) {
return databases.map((d) =>
getOutputFields(argv).reduce((acc, field) => {
acc[field] = d[field];
return acc;
}, {}),
);
}

async function listDatabasesWithAccountAPI(argv) {
const { pageSize, database } = argv;
const accountClient = new FaunaAccountClient();
const response = await accountClient.listDatabases({
pageSize,
path: database,
});
return pickOutputFields(response.results, argv);

return response.results.map(({ path, name }) => ({ path, name }));
}

async function listDatabasesWithSecret(argv) {
const { url, secret, pageSize } = argv;
const { runQueryFromString } = container.resolve("faunaClientV10");

try {
return await runQueryFromString({
const res = await runQueryFromString({
url,
secret,
// This gives us back an array of database names. If we want to
// provide the after token at some point this query will need to be updated.
expression: `Database.all().paginate(${pageSize}).data { ${getOutputFields(argv)} }`,
expression: `Database.all().paginate(${pageSize}).data { name }`,
});
return res.data;
} catch (e) {
return faunaToCommandError(e);
}
}

export async function listDatabases(argv) {
let databases;
if (argv.secret) {
databases = await listDatabasesWithSecret(argv);
return await listDatabasesWithSecret(argv);
} else {
databases = await listDatabasesWithAccountAPI(argv);
return await listDatabasesWithAccountAPI(argv);
}
return databases;
}

async function doListDatabases(argv) {
const logger = container.resolve("logger");
const { formatQueryResponse } = container.resolve("faunaClientV10");
const res = await listDatabases(argv);

if (argv.secret) {
logger.stdout(formatQueryResponse(res, argv));
} else {
logger.stderr(
chalk.yellow(
"Warning: Full database paths are not available when using --secret. Use --database if a full path, including the Region Group identified and hierarchy, is needed.",
),
);
}

if (argv.json) {
logger.stdout(colorize(res, { format: Format.JSON, color: argv.color }));
} else {
res.forEach(({ path, name }) => {
logger.stdout(path ?? name);
});
}
}

Expand Down
100 changes: 49 additions & 51 deletions test/database/list.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,16 @@ import { colorize } from "../../src/lib/formatting/colorize.mjs";
import { mockAccessKeysFile } from "../helpers.mjs";

describe("database list", () => {
let container,
fs,
logger,
runQueryFromString,
makeAccountRequest,
formatQueryResponse;
let container, fs, logger, stdout, runQueryFromString, makeAccountRequest;

beforeEach(() => {
// reset the container before each test
container = setupContainer();
fs = container.resolve("fs");
logger = container.resolve("logger");
runQueryFromString = container.resolve("faunaClientV10").runQueryFromString;
formatQueryResponse =
container.resolve("faunaClientV10").formatQueryResponse;
makeAccountRequest = container.resolve("makeAccountRequest");
stdout = container.resolve("stdoutStream");
});

describe("when --local is provided", () => {
Expand All @@ -51,17 +45,11 @@ describe("database list", () => {
url: "http://localhost:8443",
},
},
{
args: "--local --json",
expected: {
secret: "secret",
json: true,
url: "http://localhost:8443",
},
},
].forEach(({ args, expected }) => {
it(`calls fauna with the correct args: ${args}`, async () => {
const stubbedResponse = { data: [{ name: "testdb" }] };
const stubbedResponse = {
data: [{ name: "testdb" }, { name: "testdb2" }],
};
runQueryFromString.resolves(stubbedResponse);

await run(`database list ${args}`, container);
Expand All @@ -72,13 +60,9 @@ describe("database list", () => {
expression: `Database.all().paginate(${expected.pageSize ?? 1000}).data { name }`,
});

expect(logger.stdout).to.have.been.calledOnceWith(
formatQueryResponse(stubbedResponse, {
format: "json",
color: true,
}),
);
await stdout.waitForWritten();

expect(stdout.getWritten()).to.equal("testdb\ntestdb2\n");
expect(makeAccountRequest).to.not.have.been.called;
});
});
Expand All @@ -94,10 +78,6 @@ describe("database list", () => {
args: "--secret 'secret' --pageSize 10",
expected: { secret: "secret", pageSize: 10 },
},
{
args: "--secret 'secret' --json",
expected: { secret: "secret", json: true },
},
].forEach(({ args, expected }) => {
it(`calls fauna with the correct args: ${args}`, async () => {
const stubbedResponse = { data: [{ name: "testdb" }] };
Expand All @@ -111,13 +91,7 @@ describe("database list", () => {
expression: `Database.all().paginate(${expected.pageSize ?? 1000}).data { name }`,
});

expect(logger.stdout).to.have.been.calledOnceWith(
formatQueryResponse(stubbedResponse, {
format: "json",
color: true,
}),
);

expect(stdout.getWritten()).to.equal("testdb\n");
expect(makeAccountRequest).to.not.have.been.called;
});
});
Expand Down Expand Up @@ -158,10 +132,6 @@ describe("database list", () => {
args: "--database 'us/example'",
expected: { database: "us-std/example" },
},
{
args: "--database 'us/example' --json",
expected: { database: "us-std/example", json: true },
},
].forEach(({ args, expected }) => {
it(`calls the account api with the correct args: ${args}`, async () => {
mockAccessKeysFile({ fs });
Expand All @@ -172,6 +142,9 @@ describe("database list", () => {
...(expected.regionGroup
? { region_group: expected.regionGroup }
: {}),
path: expected.regionGroup
? `${expected.regionGroup}/test`
: `${expected.database}/test`,
},
],
};
Expand All @@ -189,19 +162,44 @@ describe("database list", () => {
},
});

const expectedOutput = stubbedResponse.results.map((d) => ({
name: d.name,
// region group is only returned when listing top level databases
...(expected.regionGroup
? { region_group: expected.regionGroup }
: {}),
}));

expect(logger.stdout).to.have.been.calledOnceWith(
colorize(expectedOutput, {
format: "json",
color: true,
}),
expect(stdout.getWritten()).to.equal(
`${stubbedResponse.results.map((d) => d.path).join("\n")}\n`,
);
});
});
});

describe("when --json is provided", () => {
[
"--local",
"--secret=test-secret",
"--database=us/example",
"--pageSize 10",
].forEach((args) => {
it(`outputs json when using ${args}`, async () => {
mockAccessKeysFile({ fs });

let data;
if (args.includes("--local") || args.includes("--secret")) {
data = [{ name: "testdb" }];
runQueryFromString.resolves({ data });
} else {
data = [
{
path: "us-std/test",
name: "test",
},
];
makeAccountRequest.resolves({
results: data,
});
}

await run(`database list ${args} --json`, container);
await stdout.waitForWritten();

expect(stdout.getWritten().trim()).to.equal(
`${colorize(data, { format: "json" })}`,
);
});
});
Expand Down
Loading