Files
atlantis/server/events/markdown_renderer_test.go
Luke Kysow 934b51b64f Add project name to comments.
If projects are configured using an atlantis.yaml with a specific
project name, include that in the output for comments. This is useful
because often the project names are better identifiers than the
directory and workspace names and also because some projects use the
same dir and workspace so the only way to differentiate them is via
their names.

Fixes #353
2018-12-04 12:01:05 -06:00

934 lines
22 KiB
Go

// Copyright 2017 HootSuite Media Inc.
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an AS IS BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Modified hereafter by contributors to runatlantis/atlantis.
package events_test
import (
"errors"
"fmt"
"strings"
"testing"
"github.com/runatlantis/atlantis/server/events"
"github.com/runatlantis/atlantis/server/events/models"
. "github.com/runatlantis/atlantis/testing"
)
func TestRenderErr(t *testing.T) {
err := errors.New("err")
cases := []struct {
Description string
Command events.CommandName
Error error
Expected string
}{
{
"apply error",
events.ApplyCommand,
err,
"**Apply Error**\n```\nerr\n```\n",
},
{
"plan error",
events.PlanCommand,
err,
"**Plan Error**\n```\nerr\n```\n",
},
}
r := events.MarkdownRenderer{}
for _, c := range cases {
res := events.CommandResult{
Error: c.Error,
}
for _, verbose := range []bool{true, false} {
t.Run(fmt.Sprintf("%s_%t", c.Description, verbose), func(t *testing.T) {
s := r.Render(res, c.Command, "log", verbose, models.Github)
if !verbose {
Equals(t, c.Expected, s)
} else {
Equals(t, c.Expected+"<details><summary>Log</summary>\n <p>\n\n```\nlog```\n</p></details>\n", s)
}
})
}
}
}
func TestRenderFailure(t *testing.T) {
cases := []struct {
Description string
Command events.CommandName
Failure string
Expected string
}{
{
"apply failure",
events.ApplyCommand,
"failure",
"**Apply Failed**: failure\n",
},
{
"plan failure",
events.PlanCommand,
"failure",
"**Plan Failed**: failure\n",
},
}
r := events.MarkdownRenderer{}
for _, c := range cases {
res := events.CommandResult{
Failure: c.Failure,
}
for _, verbose := range []bool{true, false} {
t.Run(fmt.Sprintf("%s_%t", c.Description, verbose), func(t *testing.T) {
s := r.Render(res, c.Command, "log", verbose, models.Github)
if !verbose {
Equals(t, c.Expected, s)
} else {
Equals(t, c.Expected+"<details><summary>Log</summary>\n <p>\n\n```\nlog```\n</p></details>\n", s)
}
})
}
}
}
func TestRenderErrAndFailure(t *testing.T) {
r := events.MarkdownRenderer{}
res := events.CommandResult{
Error: errors.New("error"),
Failure: "failure",
}
s := r.Render(res, events.PlanCommand, "", false, models.Github)
Equals(t, "**Plan Error**\n```\nerror\n```\n", s)
}
func TestRenderProjectResults(t *testing.T) {
cases := []struct {
Description string
Command events.CommandName
ProjectResults []events.ProjectResult
VCSHost models.VCSHostType
Expected string
}{
{
"no projects",
events.PlanCommand,
[]events.ProjectResult{},
models.Github,
"Ran Plan for 0 projects:\n\n\n",
},
{
"single successful plan",
events.PlanCommand,
[]events.ProjectResult{
{
PlanSuccess: &events.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
},
},
models.Github,
`Ran Plan for dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
* $atlantis apply -d path -w workspace$
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
* :repeat: To **plan** this project again, comment:
* $atlantis plan -d path -w workspace$
---
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
* $atlantis apply$
`,
},
{
"single successful plan with project name",
events.PlanCommand,
[]events.ProjectResult{
{
PlanSuccess: &events.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
},
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`Ran Plan for project: $projectname$ dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
* $atlantis apply -d path -w workspace$
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
* :repeat: To **plan** this project again, comment:
* $atlantis plan -d path -w workspace$
---
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
* $atlantis apply$
`,
},
{
"single successful apply",
events.ApplyCommand,
[]events.ProjectResult{
{
ApplySuccess: "success",
Workspace: "workspace",
RepoRelDir: "path",
},
},
models.Github,
`Ran Apply for dir: $path$ workspace: $workspace$
$$$diff
success
$$$
`,
},
{
"single successful apply with project name",
events.ApplyCommand,
[]events.ProjectResult{
{
ApplySuccess: "success",
Workspace: "workspace",
RepoRelDir: "path",
ProjectName: "projectname",
},
},
models.Github,
`Ran Apply for project: $projectname$ dir: $path$ workspace: $workspace$
$$$diff
success
$$$
`,
},
{
"multiple successful plans",
events.PlanCommand,
[]events.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PlanSuccess: &events.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
ProjectName: "projectname",
PlanSuccess: &events.PlanSuccess{
TerraformOutput: "terraform-output2",
LockURL: "lock-url2",
ApplyCmd: "atlantis apply -d path2 -w workspace",
RePlanCmd: "atlantis plan -d path2 -w workspace",
},
},
},
models.Github,
`Ran Plan for 2 projects:
1. dir: $path$ workspace: $workspace$
1. project: $projectname$ dir: $path2$ workspace: $workspace$
### 1. dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
* $atlantis apply -d path -w workspace$
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
* :repeat: To **plan** this project again, comment:
* $atlantis plan -d path -w workspace$
---
### 2. project: $projectname$ dir: $path2$ workspace: $workspace$
$$$diff
terraform-output2
$$$
* :arrow_forward: To **apply** this plan, comment:
* $atlantis apply -d path2 -w workspace$
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url2)
* :repeat: To **plan** this project again, comment:
* $atlantis plan -d path2 -w workspace$
---
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
* $atlantis apply$
`,
},
{
"multiple successful applies",
events.ApplyCommand,
[]events.ProjectResult{
{
RepoRelDir: "path",
Workspace: "workspace",
ProjectName: "projectname",
ApplySuccess: "success",
},
{
RepoRelDir: "path2",
Workspace: "workspace",
ApplySuccess: "success2",
},
},
models.Github,
`Ran Apply for 2 projects:
1. project: $projectname$ dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
### 1. project: $projectname$ dir: $path$ workspace: $workspace$
$$$diff
success
$$$
---
### 2. dir: $path2$ workspace: $workspace$
$$$diff
success2
$$$
---
`,
},
{
"single errored plan",
events.PlanCommand,
[]events.ProjectResult{
{
Error: errors.New("error"),
RepoRelDir: "path",
Workspace: "workspace",
},
},
models.Github,
`Ran Plan for dir: $path$ workspace: $workspace$
**Plan Error**
$$$
error
$$$
`,
},
{
"single failed plan",
events.PlanCommand,
[]events.ProjectResult{
{
RepoRelDir: "path",
Workspace: "workspace",
Failure: "failure",
},
},
models.Github,
`Ran Plan for dir: $path$ workspace: $workspace$
**Plan Failed**: failure
`,
},
{
"successful, failed, and errored plan",
events.PlanCommand,
[]events.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
PlanSuccess: &events.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
ApplyCmd: "atlantis apply -d path -w workspace",
RePlanCmd: "atlantis plan -d path -w workspace",
},
},
{
Workspace: "workspace",
RepoRelDir: "path2",
Failure: "failure",
},
{
Workspace: "workspace",
RepoRelDir: "path3",
ProjectName: "projectname",
Error: errors.New("error"),
},
},
models.Github,
`Ran Plan for 3 projects:
1. dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
1. project: $projectname$ dir: $path3$ workspace: $workspace$
### 1. dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
* $atlantis apply -d path -w workspace$
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
* :repeat: To **plan** this project again, comment:
* $atlantis plan -d path -w workspace$
---
### 2. dir: $path2$ workspace: $workspace$
**Plan Failed**: failure
---
### 3. project: $projectname$ dir: $path3$ workspace: $workspace$
**Plan Error**
$$$
error
$$$
---
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
* $atlantis apply$
`,
},
{
"successful, failed, and errored apply",
events.ApplyCommand,
[]events.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
ApplySuccess: "success",
},
{
Workspace: "workspace",
RepoRelDir: "path2",
Failure: "failure",
},
{
Workspace: "workspace",
RepoRelDir: "path3",
Error: errors.New("error"),
},
},
models.Github,
`Ran Apply for 3 projects:
1. dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
1. dir: $path3$ workspace: $workspace$
### 1. dir: $path$ workspace: $workspace$
$$$diff
success
$$$
---
### 2. dir: $path2$ workspace: $workspace$
**Apply Failed**: failure
---
### 3. dir: $path3$ workspace: $workspace$
**Apply Error**
$$$
error
$$$
---
`,
},
{
"successful, failed, and errored apply",
events.ApplyCommand,
[]events.ProjectResult{
{
Workspace: "workspace",
RepoRelDir: "path",
ApplySuccess: "success",
},
{
Workspace: "workspace",
RepoRelDir: "path2",
Failure: "failure",
},
{
Workspace: "workspace",
RepoRelDir: "path3",
Error: errors.New("error"),
},
},
models.Github,
`Ran Apply for 3 projects:
1. dir: $path$ workspace: $workspace$
1. dir: $path2$ workspace: $workspace$
1. dir: $path3$ workspace: $workspace$
### 1. dir: $path$ workspace: $workspace$
$$$diff
success
$$$
---
### 2. dir: $path2$ workspace: $workspace$
**Apply Failed**: failure
---
### 3. dir: $path3$ workspace: $workspace$
**Apply Error**
$$$
error
$$$
---
`,
},
}
r := events.MarkdownRenderer{}
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
res := events.CommandResult{
ProjectResults: c.ProjectResults,
}
for _, verbose := range []bool{true, false} {
t.Run(c.Description, func(t *testing.T) {
s := r.Render(res, c.Command, "log", verbose, c.VCSHost)
expWithBackticks := strings.Replace(c.Expected, "$", "`", -1)
if !verbose {
Equals(t, expWithBackticks, s)
} else {
Equals(t, expWithBackticks+"<details><summary>Log</summary>\n <p>\n\n```\nlog```\n</p></details>\n", s)
}
})
}
})
}
}
// Test that if the output is longer than 12 lines, it gets wrapped on the right
// VCS hosts during an error.
func TestRenderProjectResults_WrappedErr(t *testing.T) {
cases := []struct {
VCSHost models.VCSHostType
GitlabCommonMarkSupport bool
Output string
ShouldWrap bool
}{
{
VCSHost: models.Github,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.Github,
Output: strings.Repeat("line\n", 13),
ShouldWrap: true,
},
{
VCSHost: models.Gitlab,
GitlabCommonMarkSupport: false,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.Gitlab,
GitlabCommonMarkSupport: false,
Output: strings.Repeat("line\n", 13),
ShouldWrap: false,
},
{
VCSHost: models.Gitlab,
GitlabCommonMarkSupport: true,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.Gitlab,
GitlabCommonMarkSupport: true,
Output: strings.Repeat("line\n", 13),
ShouldWrap: true,
},
{
VCSHost: models.BitbucketCloud,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.BitbucketCloud,
Output: strings.Repeat("line\n", 13),
ShouldWrap: false,
},
{
VCSHost: models.BitbucketServer,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.BitbucketServer,
Output: strings.Repeat("line\n", 13),
ShouldWrap: false,
},
}
for _, c := range cases {
t.Run(fmt.Sprintf("%s_%v", c.VCSHost.String(), c.ShouldWrap),
func(t *testing.T) {
mr := events.MarkdownRenderer{
GitlabSupportsCommonMark: c.GitlabCommonMarkSupport,
}
rendered := mr.Render(events.CommandResult{
ProjectResults: []events.ProjectResult{
{
RepoRelDir: ".",
Workspace: "default",
Error: errors.New(c.Output),
},
},
}, events.PlanCommand, "log", false, c.VCSHost)
var exp string
if c.ShouldWrap {
exp = `Ran Plan for dir: $.$ workspace: $default$
**Plan Error**
<details><summary>Show Output</summary>
$$$
` + c.Output + `
$$$
</details>
`
} else {
exp = `Ran Plan for dir: $.$ workspace: $default$
**Plan Error**
$$$
` + c.Output + `
$$$
`
}
expWithBackticks := strings.Replace(exp, "$", "`", -1)
Equals(t, expWithBackticks, rendered)
})
}
}
// Test that if the output is longer than 12 lines, it gets wrapped on the right
// VCS hosts for a single project.
func TestRenderProjectResults_WrapSingleProject(t *testing.T) {
cases := []struct {
VCSHost models.VCSHostType
GitlabCommonMarkSupport bool
Output string
ShouldWrap bool
}{
{
VCSHost: models.Github,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.Github,
Output: strings.Repeat("line\n", 13),
ShouldWrap: true,
},
{
VCSHost: models.Gitlab,
GitlabCommonMarkSupport: false,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.Gitlab,
GitlabCommonMarkSupport: false,
Output: strings.Repeat("line\n", 13),
ShouldWrap: false,
},
{
VCSHost: models.Gitlab,
GitlabCommonMarkSupport: true,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.Gitlab,
GitlabCommonMarkSupport: true,
Output: strings.Repeat("line\n", 13),
ShouldWrap: true,
},
{
VCSHost: models.BitbucketCloud,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.BitbucketCloud,
Output: strings.Repeat("line\n", 13),
ShouldWrap: false,
},
{
VCSHost: models.BitbucketServer,
Output: strings.Repeat("line\n", 1),
ShouldWrap: false,
},
{
VCSHost: models.BitbucketServer,
Output: strings.Repeat("line\n", 13),
ShouldWrap: false,
},
}
for _, c := range cases {
for _, cmd := range []events.CommandName{events.PlanCommand, events.ApplyCommand} {
t.Run(fmt.Sprintf("%s_%s_%v", c.VCSHost.String(), cmd.String(), c.ShouldWrap),
func(t *testing.T) {
mr := events.MarkdownRenderer{
GitlabSupportsCommonMark: c.GitlabCommonMarkSupport,
}
var pr events.ProjectResult
switch cmd {
case events.PlanCommand:
pr = events.ProjectResult{
RepoRelDir: ".",
Workspace: "default",
PlanSuccess: &events.PlanSuccess{
TerraformOutput: c.Output,
LockURL: "lock-url",
RePlanCmd: "replancmd",
ApplyCmd: "applycmd",
},
}
case events.ApplyCommand:
pr = events.ProjectResult{
RepoRelDir: ".",
Workspace: "default",
ApplySuccess: c.Output,
}
}
rendered := mr.Render(events.CommandResult{
ProjectResults: []events.ProjectResult{pr},
}, cmd, "log", false, c.VCSHost)
// Check result.
var exp string
switch cmd {
case events.PlanCommand:
if c.ShouldWrap {
exp = `Ran Plan for dir: $.$ workspace: $default$
<details><summary>Show Output</summary>
$$$diff
` + c.Output + `
$$$
* :arrow_forward: To **apply** this plan, comment:
* $applycmd$
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
* :repeat: To **plan** this project again, comment:
* $replancmd$
</details>
---
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
* $atlantis apply$
`
} else {
exp = `Ran Plan for dir: $.$ workspace: $default$
$$$diff
` + c.Output + `
$$$
* :arrow_forward: To **apply** this plan, comment:
* $applycmd$
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
* :repeat: To **plan** this project again, comment:
* $replancmd$
---
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
* $atlantis apply$
`
}
case events.ApplyCommand:
if c.ShouldWrap {
exp = `Ran Apply for dir: $.$ workspace: $default$
<details><summary>Show Output</summary>
$$$diff
` + c.Output + `
$$$
</details>
`
} else {
exp = `Ran Apply for dir: $.$ workspace: $default$
$$$diff
` + c.Output + `
$$$
`
}
}
expWithBackticks := strings.Replace(exp, "$", "`", -1)
Equals(t, expWithBackticks, rendered)
})
}
}
}
func TestRenderProjectResults_MultiProjectApplyWrapped(t *testing.T) {
mr := events.MarkdownRenderer{}
tfOut := strings.Repeat("line\n", 13)
rendered := mr.Render(events.CommandResult{
ProjectResults: []events.ProjectResult{
{
RepoRelDir: ".",
Workspace: "staging",
ApplySuccess: tfOut,
},
{
RepoRelDir: ".",
Workspace: "production",
ApplySuccess: tfOut,
},
},
}, events.ApplyCommand, "log", false, models.Github)
exp := `Ran Apply for 2 projects:
1. dir: $.$ workspace: $staging$
1. dir: $.$ workspace: $production$
### 1. dir: $.$ workspace: $staging$
<details><summary>Show Output</summary>
$$$diff
` + tfOut + `
$$$
</details>
---
### 2. dir: $.$ workspace: $production$
<details><summary>Show Output</summary>
$$$diff
` + tfOut + `
$$$
</details>
---
`
expWithBackticks := strings.Replace(exp, "$", "`", -1)
Equals(t, expWithBackticks, rendered)
}
func TestRenderProjectResults_MultiProjectPlanWrapped(t *testing.T) {
mr := events.MarkdownRenderer{}
tfOut := strings.Repeat("line\n", 13)
rendered := mr.Render(events.CommandResult{
ProjectResults: []events.ProjectResult{
{
RepoRelDir: ".",
Workspace: "staging",
PlanSuccess: &events.PlanSuccess{
TerraformOutput: tfOut,
LockURL: "staging-lock-url",
ApplyCmd: "staging-apply-cmd",
RePlanCmd: "staging-replan-cmd",
},
},
{
RepoRelDir: ".",
Workspace: "production",
PlanSuccess: &events.PlanSuccess{
TerraformOutput: tfOut,
LockURL: "production-lock-url",
ApplyCmd: "production-apply-cmd",
RePlanCmd: "production-replan-cmd",
},
},
},
}, events.PlanCommand, "log", false, models.Github)
exp := `Ran Plan for 2 projects:
1. dir: $.$ workspace: $staging$
1. dir: $.$ workspace: $production$
### 1. dir: $.$ workspace: $staging$
<details><summary>Show Output</summary>
$$$diff
` + tfOut + `
$$$
* :arrow_forward: To **apply** this plan, comment:
* $staging-apply-cmd$
* :put_litter_in_its_place: To **delete** this plan click [here](staging-lock-url)
* :repeat: To **plan** this project again, comment:
* $staging-replan-cmd$
</details>
---
### 2. dir: $.$ workspace: $production$
<details><summary>Show Output</summary>
$$$diff
` + tfOut + `
$$$
* :arrow_forward: To **apply** this plan, comment:
* $production-apply-cmd$
* :put_litter_in_its_place: To **delete** this plan click [here](production-lock-url)
* :repeat: To **plan** this project again, comment:
* $production-replan-cmd$
</details>
---
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
* $atlantis apply$
`
expWithBackticks := strings.Replace(exp, "$", "`", -1)
Equals(t, expWithBackticks, rendered)
}