woodpecker/server/api/agent_test.go

275 lines
8 KiB
Go

// Copyright 2024 Woodpecker Authors
//
// 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.
package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"go.woodpecker-ci.org/woodpecker/v2/server"
"go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/queue"
queue_mocks "go.woodpecker-ci.org/woodpecker/v2/server/queue/mocks"
mocks_manager "go.woodpecker-ci.org/woodpecker/v2/server/services/mocks"
store_mocks "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks"
"go.woodpecker-ci.org/woodpecker/v2/server/store/types"
)
var fakeAgent = &model.Agent{
ID: 1,
Name: "test-agent",
OwnerID: 1,
NoSchedule: false,
}
func TestGetAgents(t *testing.T) {
gin.SetMode(gin.TestMode)
t.Run("should get agents", func(t *testing.T) {
agents := []*model.Agent{fakeAgent}
mockStore := store_mocks.NewStore(t)
mockStore.On("AgentList", mock.Anything).Return(agents, nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("store", mockStore)
GetAgents(c)
c.Writer.WriteHeaderNow()
mockStore.AssertCalled(t, "AgentList", mock.Anything)
assert.Equal(t, http.StatusOK, w.Code)
var response []*model.Agent
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, agents, response)
})
}
func TestGetAgent(t *testing.T) {
gin.SetMode(gin.TestMode)
t.Run("should get agent", func(t *testing.T) {
mockStore := store_mocks.NewStore(t)
mockStore.On("AgentFind", int64(1)).Return(fakeAgent, nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("store", mockStore)
c.Params = gin.Params{{Key: "agent_id", Value: "1"}}
GetAgent(c)
c.Writer.WriteHeaderNow()
mockStore.AssertCalled(t, "AgentFind", int64(1))
assert.Equal(t, http.StatusOK, w.Code)
var response model.Agent
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, fakeAgent, &response)
})
t.Run("should return bad request for invalid agent id", func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "agent_id", Value: "invalid"}}
GetAgent(c)
c.Writer.WriteHeaderNow()
assert.Equal(t, http.StatusBadRequest, w.Code)
})
t.Run("should return not found for non-existent agent", func(t *testing.T) {
mockStore := store_mocks.NewStore(t)
mockStore.On("AgentFind", int64(2)).Return((*model.Agent)(nil), types.RecordNotExist)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("store", mockStore)
c.Params = gin.Params{{Key: "agent_id", Value: "2"}}
GetAgent(c)
c.Writer.WriteHeaderNow()
assert.Equal(t, http.StatusNotFound, w.Code)
})
}
func TestPatchAgent(t *testing.T) {
gin.SetMode(gin.TestMode)
t.Run("should update agent", func(t *testing.T) {
updatedAgent := *fakeAgent
updatedAgent.Name = "updated-agent"
mockStore := store_mocks.NewStore(t)
mockStore.On("AgentFind", int64(1)).Return(fakeAgent, nil)
mockStore.On("AgentUpdate", mock.AnythingOfType("*model.Agent")).Return(nil)
mockManager := mocks_manager.NewManager(t)
server.Config.Services.Manager = mockManager
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("store", mockStore)
c.Params = gin.Params{{Key: "agent_id", Value: "1"}}
c.Request, _ = http.NewRequest(http.MethodPatch, "/", strings.NewReader(`{"name":"updated-agent"}`))
c.Request.Header.Set("Content-Type", "application/json")
PatchAgent(c)
c.Writer.WriteHeaderNow()
mockStore.AssertCalled(t, "AgentFind", int64(1))
mockStore.AssertCalled(t, "AgentUpdate", mock.AnythingOfType("*model.Agent"))
assert.Equal(t, http.StatusOK, w.Code)
var response model.Agent
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "updated-agent", response.Name)
})
}
func TestPostAgent(t *testing.T) {
gin.SetMode(gin.TestMode)
t.Run("should create agent", func(t *testing.T) {
newAgent := &model.Agent{
Name: "new-agent",
NoSchedule: false,
}
mockStore := store_mocks.NewStore(t)
mockStore.On("AgentCreate", mock.AnythingOfType("*model.Agent")).Return(nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("store", mockStore)
c.Set("user", &model.User{ID: 1})
c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"name":"new-agent"}`))
c.Request.Header.Set("Content-Type", "application/json")
PostAgent(c)
c.Writer.WriteHeaderNow()
mockStore.AssertCalled(t, "AgentCreate", mock.AnythingOfType("*model.Agent"))
assert.Equal(t, http.StatusOK, w.Code)
var response model.Agent
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, newAgent.Name, response.Name)
assert.NotEmpty(t, response.Token)
})
}
func TestDeleteAgent(t *testing.T) {
gin.SetMode(gin.TestMode)
t.Run("should delete agent", func(t *testing.T) {
mockStore := store_mocks.NewStore(t)
mockStore.On("AgentFind", int64(1)).Return(fakeAgent, nil)
mockStore.On("AgentDelete", mock.AnythingOfType("*model.Agent")).Return(nil)
mockManager := mocks_manager.NewManager(t)
server.Config.Services.Manager = mockManager
mockQueue := queue_mocks.NewQueue(t)
mockQueue.On("Info", mock.Anything).Return(queue.InfoT{})
mockQueue.On("KickAgentWorkers", int64(1)).Return()
server.Config.Services.Queue = mockQueue
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("store", mockStore)
c.Params = gin.Params{{Key: "agent_id", Value: "1"}}
DeleteAgent(c)
c.Writer.WriteHeaderNow()
mockStore.AssertCalled(t, "AgentFind", int64(1))
mockStore.AssertCalled(t, "AgentDelete", mock.AnythingOfType("*model.Agent"))
mockQueue.AssertCalled(t, "KickAgentWorkers", int64(1))
assert.Equal(t, http.StatusNoContent, w.Code)
})
t.Run("should not delete agent with running tasks", func(t *testing.T) {
mockStore := store_mocks.NewStore(t)
mockStore.On("AgentFind", int64(1)).Return(fakeAgent, nil)
mockManager := mocks_manager.NewManager(t)
server.Config.Services.Manager = mockManager
mockQueue := queue_mocks.NewQueue(t)
mockQueue.On("Info", mock.Anything).Return(queue.InfoT{
Running: []*model.Task{{AgentID: 1}},
})
server.Config.Services.Queue = mockQueue
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("store", mockStore)
c.Params = gin.Params{{Key: "agent_id", Value: "1"}}
DeleteAgent(c)
c.Writer.WriteHeaderNow()
mockStore.AssertCalled(t, "AgentFind", int64(1))
mockStore.AssertNotCalled(t, "AgentDelete", mock.Anything)
assert.Equal(t, http.StatusConflict, w.Code)
})
}
func TestPostOrgAgent(t *testing.T) {
gin.SetMode(gin.TestMode)
t.Run("create org agent should succeed", func(t *testing.T) {
mockStore := store_mocks.NewStore(t)
mockStore.On("AgentCreate", mock.AnythingOfType("*model.Agent")).Return(nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("store", mockStore)
// Set up a non-admin user
c.Set("user", &model.User{
ID: 1,
Admin: false,
})
c.Params = gin.Params{{Key: "org_id", Value: "1"}}
c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"name":"new-agent"}`))
c.Request.Header.Set("Content-Type", "application/json")
PostOrgAgent(c)
c.Writer.WriteHeaderNow()
assert.Equal(t, http.StatusOK, w.Code)
// Ensure an agent was created
mockStore.AssertCalled(t, "AgentCreate", mock.AnythingOfType("*model.Agent"))
})
}