Files
atlantis/server/controllers/api_controller_test.go
Patrick Vinograd 1f83f13cb5 feat: New API endpoint to list locks (#5328)
Signed-off-by: Patrick Vinograd <patrick.vinograd@devoted.com>
2025-02-16 15:49:05 +00:00

323 lines
8.9 KiB
Go

package controllers_test
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
. "github.com/petergtz/pegomock/v4"
"github.com/runatlantis/atlantis/server/controllers"
. "github.com/runatlantis/atlantis/server/core/locking/mocks"
"github.com/runatlantis/atlantis/server/events"
"github.com/runatlantis/atlantis/server/events/command"
. "github.com/runatlantis/atlantis/server/events/mocks"
"github.com/runatlantis/atlantis/server/events/models"
. "github.com/runatlantis/atlantis/server/events/vcs/mocks"
"github.com/runatlantis/atlantis/server/logging"
"github.com/runatlantis/atlantis/server/metrics"
. "github.com/runatlantis/atlantis/testing"
)
const atlantisTokenHeader = "X-Atlantis-Token"
const atlantisToken = "token"
func TestAPIController_Plan(t *testing.T) {
ac, projectCommandBuilder, projectCommandRunner := setup(t)
cases := []struct {
repository string
ref string
vcsType string
pr int
projects []string
paths []struct {
Directory string
Workspace string
}
}{
{
repository: "Repo",
ref: "main",
vcsType: "Gitlab",
projects: []string{"default"},
},
{
repository: "Repo",
ref: "main",
vcsType: "Gitlab",
pr: 1,
},
{
repository: "Repo",
ref: "main",
vcsType: "Gitlab",
paths: []struct {
Directory string
Workspace string
}{
{
Directory: ".",
Workspace: "myworkspace",
},
{
Directory: "./myworkspace2",
Workspace: "myworkspace2",
},
},
},
{
repository: "Repo",
ref: "main",
vcsType: "Gitlab",
pr: 1,
projects: []string{"test"},
paths: []struct {
Directory string
Workspace string
}{
{
Directory: ".",
Workspace: "myworkspace",
},
},
},
}
expectedCalls := 0
for _, c := range cases {
body, _ := json.Marshal(controllers.APIRequest{
Repository: c.repository,
Ref: c.ref,
Type: c.vcsType,
PR: c.pr,
Projects: c.projects,
Paths: c.paths,
})
req, _ := http.NewRequest("POST", "", bytes.NewBuffer(body))
req.Header.Set(atlantisTokenHeader, atlantisToken)
w := httptest.NewRecorder()
ac.Plan(w, req)
ResponseContains(t, w, http.StatusOK, "")
expectedCalls += len(c.projects)
expectedCalls += len(c.paths)
}
projectCommandBuilder.VerifyWasCalled(Times(expectedCalls)).BuildPlanCommands(Any[*command.Context](), Any[*events.CommentCommand]())
projectCommandRunner.VerifyWasCalled(Times(expectedCalls)).Plan(Any[command.ProjectContext]())
}
func TestAPIController_Apply(t *testing.T) {
ac, projectCommandBuilder, projectCommandRunner := setup(t)
cases := []struct {
repository string
ref string
vcsType string
pr int
projects []string
paths []struct {
Directory string
Workspace string
}
}{
{
repository: "Repo",
ref: "main",
vcsType: "Gitlab",
projects: []string{"default"},
},
{
repository: "Repo",
ref: "main",
vcsType: "Gitlab",
pr: 1,
},
{
repository: "Repo",
ref: "main",
vcsType: "Gitlab",
paths: []struct {
Directory string
Workspace string
}{
{
Directory: ".",
Workspace: "myworkspace",
},
{
Directory: "./myworkspace2",
Workspace: "myworkspace2",
},
},
},
{
repository: "Repo",
ref: "main",
vcsType: "Gitlab",
pr: 1,
projects: []string{"test"},
paths: []struct {
Directory string
Workspace string
}{
{
Directory: ".",
Workspace: "myworkspace",
},
},
},
}
expectedCalls := 0
for _, c := range cases {
body, _ := json.Marshal(controllers.APIRequest{
Repository: c.repository,
Ref: c.ref,
Type: c.vcsType,
PR: c.pr,
Projects: c.projects,
Paths: c.paths,
})
req, _ := http.NewRequest("POST", "", bytes.NewBuffer(body))
req.Header.Set(atlantisTokenHeader, atlantisToken)
w := httptest.NewRecorder()
ac.Apply(w, req)
ResponseContains(t, w, http.StatusOK, "")
expectedCalls += len(c.projects)
expectedCalls += len(c.paths)
}
projectCommandBuilder.VerifyWasCalled(Times(expectedCalls)).BuildApplyCommands(Any[*command.Context](), Any[*events.CommentCommand]())
projectCommandRunner.VerifyWasCalled(Times(expectedCalls)).Plan(Any[command.ProjectContext]())
projectCommandRunner.VerifyWasCalled(Times(expectedCalls)).Apply(Any[command.ProjectContext]())
}
func TestAPIController_ListLocks(t *testing.T) {
ac, _, _ := setup(t)
time := time.Now()
expected := controllers.ListLocksResult{[]controllers.LockDetail{
{
Name: "lock-id",
ProjectName: "terraform",
ProjectRepo: "owner/repo",
ProjectRepoPath: "/path",
PullID: 123,
PullURL: "url",
User: "jdoe",
Workspace: "default",
Time: time,
},
},
}
mockLock := models.ProjectLock{
Project: models.Project{ProjectName: "terraform", RepoFullName: "owner/repo", Path: "/path"},
Pull: models.PullRequest{Num: 123, URL: "url", Author: "lkysow"},
User: models.User{Username: "jdoe"},
Workspace: "default",
Time: time,
}
mockLocks := map[string]models.ProjectLock{
"lock-id": mockLock,
}
When(ac.Locker.List()).ThenReturn(mockLocks, nil)
req, _ := http.NewRequest("GET", "", nil)
w := httptest.NewRecorder()
ac.ListLocks(w, req)
response, _ := io.ReadAll(w.Result().Body)
var result controllers.ListLocksResult
err := json.Unmarshal(response, &result)
Ok(t, err)
Equals(t, expected, result)
}
func TestAPIController_ListLocksEmpty(t *testing.T) {
ac, _, _ := setup(t)
expected := controllers.ListLocksResult{}
mockLocks := map[string]models.ProjectLock{}
When(ac.Locker.List()).ThenReturn(mockLocks, nil)
req, _ := http.NewRequest("GET", "", nil)
w := httptest.NewRecorder()
ac.ListLocks(w, req)
response, _ := io.ReadAll(w.Result().Body)
var result controllers.ListLocksResult
err := json.Unmarshal(response, &result)
Ok(t, err)
Equals(t, expected, result)
}
func setup(t *testing.T) (controllers.APIController, *MockProjectCommandBuilder, *MockProjectCommandRunner) {
RegisterMockTestingT(t)
locker := NewMockLocker()
logger := logging.NewNoopLogger(t)
parser := NewMockEventParsing()
repoAllowlistChecker, err := events.NewRepoAllowlistChecker("*")
scope, _, _ := metrics.NewLoggingScope(logger, "null")
vcsClient := NewMockClient()
workingDir := NewMockWorkingDir()
Ok(t, err)
workingDirLocker := NewMockWorkingDirLocker()
When(workingDirLocker.TryLock(Any[string](), Any[int](), Eq(events.DefaultWorkspace), Eq(events.DefaultRepoRelDir))).
ThenReturn(func() {}, nil)
projectCommandBuilder := NewMockProjectCommandBuilder()
When(projectCommandBuilder.BuildPlanCommands(Any[*command.Context](), Any[*events.CommentCommand]())).
ThenReturn([]command.ProjectContext{{
CommandName: command.Plan,
}}, nil)
When(projectCommandBuilder.BuildApplyCommands(Any[*command.Context](), Any[*events.CommentCommand]())).
ThenReturn([]command.ProjectContext{{
CommandName: command.Apply,
}}, nil)
projectCommandRunner := NewMockProjectCommandRunner()
When(projectCommandRunner.Plan(Any[command.ProjectContext]())).ThenReturn(command.ProjectResult{
PlanSuccess: &models.PlanSuccess{},
})
When(projectCommandRunner.Apply(Any[command.ProjectContext]())).ThenReturn(command.ProjectResult{
ApplySuccess: "success",
})
preWorkflowHooksCommandRunner := NewMockPreWorkflowHooksCommandRunner()
When(preWorkflowHooksCommandRunner.RunPreHooks(Any[*command.Context](), Any[*events.CommentCommand]())).ThenReturn(nil)
postWorkflowHooksCommandRunner := NewMockPostWorkflowHooksCommandRunner()
When(postWorkflowHooksCommandRunner.RunPostHooks(Any[*command.Context](), Any[*events.CommentCommand]())).ThenReturn(nil)
commitStatusUpdater := NewMockCommitStatusUpdater()
When(commitStatusUpdater.UpdateCombined(Any[logging.SimpleLogging](), Any[models.Repo](), Any[models.PullRequest](), Any[models.CommitStatus](), Any[command.Name]())).ThenReturn(nil)
ac := controllers.APIController{
APISecret: []byte(atlantisToken),
Locker: locker,
Logger: logger,
Scope: scope,
Parser: parser,
ProjectCommandBuilder: projectCommandBuilder,
ProjectPlanCommandRunner: projectCommandRunner,
ProjectApplyCommandRunner: projectCommandRunner,
PreWorkflowHooksCommandRunner: preWorkflowHooksCommandRunner,
PostWorkflowHooksCommandRunner: postWorkflowHooksCommandRunner,
VCSClient: vcsClient,
RepoAllowlistChecker: repoAllowlistChecker,
WorkingDir: workingDir,
WorkingDirLocker: workingDirLocker,
CommitStatusUpdater: commitStatusUpdater,
}
return ac, projectCommandBuilder, projectCommandRunner
}