garm/runner/garm_tools_test.go
Gabriel Adrian Samfira 090fabda9d Fix tests
Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2026-02-08 00:27:47 +02:00

466 lines
14 KiB
Go

// Copyright 2025 Cloudbase Solutions SRL
//
// 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.
//go:build testing
// +build testing
package runner
import (
"bytes"
"context"
"fmt"
"testing"
"github.com/stretchr/testify/suite"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
commonParams "github.com/cloudbase/garm-provider-common/params"
"github.com/cloudbase/garm/database"
dbCommon "github.com/cloudbase/garm/database/common"
garmTesting "github.com/cloudbase/garm/internal/testing"
"github.com/cloudbase/garm/params"
)
var (
windowsAMD64ToolsTags = []string{
garmAgentFileTag,
garmAgentOSTypeWindowsTag,
garmAgentOSArchAMD64Tag,
}
windowsARM64ToolsTags = []string{
garmAgentFileTag,
garmAgentOSTypeWindowsTag,
garmAgentOSArchARM64Tag,
}
linuxARM64ToolsTags = []string{
garmAgentFileTag,
garmAgentOSTypeLinuxTag,
garmAgentOSArchARM64Tag,
}
)
type GARMToolsTestSuite struct {
suite.Suite
AdminContext context.Context
UnauthorizedContext context.Context
Store dbCommon.Store
Runner *Runner
}
func (s *GARMToolsTestSuite) SetupTest() {
dbCfg := garmTesting.GetTestSqliteDBConfig(s.T())
db, err := database.NewDatabase(context.Background(), dbCfg)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
s.AdminContext = adminCtx
s.UnauthorizedContext = context.Background()
s.Store = db
runner := &Runner{
ctx: adminCtx,
store: db,
}
s.Runner = runner
}
func (s *GARMToolsTestSuite) TestCreateGARMToolUnauthorized() {
param := params.CreateGARMToolParams{
Name: "garm-agent-linux-amd64",
Description: "GARM agent for Linux AMD64",
Size: 1024,
OSType: "linux",
OSArch: "amd64",
Version: "v1.0.0",
}
reader := bytes.NewReader([]byte("test binary content"))
_, err := s.Runner.CreateGARMTool(s.UnauthorizedContext, param, reader)
s.Require().Error(err)
s.Equal(runnerErrors.ErrUnauthorized, err)
}
func (s *GARMToolsTestSuite) TestCreateGARMToolMissingVersion() {
param := params.CreateGARMToolParams{
Name: "garm-agent-linux-amd64",
Description: "GARM agent for Linux AMD64",
Size: 1024,
OSType: "linux",
OSArch: "amd64",
Version: "",
}
reader := bytes.NewReader([]byte("test binary content"))
_, err := s.Runner.CreateGARMTool(s.AdminContext, param, reader)
s.Require().Error(err)
s.Contains(err.Error(), "version is required")
}
func (s *GARMToolsTestSuite) TestCreateGARMToolInvalidOSType() {
param := params.CreateGARMToolParams{
Name: "garm-agent-invalid-amd64",
Description: "Invalid OS type",
Size: 1024,
OSType: "invalid",
OSArch: "amd64",
Version: "v1.0.0",
}
reader := bytes.NewReader([]byte("test binary content"))
_, err := s.Runner.CreateGARMTool(s.AdminContext, param, reader)
s.Require().Error(err)
s.Contains(err.Error(), "invalid os_type")
}
func (s *GARMToolsTestSuite) TestCreateGARMToolInvalidOSArch() {
param := params.CreateGARMToolParams{
Name: "garm-agent-linux-invalid",
Description: "Invalid arch",
Size: 1024,
OSType: "linux",
OSArch: "invalid",
Version: "v1.0.0",
}
reader := bytes.NewReader([]byte("test binary content"))
_, err := s.Runner.CreateGARMTool(s.AdminContext, param, reader)
s.Require().Error(err)
s.Contains(err.Error(), "invalid os_arch")
}
func (s *GARMToolsTestSuite) TestCreateGARMToolSuccess() {
content := []byte("test binary content for linux amd64")
param := params.CreateGARMToolParams{
Name: "garm-agent-linux-amd64",
Description: "GARM agent for Linux AMD64",
Size: int64(len(content)),
OSType: commonParams.Linux,
OSArch: commonParams.Amd64,
Version: "v1.0.0",
}
reader := bytes.NewReader(content)
tool, err := s.Runner.CreateGARMTool(s.AdminContext, param, reader)
s.Require().NoError(err)
s.Equal(param.Name, tool.Name)
s.Equal(param.Description, tool.Description)
s.Equal(int64(len(content)), tool.Size)
// Verify tags (should include origin=manual)
expectedTags := []string{
"category=garm-agent",
"os_type=linux",
"os_arch=amd64",
"version=v1.0.0",
"origin=manual",
}
s.ElementsMatch(expectedTags, tool.Tags)
}
func (s *GARMToolsTestSuite) TestCreateGARMToolCleansUpOldVersions() {
// Upload version 1.0.0
param1 := params.CreateGARMToolParams{
Name: "garm-agent-linux-arm64",
Description: "GARM agent for Linux ARM64 v1.0.0",
Size: 1024,
OSType: commonParams.Linux,
OSArch: commonParams.Arm64,
Version: "v1.0.0",
}
reader1 := bytes.NewReader([]byte("version 1.0.0 binary"))
tool1, err := s.Runner.CreateGARMTool(s.AdminContext, param1, reader1)
s.Require().NoError(err)
// Upload version 1.1.0
param2 := params.CreateGARMToolParams{
Name: "garm-agent-linux-arm64",
Description: "GARM agent for Linux ARM64 v1.1.0",
Size: 2048,
OSType: commonParams.Linux,
OSArch: commonParams.Arm64,
Version: "v1.1.0",
}
reader2 := bytes.NewReader([]byte("version 1.1.0 binary"))
tool2, err := s.Runner.CreateGARMTool(s.AdminContext, param2, reader2)
s.Require().NoError(err)
// Verify v1.0.0 was deleted
_, err = s.Store.GetFileObject(s.AdminContext, tool1.ID)
s.Require().Error(err)
s.Contains(err.Error(), "could not find file object")
// Verify v1.1.0 still exists
existing, err := s.Store.GetFileObject(s.AdminContext, tool2.ID)
s.Require().NoError(err)
s.Equal(tool2.ID, existing.ID)
// Search for all linux/arm64 tools - should only find v1.1.0
results, err := s.Store.SearchFileObjectByTags(s.AdminContext, linuxARM64ToolsTags, 1, 10)
s.Require().NoError(err)
s.Equal(uint64(1), results.TotalCount)
s.Len(results.Results, 1)
s.Equal(tool2.ID, results.Results[0].ID)
}
func (s *GARMToolsTestSuite) TestCreateGARMToolPaginationCleanup() {
// Upload 150 old versions for windows/amd64
for i := 1; i <= 150; i++ {
param := params.CreateGARMToolParams{
Name: fmt.Sprintf("garm-agent-windows-amd64-v%d", i),
Description: fmt.Sprintf("Version %d", i),
Size: int64(i * 100),
OSType: commonParams.Windows,
OSArch: commonParams.Amd64,
Version: fmt.Sprintf("v0.0.%d", i),
}
reader := bytes.NewReader([]byte(fmt.Sprintf("version %d binary", i)))
_, err := s.Runner.CreateGARMTool(s.AdminContext, param, reader)
s.Require().NoError(err)
}
// Upload the latest version
latestParam := params.CreateGARMToolParams{
Name: "garm-agent-windows-amd64",
Description: "Latest version",
Size: 99999,
OSType: commonParams.Windows,
OSArch: commonParams.Amd64,
Version: "v2.0.0",
}
reader := bytes.NewReader([]byte("latest version binary"))
latestTool, err := s.Runner.CreateGARMTool(s.AdminContext, latestParam, reader)
s.Require().NoError(err)
// Verify only the latest version exists
results, err := s.Store.SearchFileObjectByTags(s.AdminContext, windowsAMD64ToolsTags, 1, 200)
s.Require().NoError(err)
s.Equal(uint64(1), results.TotalCount)
s.Len(results.Results, 1)
s.Equal(latestTool.ID, results.Results[0].ID)
s.Equal("v2.0.0", getVersionFromTags(results.Results[0].Tags))
}
func (s *GARMToolsTestSuite) TestCreateGARMToolDoesNotAffectOtherPlatforms() {
// Upload linux/amd64 v1.0.0
param1 := params.CreateGARMToolParams{
Name: "garm-agent-linux-amd64",
Description: "Linux AMD64",
Size: 1024,
OSType: commonParams.Linux,
OSArch: commonParams.Amd64,
Version: "v1.0.0",
}
reader1 := bytes.NewReader([]byte("linux amd64 binary"))
tool1, err := s.Runner.CreateGARMTool(s.AdminContext, param1, reader1)
s.Require().NoError(err)
// Upload windows/amd64 v1.0.0
param2 := params.CreateGARMToolParams{
Name: "garm-agent-windows-amd64",
Description: "Windows AMD64",
Size: 2048,
OSType: commonParams.Windows,
OSArch: commonParams.Amd64,
Version: "v1.0.0",
}
reader2 := bytes.NewReader([]byte("windows amd64 binary"))
tool2, err := s.Runner.CreateGARMTool(s.AdminContext, param2, reader2)
s.Require().NoError(err)
// Verify both still exist
_, err = s.Store.GetFileObject(s.AdminContext, tool1.ID)
s.Require().NoError(err)
_, err = s.Store.GetFileObject(s.AdminContext, tool2.ID)
s.Require().NoError(err)
}
func (s *GARMToolsTestSuite) TestDeleteGarmToolUnauthorized() {
err := s.Runner.DeleteGarmTool(s.UnauthorizedContext, "linux", "amd64")
s.Require().Error(err)
s.Equal(runnerErrors.ErrUnauthorized, err)
}
func (s *GARMToolsTestSuite) TestDeleteGarmToolInvalidOSType() {
err := s.Runner.DeleteGarmTool(s.AdminContext, "invalid", "amd64")
s.Require().Error(err)
s.Contains(err.Error(), "invalid os_type")
}
func (s *GARMToolsTestSuite) TestDeleteGarmToolInvalidOSArch() {
err := s.Runner.DeleteGarmTool(s.AdminContext, "linux", "invalid")
s.Require().Error(err)
s.Contains(err.Error(), "invalid os_arch")
}
func (s *GARMToolsTestSuite) TestDeleteGarmToolNotFound() {
err := s.Runner.DeleteGarmTool(s.AdminContext, "linux", "amd64")
s.Require().Error(err)
s.Contains(err.Error(), "no garm-agent tools found")
}
func (s *GARMToolsTestSuite) TestDeleteGarmToolSuccess() {
// Create a tool
param := params.CreateGARMToolParams{
Name: "garm-agent-linux-amd64",
Description: "GARM agent for Linux AMD64",
Size: 1024,
OSType: commonParams.Linux,
OSArch: commonParams.Amd64,
Version: "v1.0.0",
}
reader := bytes.NewReader([]byte("test binary"))
tool, err := s.Runner.CreateGARMTool(s.AdminContext, param, reader)
s.Require().NoError(err)
// Delete it
err = s.Runner.DeleteGarmTool(s.AdminContext, "linux", "amd64")
s.Require().NoError(err)
// Verify it's gone
_, err = s.Store.GetFileObject(s.AdminContext, tool.ID)
s.Require().Error(err)
s.Contains(err.Error(), "could not find file object")
}
func (s *GARMToolsTestSuite) TestDeleteGarmToolDeletesAllVersions() {
// Create multiple versions using windows/arm64
for i := 1; i <= 5; i++ {
reader := bytes.NewReader([]byte(fmt.Sprintf("version %d", i)))
// CreateGARMTool only keeps the latest, so we need to use the store directly
// to create multiple versions
tags := windowsARM64ToolsTags
tags = append(tags, fmt.Sprintf("version=v1.%d.0", i))
createParam := params.CreateFileObjectParams{
Name: fmt.Sprintf("garm-agent-windows-arm64-v%d", i),
Description: fmt.Sprintf("Version %d", i),
Size: int64(i * 100),
Tags: tags,
}
_, err := s.Store.CreateFileObject(s.AdminContext, createParam, reader)
s.Require().NoError(err)
}
// Verify we have 5 versions
results, err := s.Store.SearchFileObjectByTags(s.AdminContext, windowsARM64ToolsTags, 1, 10)
s.Require().NoError(err)
s.Equal(uint64(5), results.TotalCount)
// Delete all
err = s.Runner.DeleteGarmTool(s.AdminContext, "windows", "arm64")
s.Require().NoError(err)
// Verify all are gone
results, err = s.Store.SearchFileObjectByTags(s.AdminContext, windowsARM64ToolsTags, 1, 10)
s.Require().NoError(err)
s.Equal(uint64(0), results.TotalCount)
}
func (s *GARMToolsTestSuite) TestListAllGARMToolsUnauthorized() {
_, err := s.Runner.ListAllGARMTools(s.UnauthorizedContext)
s.Require().Error(err)
s.Equal(runnerErrors.ErrUnauthorized, err)
}
func (s *GARMToolsTestSuite) TestListAllGARMToolsEmpty() {
tools, err := s.Runner.ListAllGARMTools(s.AdminContext)
s.Require().NoError(err)
s.Empty(tools)
}
func (s *GARMToolsTestSuite) TestListAllGARMToolsSinglePlatform() {
// Create one tool
param := params.CreateGARMToolParams{
Name: "garm-agent-linux-amd64",
Description: "GARM agent for Linux AMD64",
Size: 1024,
OSType: commonParams.Linux,
OSArch: commonParams.Amd64,
Version: "v1.0.0",
}
reader := bytes.NewReader([]byte("test binary"))
_, err := s.Runner.CreateGARMTool(s.AdminContext, param, reader)
s.Require().NoError(err)
tools, err := s.Runner.ListAllGARMTools(s.AdminContext)
s.Require().NoError(err)
s.Len(tools, 1)
s.Equal("linux", string(tools[0].OSType))
s.Equal("amd64", string(tools[0].OSArch))
}
func (s *GARMToolsTestSuite) TestListAllGARMToolsMultiplePlatforms() {
// Create tools for all supported platforms
platforms := []struct {
osType commonParams.OSType
osArch commonParams.OSArch
}{
{commonParams.Linux, commonParams.Amd64},
{commonParams.Linux, commonParams.Arm64},
{commonParams.Windows, commonParams.Amd64},
{commonParams.Windows, commonParams.Arm64},
}
for _, p := range platforms {
param := params.CreateGARMToolParams{
Name: fmt.Sprintf("garm-agent-%s-%s", p.osType, p.osArch),
Description: fmt.Sprintf("GARM agent for %s %s", p.osType, p.osArch),
Size: 1024,
OSType: p.osType,
OSArch: p.osArch,
Version: "v1.0.0",
}
reader := bytes.NewReader([]byte(fmt.Sprintf("%s %s binary", p.osType, p.osArch)))
_, err := s.Runner.CreateGARMTool(s.AdminContext, param, reader)
s.Require().NoError(err)
}
tools, err := s.Runner.ListAllGARMTools(s.AdminContext)
s.Require().NoError(err)
s.Len(tools, 4)
// Verify we have all platforms
foundPlatforms := make(map[string]bool)
for _, tool := range tools {
key := fmt.Sprintf("%s-%s", tool.OSType, tool.OSArch)
foundPlatforms[key] = true
}
s.True(foundPlatforms["linux-amd64"])
s.True(foundPlatforms["linux-arm64"])
s.True(foundPlatforms["windows-amd64"])
s.True(foundPlatforms["windows-arm64"])
}
// Helper function to extract version from tags
func getVersionFromTags(tags []string) string {
for _, tag := range tags {
if len(tag) > 8 && tag[:8] == "version=" {
return tag[8:]
}
}
return ""
}
func TestGARMToolsTestSuite(t *testing.T) {
suite.Run(t, new(GARMToolsTestSuite))
}