Skip to content

Commit

Permalink
test: implement tests for command level errors
Browse files Browse the repository at this point in the history
  • Loading branch information
stefreak committed Aug 31, 2023
1 parent 8eb68a0 commit 1eaf921
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 10 deletions.
6 changes: 1 addition & 5 deletions core/src/commands/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,10 @@ export class VersionCommand extends Command {
override noProject = true

override async action(params: CommandParams): Promise<CommandResult<VersionCommandResult>> {
const { log, opts } = params
const { log } = params
const version = getPackageVersion()
log.info(`garden version: ${version}`)

if (opts.env === "test-command-error") {
throw new Error("Test")
}

return {
result: { version },
}
Expand Down
24 changes: 24 additions & 0 deletions core/src/util/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,3 +492,27 @@ export function expectError(fn: Function, assertion: ExpectErrorAssertion = {})

return handleNonError(false)
}

// adapted from https://stackoverflow.com/a/18543419/1518423
export function captureStream(stream: NodeJS.WritableStream) {
const oldWrite = stream.write
let buf: string = ""

class FakeWrite {
write(chunk, _callback)
write(chunk, _encoding?, _callback?) {
buf += chunk.toString()
}
}

stream["write"] = FakeWrite.prototype.write

return {
unhook: function unhook() {
stream.write = oldWrite
},
captured: () => {
return buf
},
}
}
2 changes: 1 addition & 1 deletion core/src/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,7 @@ export function getGitHubIssueLink(title: string, type: "bug" | "crash" | "featu
case "bug":
return `https://github.com/garden-io/garden/issues/new?labels=bug&template=BUG_REPORT.md&title=${title}`
case "crash":
return `https://github.com/garden-io/garden/issues/new?labels=bug,crashtemplate=CRASH.md&title=${title}`
return `https://github.com/garden-io/garden/issues/new?labels=bug,crash&template=CRASH.md&title=${title}`
}
}

Expand Down
84 changes: 81 additions & 3 deletions core/test/unit/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ import { GardenCli, validateRuntimeRequirementsCached } from "../../../../src/cl
import { getDataDir, projectRootA, initTestLogger } from "../../../helpers"
import { gardenEnv, GARDEN_CORE_ROOT } from "../../../../src/constants"
import { join, resolve } from "path"
import { Command, CommandGroup, CommandParams, PrepareParams } from "../../../../src/commands/base"
import { Command, CommandGroup, CommandParams, CommandResult, PrepareParams } from "../../../../src/commands/base"
import { UtilCommand } from "../../../../src/commands/util/util"
import { StringParameter } from "../../../../src/cli/params"
import stripAnsi from "strip-ansi"
import { ToolsCommand } from "../../../../src/commands/tools"
import { getRootLogger, RootLogger } from "../../../../src/logger/logger"
import { load } from "js-yaml"
import { Type, load } from "js-yaml"
import { startServer } from "../../../../src/server/server"
import { envSupportsEmoji } from "../../../../src/logger/util"
import { expectError, expectFuzzyMatch } from "../../../../src/util/testing"
import { captureStream, expectError, expectFuzzyMatch } from "../../../../src/util/testing"
import { GlobalConfigStore } from "../../../../src/config-store/global"
import tmp from "tmp-promise"
import { CloudCommand } from "../../../../src/commands/cloud/cloud"
Expand All @@ -33,6 +33,10 @@ import { mkdirp } from "fs-extra"
import { uuidv4 } from "../../../../src/util/random"
import { makeDummyGarden } from "../../../../src/garden"
import { TestGardenCli } from "../../../helpers/cli"
import { NotImplementedError } from "../../../../src/exceptions"
import sinon, { SinonStub } from "sinon"
import dedent from "dedent"
import { last } from "lodash"

describe("cli", () => {
let cli: GardenCli
Expand Down Expand Up @@ -910,6 +914,80 @@ describe("cli", () => {
"Invalid value for option --env: Invalid environment specified ($.%): must be a valid environment name or <namespace>.<environment>"
)
})

describe("Command error handling", async () => {
let hook: ReturnType<typeof captureStream>

beforeEach(() => {
hook = captureStream(process.stdout)
})
afterEach(() => {
hook.unhook()
})
it("handles GardenError on the command level correctly", async () => {
class TestCommand extends Command {
name = "test-command"
help = "halp!"
override noProject = true

override printHeader() {}

async action({}): Promise<CommandResult> {
throw new NotImplementedError({ message: "Error message" })
}
}

const cmd = new TestCommand()
cli.addCommand(cmd)

const { code } = await cli.run({ args: ["test-command"], exitOnError: false })

expect(code).to.equal(1)
const output = stripAnsi(hook.captured())
expect(output).to.eql(dedent`
Error message
See .garden/error.log for detailed error message\n`)
})

it("handles crash on the command level correctly", async () => {
class TestCommand extends Command {
name = "test-command"
help = "halp!"
override noProject = true

override printHeader() {}

async action({}): Promise<CommandResult> {
throw new TypeError("Cannot read property foo of undefined.")
}
}

const cmd = new TestCommand()
cli.addCommand(cmd)

const { code } = await cli.run({ args: ["test-command"], exitOnError: false })

expect(code).to.equal(1)
const outputLines = stripAnsi(hook.captured()).split("\n")

const firstSevenLines = outputLines.slice(0, 7).join("\n")
expect(firstSevenLines).to.eql(dedent`
Encountered an unexpected Garden error. We are sorry for this. This is likely a bug 🍂
You can help by reporting this on GitHub: https://github.com/garden-io/garden/issues/new?labels=bug,crash&template=CRASH.md&title=Crash%3A%20Cannot%20read%20property%20foo%20of%20undefined.
Please attach the following information to the bug report after making sure that the error message does not contain sensitive information:
TypeError: Cannot read property foo of undefined.`)

const firstStackTraceLine = outputLines[7]
expect(firstStackTraceLine).to.contain("at TestCommand.action (")

const lastLine = outputLines[outputLines.length - 2] // the last line is empty due to trailing newline
expect(lastLine).to.eql("See .garden/error.log for detailed error message")
})
})
})

describe("makeDummyGarden", () => {
Expand Down
1 change: 0 additions & 1 deletion core/test/unit/src/commands/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ describe("VersionCommand", () => {
const garden = await makeDummyGarden(tmpDir.path, { commandInfo: { name: "version", args: {}, opts: {} } })
const { result } = await command.action({
log: garden.log,
opts: {}
} as any)
expect(result).to.eql({
version: getPackageVersion(),
Expand Down

0 comments on commit 1eaf921

Please sign in to comment.