Skip to content

Commit

Permalink
Add schema push --stage
Browse files Browse the repository at this point in the history
  • Loading branch information
macmv committed Sep 10, 2024
1 parent a2568ce commit b3044e6
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 16 deletions.
58 changes: 42 additions & 16 deletions src/commands/schema/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ export default class PushSchemaCommand extends SchemaCommand {
description: "Push the change without a diff or schema version check",
default: false,
}),
stage: Flags.boolean({
description:
"Stages the schema change, instead of applying it immediately",
default: false,
}),
};

static description = "Push the current project's .fsl files to Fauna.";

static examples = [
"$ fauna schema push",
"$ fauna schema push --dir schemas/myschema",
"$ fauna schema push --stage",
];

async run() {
Expand All @@ -24,8 +30,14 @@ export default class PushSchemaCommand extends SchemaCommand {
try {
const { url, secret } = await this.fetchsetup();
if (this.flags?.force) {
// Just push.
const res = await fetch(new URL("/schema/1/update?force=true", url), {
const params = new URLSearchParams({
force: "true", // Just push.
staged: this.flags?.stage ? "true" : "false",
});

// This is how MDN says to do it for some reason.
const path = new URL(`/schema/1/update?${params}`, url);
const res = await fetch(path, {
method: "POST",
headers: { AUTHORIZATION: `Bearer ${secret}` },
body: this.body(files),
Expand All @@ -34,21 +46,30 @@ export default class PushSchemaCommand extends SchemaCommand {
// @ts-expect-error-next-line
duplex: "half",
});

const json = await res.json();
if (json.error) {
this.error(json.error.message);
this.error(json.error?.message ?? json.error);
}
} else {
// Confirm diff, then push it.
const res = await fetch(new URL("/schema/1/validate?force=true", url), {
// 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 path = new URL(`/schema/1/validate?${params}`, url);
const res = await fetch(path, {
method: "POST",
headers: { AUTHORIZATION: `Bearer ${secret}` },
body: this.body(files),
// @ts-expect-error-next-line
duplex: "half",
});
const json = await res.json();
if (json.error) {
this.error(json.error.message);
this.error(json.error?.message ?? json.error);
}

let message = "Accept and push changes?";
if (json.diff) {
this.log(`Proposed diff:\n`);
Expand All @@ -61,17 +82,22 @@ export default class PushSchemaCommand extends SchemaCommand {
message,
default: false,
});

if (confirmed) {
const res = await fetch(
new URL(`/schema/1/update?version=${json.version}`, url),
{
method: "POST",
headers: { AUTHORIZATION: `Bearer ${secret}` },
body: this.body(files),
// @ts-expect-error-next-line
duplex: "half",
}
);
const params = new URLSearchParams({
version: json.version,
staged: this.flags?.stage ? "true" : "false",
});

const path = new URL(`/schema/1/update?${params}`, url);
const res = await fetch(path, {
method: "POST",
headers: { AUTHORIZATION: `Bearer ${secret}` },
body: this.body(files),
// @ts-expect-error-next-line
duplex: "half",
});

const json0 = await res.json();
if (json0.error) {
this.error(json0.error.message);
Expand Down
107 changes: 107 additions & 0 deletions test/integ/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { runCommand } from "@oclif/test";
import { fail } from "assert";

export type ShellResult = { stdout: string; stderr: string; ok: boolean };

const TEST_PREFIX = "fauna_shell_integ_test_";

export const newDB = async (secret?: string): Promise<string> => {
const name = TEST_PREFIX + Math.floor(Math.random() * 1000000000);

return evalOk<string>(
stripMargin(
`|if (Database.byName('${name}').exists()) {
| Database.byName('${name}').delete()
|}
|Database.create({ name: '${name}', typechecked: true })
|Key.create({ role: 'admin', database: '${name}' }).secret
|`
),
secret
);
};

// Cleanup after ourselves.
after(async () => {
evalOk(
stripMargin(
`|Database.all().forEach((db) => {
| if (db.name.startsWith('${TEST_PREFIX}')) {
| db.delete()
| }
|})
|`
)
);
});

export const evalOk = async <T>(code: string, secret?: string): Promise<T> => {
const res = JSON.parse(
await shellOk(`fauna eval "${code}" --format json`, secret)
);
// FIXME: This should really fail `shellOk`, but error handling is hard.
if (res?.error) {
fail(`Eval failed: ${res.summary}`);
}

return res;
};

export const shellOk = async (
cmd: string,
secret?: string
): Promise<string> => {
const res = await shell(cmd, secret);
if (!res.ok) {
fail(`Command unexpectedly failed:\n${res.stderr}`);
}

return res.stdout;
};

export const shellErr = async (cmd: string): Promise<string> => {
const res = await shell(cmd);
if (res.ok) {
fail(`Command should not have exitted succesfully:\n${res.stdout}`);
}

return res.stderr;
};

export const stripMargin = (str: string): string => {
return str
.split("\n")
.map((line) => {
const trimmed = line.trimStart();
if (trimmed.startsWith("|")) {
return trimmed.slice(1);
} else {
return trimmed;
}
})
.join("\n");
};

export const shell = async (
cmd: string,
secret?: string
): Promise<ShellResult> => {
const parts = cmd.split(" ");
if (parts[0] !== "fauna") {
fail("Command must start with fauna");
}

const opts = [
parts.slice(1).join(" "),
"--url http://127.0.0.1:8443",
`--secret ${secret ?? "secret"}`,
];

const out = await runCommand(opts);

return {
stdout: out.stdout,
stderr: out.stderr + out.error?.message,
ok: out.error === undefined,
};
};
45 changes: 45 additions & 0 deletions test/integ/schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { expect } from "chai";
import { evalOk, newDB, shellOk, stripMargin } from "./base";

it("fauna schema push --stage --force works", async () => {
const secret = await newDB();

await shellOk(
"fauna schema push --dir test/integ/schema/start --force",
secret
);

expect(
await evalOk("Collection.all().map(.name).toArray()", secret)
).to.deep.equal(["User"]);

await shellOk(
"fauna schema push --dir test/integ/schema/staged_index --force --stage",
secret
);

// Index should be in the FQL definition.
expect(
await evalOk("Collection.byName('User')!.indexes.byName", secret)
).to.deep.equal({
terms: [
{
field: ".name",
mva: false,
},
],
queryable: true,
status: "complete",
});

// But, the index should not be visible on the companion object.
expect(
await evalOk(
stripMargin(
`|let user: Any = User
|user.byName`
),
secret
)
).to.deep.equal(null);
});
8 changes: 8 additions & 0 deletions test/integ/schema/staged_index/main.fsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
collection User {
name: String
email: String

index byName {
terms [.name]
}
}
4 changes: 4 additions & 0 deletions test/integ/schema/start/main.fsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
collection User {
name: String
email: String
}

0 comments on commit b3044e6

Please sign in to comment.