Skip to content

Commit

Permalink
Add unit test.
Browse files Browse the repository at this point in the history
  • Loading branch information
rosalyntan committed Dec 31, 2024
1 parent 49e88a2 commit 6edab81
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 51 deletions.
142 changes: 142 additions & 0 deletions src/dataconnect/build.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { expect } from "chai";
import * as sinon from "sinon";
import * as prompt from "../prompt";
import { handleBuildErrors } from "./build";
import { GraphqlError } from "./types";

describe("handleBuildErrors", () => {
let promptOnceStub: sinon.SinonStub;
beforeEach(() => {
promptOnceStub = sinon
.stub(prompt, "promptOnce")
.throws("unexpected call to prompt.promptOnce");
});
afterEach(() => {
sinon.verifyAndRestore();
});
const cases: {
desc: string;
graphqlErr: GraphqlError[];
nonInteractive: boolean;
force: boolean;
dryRun: boolean;
promptAnswer?: string;
expectErr: boolean;
}[] = [
{
desc: "Only build error",
graphqlErr: [{ message: "build error" }],
nonInteractive: false,
force: true,
dryRun: false,
expectErr: true,
},
{
desc: "Build error with evolution error",
graphqlErr: [
{ message: "build error" },
{ message: "evolution error", extensions: { warningLevel: "INTERACTIVE_ACK" } },
],
nonInteractive: false,
force: true,
dryRun: false,
expectErr: true,
},
{
desc: "Interactive ack evolution error, prompt and accept",
graphqlErr: [{ message: "evolution error", extensions: { warningLevel: "INTERACTIVE_ACK" } }],
nonInteractive: false,
force: false,
dryRun: false,
promptAnswer: "proceed",
expectErr: false,
},
{
desc: "Interactive ack evolution error, prompt and reject",
graphqlErr: [{ message: "evolution error", extensions: { warningLevel: "INTERACTIVE_ACK" } }],
nonInteractive: false,
force: false,
dryRun: false,
promptAnswer: "abort",
expectErr: true,
},
{
desc: "Interactive ack evolution error, nonInteractive=true",
graphqlErr: [{ message: "evolution error", extensions: { warningLevel: "INTERACTIVE_ACK" } }],
nonInteractive: true,
force: false,
dryRun: false,
expectErr: false,
},
{
desc: "Interactive ack evolution error, force=true",
graphqlErr: [{ message: "evolution error", extensions: { warningLevel: "INTERACTIVE_ACK" } }],
nonInteractive: false,
force: true,
dryRun: false,
expectErr: false,
},
{
desc: "Interactive ack evolution error, dryRun=true",
graphqlErr: [{ message: "evolution error", extensions: { warningLevel: "INTERACTIVE_ACK" } }],
nonInteractive: false,
force: false,
dryRun: true,
expectErr: false,
},
{
desc: "Required ack evolution error, prompt and accept",
graphqlErr: [{ message: "evolution error", extensions: { warningLevel: "REQUIRE_ACK" } }],
nonInteractive: false,
force: false,
dryRun: false,
promptAnswer: "proceed",
expectErr: false,
},
{
desc: "Required ack evolution error, prompt and reject",
graphqlErr: [{ message: "evolution error", extensions: { warningLevel: "REQUIRE_ACK" } }],
nonInteractive: false,
force: false,
dryRun: false,
promptAnswer: "abort",
expectErr: true,
},
{
desc: "Required ack evolution error, nonInteractive=true, force=false",
graphqlErr: [{ message: "evolution error", extensions: { warningLevel: "REQUIRE_ACK" } }],
nonInteractive: true,
force: false,
dryRun: false,
expectErr: true,
},
{
desc: "Required ack evolution error, nonInteractive=true, force=true",
graphqlErr: [{ message: "evolution error", extensions: { warningLevel: "REQUIRE_ACK" } }],
nonInteractive: true,
force: true,
dryRun: false,
expectErr: false,
},
{
desc: "Required ack evolution error, nonInteractive=false, force=true",
graphqlErr: [{ message: "evolution error", extensions: { warningLevel: "REQUIRE_ACK" } }],
nonInteractive: false,
force: true,
dryRun: false,
expectErr: false,
},
];
for (const c of cases) {
it(c.desc, async () => {
try {
if (c.promptAnswer) {
promptOnceStub.resolves(c.promptAnswer);
}
await handleBuildErrors(c.graphqlErr, c.nonInteractive, c.force, c.dryRun);
} catch (err) {
expect(c.expectErr).to.be.true;
}
});
}
});
107 changes: 56 additions & 51 deletions src/dataconnect/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as experiments from "../experiments";
import { promptOnce } from "../prompt";
import * as utils from "../utils";
import { prettify, prettifyWithWorkaround } from "./graphqlError";
import { DeploymentMetadata } from "./types";
import { DeploymentMetadata, GraphqlError } from "./types";

export async function build(

Check warning on line 10 in src/dataconnect/build.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment
options: Options,
Expand All @@ -24,62 +24,67 @@ export async function build(
}
const buildResult = await DataConnectEmulator.build(args);
if (buildResult?.errors?.length) {
if (buildResult.errors.filter((w) => !w.extensions?.warningLevel).length) {
// Throw immediately if there are any build errors in the GraphQL schema or connectors.
throw new FirebaseError(
`There are errors in your schema and connector files:\n${buildResult.errors.map(prettify).join("\n")}`,
);
}
const interactiveAcks = buildResult.errors.filter(
(w) => w.extensions?.warningLevel === "INTERACTIVE_ACK",
await handleBuildErrors(buildResult.errors, options.nonInteractive, options.force, dryRun);
}
return buildResult?.metadata ?? {};
}

export async function handleBuildErrors(

Check warning on line 32 in src/dataconnect/build.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function

Check warning on line 32 in src/dataconnect/build.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment
errors: GraphqlError[],
nonInteractive: boolean,
force: boolean,
dryRun?: boolean,
) {
if (errors.filter((w) => !w.extensions?.warningLevel).length) {
// Throw immediately if there are any build errors in the GraphQL schema or connectors.
throw new FirebaseError(
`There are errors in your schema and connector files:\n${errors.map(prettify).join("\n")}`,
);
const requiredAcks = buildResult.errors.filter(
(w) => w.extensions?.warningLevel === "REQUIRE_ACK",
}
const interactiveAcks = errors.filter((w) => w.extensions?.warningLevel === "INTERACTIVE_ACK");
const requiredAcks = errors.filter((w) => w.extensions?.warningLevel === "REQUIRE_ACK");
const choices = [
{ name: "Acknowledge all changes and proceed", value: "proceed" },
{ name: "Reject changes and abort", value: "abort" },
];
if (requiredAcks.length) {
utils.logLabeledWarning(
"dataconnect",
`There are changes in your schema or connectors that may break your existing applications. These changes require explicit acknowledgement to proceed. You may either reject the changes and update your sources with the suggested workaround(s), if any, or acknowledge these changes and proceed with the deployment:\n` +
prettifyWithWorkaround(requiredAcks),
);
const choices = [
{ name: "Acknowledge all changes and proceed", value: "proceed" },
{ name: "Reject changes and abort", value: "abort" },
];
if (requiredAcks.length) {
utils.logLabeledWarning(
"dataconnect",
`There are changes in your schema or connectors that may break your existing applications. These changes require explicit acknowledgement to proceed. You may either reject the changes and update your sources with the suggested workaround(s), if any, or acknowledge these changes and proceed with the deployment:\n` +
prettifyWithWorkaround(requiredAcks),
if (nonInteractive && !force) {
throw new FirebaseError(
"Explicit acknowledgement required for breaking schema or connector changes. Rerun this command with --force to deploy these changes.",
);
if (options.nonInteractive && !options.force) {
throw new FirebaseError(
"Explicit acknowledgement required for breaking schema or connector changes. Rerun this command with --force to deploy these changes.",
);
} else if (!options.nonInteractive && !options.force && !dryRun) {
const result = await promptOnce({
message: "Would you like to proceed with these breaking changes?",
type: "list",
choices,
default: "abort",
});
if (result === "abort") {
throw new FirebaseError(`Deployment aborted.`);
}
} else if (!nonInteractive && !force && !dryRun) {
const result = await promptOnce({

Check warning on line 61 in src/dataconnect/build.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value
message: "Would you like to proceed with these breaking changes?",
type: "list",
choices,
default: "abort",
});
if (result === "abort") {
throw new FirebaseError(`Deployment aborted.`);
}
}
if (interactiveAcks.length) {
utils.logLabeledWarning(
"dataconnect",
`There are changes in your schema or connectors that may cause unexpected behavior in your existing applications:\n` +
interactiveAcks.map(prettify).join("\n"),
);
if (!options.nonInteractive && !options.force && !dryRun) {
const result = await promptOnce({
message: "Would you like to proceed with these changes?",
type: "list",
choices,
default: "proceed",
});
if (result === "abort") {
throw new FirebaseError(`Deployment aborted.`);
}
}
if (interactiveAcks.length) {
utils.logLabeledWarning(
"dataconnect",
`There are changes in your schema or connectors that may cause unexpected behavior in your existing applications:\n` +
interactiveAcks.map(prettify).join("\n"),
);
if (!nonInteractive && !force && !dryRun) {
const result = await promptOnce({

Check warning on line 79 in src/dataconnect/build.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value
message: "Would you like to proceed with these changes?",
type: "list",
choices,
default: "proceed",
});
if (result === "abort") {
throw new FirebaseError(`Deployment aborted.`);
}
}
}
return buildResult?.metadata ?? {};
}

0 comments on commit 6edab81

Please sign in to comment.