mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-29 04:28:22 +00:00
This is two changes: Replacing all usages of SimpleLogger with it's interface SimpleLogging which makes it easier to swap out loggers going forward Nukes SimpleLogger in favor of StructuredLogger which allows us to better analyze logs in our log management system we choose.
412 lines
13 KiB
Go
412 lines
13 KiB
Go
package server_test
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/runatlantis/atlantis/server/events/db"
|
|
"github.com/runatlantis/atlantis/server/events/locking"
|
|
|
|
"github.com/gorilla/mux"
|
|
. "github.com/petergtz/pegomock"
|
|
"github.com/runatlantis/atlantis/server"
|
|
"github.com/runatlantis/atlantis/server/events"
|
|
|
|
"github.com/runatlantis/atlantis/server/events/locking/mocks"
|
|
mocks2 "github.com/runatlantis/atlantis/server/events/mocks"
|
|
"github.com/runatlantis/atlantis/server/events/models"
|
|
vcsmocks "github.com/runatlantis/atlantis/server/events/vcs/mocks"
|
|
"github.com/runatlantis/atlantis/server/logging"
|
|
sMocks "github.com/runatlantis/atlantis/server/mocks"
|
|
. "github.com/runatlantis/atlantis/testing"
|
|
)
|
|
|
|
func AnyRepo() models.Repo {
|
|
RegisterMatcher(NewAnyMatcher(reflect.TypeOf(models.Repo{})))
|
|
return models.Repo{}
|
|
}
|
|
|
|
func TestCreateApplyLock(t *testing.T) {
|
|
t.Run("Creates apply lock", func(t *testing.T) {
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
w := httptest.NewRecorder()
|
|
|
|
layout := "2006-01-02T15:04:05.000Z"
|
|
strLockTime := "2020-09-01T00:45:26.371Z"
|
|
expLockTime := "2020-09-01 00:45:26"
|
|
lockTime, _ := time.Parse(layout, strLockTime)
|
|
|
|
l := mocks.NewMockApplyLocker()
|
|
When(l.LockApply()).ThenReturn(locking.ApplyCommandLock{
|
|
Locked: true,
|
|
Time: lockTime,
|
|
}, nil)
|
|
|
|
lc := server.LocksController{
|
|
Logger: logging.NewNoopLogger(t),
|
|
ApplyLocker: l,
|
|
}
|
|
lc.LockApply(w, req)
|
|
|
|
responseContains(t, w, http.StatusOK, fmt.Sprintf("Apply Lock is acquired on %s", expLockTime))
|
|
})
|
|
|
|
t.Run("Apply lock creation fails", func(t *testing.T) {
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
w := httptest.NewRecorder()
|
|
|
|
l := mocks.NewMockApplyLocker()
|
|
When(l.LockApply()).ThenReturn(locking.ApplyCommandLock{
|
|
Locked: false,
|
|
}, errors.New("failed to acquire lock"))
|
|
|
|
lc := server.LocksController{
|
|
Logger: logging.NewNoopLogger(t),
|
|
ApplyLocker: l,
|
|
}
|
|
lc.LockApply(w, req)
|
|
|
|
responseContains(t, w, http.StatusInternalServerError, fmt.Sprintf("creating apply lock failed with: failed to acquire lock"))
|
|
})
|
|
}
|
|
|
|
func TestUnlockApply(t *testing.T) {
|
|
t.Run("Apply lock deleted successfully", func(t *testing.T) {
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
w := httptest.NewRecorder()
|
|
|
|
l := mocks.NewMockApplyLocker()
|
|
When(l.UnlockApply()).ThenReturn(nil)
|
|
|
|
lc := server.LocksController{
|
|
Logger: logging.NewNoopLogger(t),
|
|
ApplyLocker: l,
|
|
}
|
|
lc.UnlockApply(w, req)
|
|
|
|
responseContains(t, w, http.StatusOK, fmt.Sprintf("Deleted apply lock"))
|
|
})
|
|
|
|
t.Run("Apply lock deletion failed", func(t *testing.T) {
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
w := httptest.NewRecorder()
|
|
|
|
l := mocks.NewMockApplyLocker()
|
|
When(l.UnlockApply()).ThenReturn(errors.New("failed to delete lock"))
|
|
|
|
lc := server.LocksController{
|
|
Logger: logging.NewNoopLogger(t),
|
|
ApplyLocker: l,
|
|
}
|
|
lc.UnlockApply(w, req)
|
|
|
|
responseContains(t, w, http.StatusInternalServerError, fmt.Sprintf("deleting apply lock failed with: failed to delete lock"))
|
|
})
|
|
}
|
|
|
|
func TestGetLockRoute_NoLockID(t *testing.T) {
|
|
t.Log("If there is no lock ID in the request then we should get a 400")
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
w := httptest.NewRecorder()
|
|
lc := server.LocksController{
|
|
Logger: logging.NewNoopLogger(t),
|
|
}
|
|
lc.GetLock(w, req)
|
|
responseContains(t, w, http.StatusBadRequest, "No lock id in request")
|
|
}
|
|
|
|
func TestGetLock_InvalidLockID(t *testing.T) {
|
|
t.Log("If the lock ID is invalid then we should get a 400")
|
|
lc := server.LocksController{
|
|
Logger: logging.NewNoopLogger(t),
|
|
}
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
req = mux.SetURLVars(req, map[string]string{"id": "%A@"})
|
|
w := httptest.NewRecorder()
|
|
lc.GetLock(w, req)
|
|
responseContains(t, w, http.StatusBadRequest, "Invalid lock id")
|
|
}
|
|
|
|
func TestGetLock_LockerErr(t *testing.T) {
|
|
t.Log("If there is an error retrieving the lock, a 500 is returned")
|
|
RegisterMockTestingT(t)
|
|
l := mocks.NewMockLocker()
|
|
When(l.GetLock("id")).ThenReturn(nil, errors.New("err"))
|
|
lc := server.LocksController{
|
|
Logger: logging.NewNoopLogger(t),
|
|
Locker: l,
|
|
}
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
|
w := httptest.NewRecorder()
|
|
lc.GetLock(w, req)
|
|
responseContains(t, w, http.StatusInternalServerError, "err")
|
|
}
|
|
|
|
func TestGetLock_None(t *testing.T) {
|
|
t.Log("If there is no lock at that ID we get a 404")
|
|
RegisterMockTestingT(t)
|
|
l := mocks.NewMockLocker()
|
|
When(l.GetLock("id")).ThenReturn(nil, nil)
|
|
lc := server.LocksController{
|
|
Logger: logging.NewNoopLogger(t),
|
|
Locker: l,
|
|
}
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
|
w := httptest.NewRecorder()
|
|
lc.GetLock(w, req)
|
|
responseContains(t, w, http.StatusNotFound, "No lock found at id \"id\"")
|
|
}
|
|
|
|
func TestGetLock_Success(t *testing.T) {
|
|
t.Log("Should be able to render a lock successfully")
|
|
RegisterMockTestingT(t)
|
|
l := mocks.NewMockLocker()
|
|
When(l.GetLock("id")).ThenReturn(&models.ProjectLock{
|
|
Project: models.Project{RepoFullName: "owner/repo", Path: "path"},
|
|
Pull: models.PullRequest{URL: "url", Author: "lkysow"},
|
|
Workspace: "workspace",
|
|
}, nil)
|
|
tmpl := sMocks.NewMockTemplateWriter()
|
|
atlantisURL, err := url.Parse("https://example.com/basepath")
|
|
Ok(t, err)
|
|
lc := server.LocksController{
|
|
Logger: logging.NewNoopLogger(t),
|
|
Locker: l,
|
|
LockDetailTemplate: tmpl,
|
|
AtlantisVersion: "1300135",
|
|
AtlantisURL: atlantisURL,
|
|
}
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
|
w := httptest.NewRecorder()
|
|
lc.GetLock(w, req)
|
|
tmpl.VerifyWasCalledOnce().Execute(w, server.LockDetailData{
|
|
LockKeyEncoded: "id",
|
|
LockKey: "id",
|
|
RepoOwner: "owner",
|
|
RepoName: "repo",
|
|
PullRequestLink: "url",
|
|
LockedBy: "lkysow",
|
|
Workspace: "workspace",
|
|
AtlantisVersion: "1300135",
|
|
CleanedBasePath: "/basepath",
|
|
})
|
|
responseContains(t, w, http.StatusOK, "")
|
|
}
|
|
|
|
func TestDeleteLock_NoLockID(t *testing.T) {
|
|
t.Log("If there is no lock ID in the request then we should get a 400")
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
w := httptest.NewRecorder()
|
|
lc := server.LocksController{Logger: logging.NewNoopLogger(t)}
|
|
lc.DeleteLock(w, req)
|
|
responseContains(t, w, http.StatusBadRequest, "No lock id in request")
|
|
}
|
|
|
|
func TestDeleteLock_InvalidLockID(t *testing.T) {
|
|
t.Log("If the lock ID is invalid then we should get a 400")
|
|
lc := server.LocksController{Logger: logging.NewNoopLogger(t)}
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
req = mux.SetURLVars(req, map[string]string{"id": "%A@"})
|
|
w := httptest.NewRecorder()
|
|
lc.DeleteLock(w, req)
|
|
responseContains(t, w, http.StatusBadRequest, "Invalid lock id \"%A@\"")
|
|
}
|
|
|
|
func TestDeleteLock_LockerErr(t *testing.T) {
|
|
t.Log("If there is an error retrieving the lock, a 500 is returned")
|
|
RegisterMockTestingT(t)
|
|
dlc := mocks2.NewMockDeleteLockCommand()
|
|
When(dlc.DeleteLock("id")).ThenReturn(nil, errors.New("err"))
|
|
lc := server.LocksController{
|
|
DeleteLockCommand: dlc,
|
|
Logger: logging.NewNoopLogger(t),
|
|
}
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
|
w := httptest.NewRecorder()
|
|
lc.DeleteLock(w, req)
|
|
responseContains(t, w, http.StatusInternalServerError, "err")
|
|
}
|
|
|
|
func TestDeleteLock_None(t *testing.T) {
|
|
t.Log("If there is no lock at that ID we get a 404")
|
|
RegisterMockTestingT(t)
|
|
dlc := mocks2.NewMockDeleteLockCommand()
|
|
When(dlc.DeleteLock("id")).ThenReturn(nil, nil)
|
|
lc := server.LocksController{
|
|
DeleteLockCommand: dlc,
|
|
Logger: logging.NewNoopLogger(t),
|
|
}
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
|
w := httptest.NewRecorder()
|
|
lc.DeleteLock(w, req)
|
|
responseContains(t, w, http.StatusNotFound, "No lock found at id \"id\"")
|
|
}
|
|
|
|
func TestDeleteLock_OldFormat(t *testing.T) {
|
|
t.Log("If the lock doesn't have BaseRepo set it is deleted successfully")
|
|
RegisterMockTestingT(t)
|
|
cp := vcsmocks.NewMockClient()
|
|
dlc := mocks2.NewMockDeleteLockCommand()
|
|
When(dlc.DeleteLock("id")).ThenReturn(&models.ProjectLock{}, nil)
|
|
lc := server.LocksController{
|
|
DeleteLockCommand: dlc,
|
|
Logger: logging.NewNoopLogger(t),
|
|
VCSClient: cp,
|
|
}
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
|
w := httptest.NewRecorder()
|
|
lc.DeleteLock(w, req)
|
|
responseContains(t, w, http.StatusOK, "Deleted lock id \"id\"")
|
|
cp.VerifyWasCalled(Never()).CreateComment(AnyRepo(), AnyInt(), AnyString(), AnyString())
|
|
}
|
|
|
|
func TestDeleteLock_UpdateProjectStatus(t *testing.T) {
|
|
t.Log("When deleting a lock, pull status has to be updated to reflect discarded plan")
|
|
RegisterMockTestingT(t)
|
|
|
|
repoName := "owner/repo"
|
|
projectPath := "path"
|
|
workspaceName := "workspace"
|
|
|
|
cp := vcsmocks.NewMockClient()
|
|
l := mocks2.NewMockDeleteLockCommand()
|
|
workingDir := mocks2.NewMockWorkingDir()
|
|
workingDirLocker := events.NewDefaultWorkingDirLocker()
|
|
pull := models.PullRequest{
|
|
BaseRepo: models.Repo{FullName: repoName},
|
|
}
|
|
When(l.DeleteLock("id")).ThenReturn(&models.ProjectLock{
|
|
Pull: pull,
|
|
Workspace: workspaceName,
|
|
Project: models.Project{
|
|
Path: projectPath,
|
|
RepoFullName: repoName,
|
|
},
|
|
}, nil)
|
|
tmp, cleanup := TempDir(t)
|
|
defer cleanup()
|
|
db, err := db.New(tmp)
|
|
Ok(t, err)
|
|
// Seed the DB with a successful plan for that project (that is later discarded).
|
|
_, err = db.UpdatePullWithResults(pull, []models.ProjectResult{
|
|
{
|
|
Command: models.PlanCommand,
|
|
RepoRelDir: projectPath,
|
|
Workspace: workspaceName,
|
|
PlanSuccess: &models.PlanSuccess{
|
|
TerraformOutput: "tf-output",
|
|
LockURL: "lock-url",
|
|
},
|
|
},
|
|
})
|
|
Ok(t, err)
|
|
lc := server.LocksController{
|
|
DeleteLockCommand: l,
|
|
Logger: logging.NewNoopLogger(t),
|
|
VCSClient: cp,
|
|
WorkingDirLocker: workingDirLocker,
|
|
WorkingDir: workingDir,
|
|
DB: db,
|
|
}
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
|
w := httptest.NewRecorder()
|
|
lc.DeleteLock(w, req)
|
|
responseContains(t, w, http.StatusOK, "Deleted lock id \"id\"")
|
|
status, err := db.GetPullStatus(pull)
|
|
Ok(t, err)
|
|
Assert(t, status != nil, "status was nil")
|
|
Equals(t, []models.ProjectStatus{
|
|
{
|
|
Workspace: workspaceName,
|
|
RepoRelDir: projectPath,
|
|
Status: models.DiscardedPlanStatus,
|
|
},
|
|
}, status.Projects)
|
|
}
|
|
|
|
func TestDeleteLock_CommentFailed(t *testing.T) {
|
|
t.Log("If the commenting fails we still return success")
|
|
RegisterMockTestingT(t)
|
|
dlc := mocks2.NewMockDeleteLockCommand()
|
|
When(dlc.DeleteLock("id")).ThenReturn(&models.ProjectLock{
|
|
Pull: models.PullRequest{
|
|
BaseRepo: models.Repo{FullName: "owner/repo"},
|
|
},
|
|
}, nil)
|
|
cp := vcsmocks.NewMockClient()
|
|
workingDir := mocks2.NewMockWorkingDir()
|
|
workingDirLocker := events.NewDefaultWorkingDirLocker()
|
|
When(cp.CreateComment(AnyRepo(), AnyInt(), AnyString(), AnyString())).ThenReturn(errors.New("err"))
|
|
tmp, cleanup := TempDir(t)
|
|
defer cleanup()
|
|
db, err := db.New(tmp)
|
|
Ok(t, err)
|
|
lc := server.LocksController{
|
|
DeleteLockCommand: dlc,
|
|
Logger: logging.NewNoopLogger(t),
|
|
VCSClient: cp,
|
|
WorkingDir: workingDir,
|
|
WorkingDirLocker: workingDirLocker,
|
|
DB: db,
|
|
}
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
|
w := httptest.NewRecorder()
|
|
lc.DeleteLock(w, req)
|
|
responseContains(t, w, http.StatusOK, "Deleted lock id \"id\"")
|
|
}
|
|
|
|
func TestDeleteLock_CommentSuccess(t *testing.T) {
|
|
t.Log("We should comment back on the pull request if the lock is deleted")
|
|
RegisterMockTestingT(t)
|
|
cp := vcsmocks.NewMockClient()
|
|
dlc := mocks2.NewMockDeleteLockCommand()
|
|
workingDir := mocks2.NewMockWorkingDir()
|
|
workingDirLocker := events.NewDefaultWorkingDirLocker()
|
|
pull := models.PullRequest{
|
|
BaseRepo: models.Repo{FullName: "owner/repo"},
|
|
}
|
|
When(dlc.DeleteLock("id")).ThenReturn(&models.ProjectLock{
|
|
Pull: pull,
|
|
Workspace: "workspace",
|
|
Project: models.Project{
|
|
Path: "path",
|
|
RepoFullName: "owner/repo",
|
|
},
|
|
}, nil)
|
|
tmp, cleanup := TempDir(t)
|
|
defer cleanup()
|
|
db, err := db.New(tmp)
|
|
Ok(t, err)
|
|
lc := server.LocksController{
|
|
DeleteLockCommand: dlc,
|
|
Logger: logging.NewNoopLogger(t),
|
|
VCSClient: cp,
|
|
DB: db,
|
|
WorkingDir: workingDir,
|
|
WorkingDirLocker: workingDirLocker,
|
|
}
|
|
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
|
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
|
w := httptest.NewRecorder()
|
|
lc.DeleteLock(w, req)
|
|
responseContains(t, w, http.StatusOK, "Deleted lock id \"id\"")
|
|
cp.VerifyWasCalled(Once()).CreateComment(pull.BaseRepo, pull.Num,
|
|
"**Warning**: The plan for dir: `path` workspace: `workspace` was **discarded** via the Atlantis UI.\n\n"+
|
|
"To `apply` this plan you must run `plan` again.", "")
|
|
}
|