Files
portainer/api/gitops/workflows/fetch_test.go
2026-06-08 15:01:55 -03:00

283 lines
8.7 KiB
Go

package workflows
import (
"strconv"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/datastore"
gittypes "github.com/portainer/portainer/api/git/types"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/set"
"github.com/stretchr/testify/require"
)
func adminContext() *security.RestrictedRequestContext {
return &security.RestrictedRequestContext{IsAdmin: true, UserID: 1}
}
func mustCreateGitWorkflow(t *testing.T, tx dataservices.DataStoreTx, stack *portainer.Stack) {
t.Helper()
cfg := stack.GitConfig
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: cfg}
require.NoError(t, tx.Source().Create(src))
wf := &portainer.Workflow{Artifacts: []portainer.Artifact{{
StackID: stack.ID,
Files: []portainer.ArtifactFile{{SourceID: src.ID}},
}}}
require.NoError(t, tx.Workflow().Create(wf))
stack.WorkflowID = wf.ID
stack.GitConfig = nil
require.NoError(t, tx.Stack().Create(stack))
}
func TestAddSourceStats_NoOp(t *testing.T) {
t.Parallel()
result := make(map[portainer.SourceID]SourceStats)
addSourceStats(result, nil, nil, 0)
require.Empty(t, result)
}
func TestAddSourceStats_AccumulatesWorkflowCount(t *testing.T) {
t.Parallel()
result := make(map[portainer.SourceID]SourceStats)
addSourceStats(result, []portainer.SourceID{1}, nil, 0)
addSourceStats(result, []portainer.SourceID{1}, nil, 0)
require.Equal(t, 2, result[1].WorkflowCount)
}
func TestAddSourceStats_CollectsUniqueEndpointIDs(t *testing.T) {
t.Parallel()
result := make(map[portainer.SourceID]SourceStats)
addSourceStats(result, []portainer.SourceID{1}, []portainer.EndpointID{10, 20}, 0)
addSourceStats(result, []portainer.SourceID{1}, []portainer.EndpointID{20, 30}, 0)
require.Len(t, result[1].EndpointIDs, 3)
require.True(t, result[1].EndpointIDs[10])
require.True(t, result[1].EndpointIDs[20])
require.True(t, result[1].EndpointIDs[30])
}
func TestAddSourceStats_MaxLastSync(t *testing.T) {
t.Parallel()
result := make(map[portainer.SourceID]SourceStats)
addSourceStats(result, []portainer.SourceID{1}, nil, 100)
addSourceStats(result, []portainer.SourceID{1}, nil, 500)
addSourceStats(result, []portainer.SourceID{1}, nil, 200)
require.Equal(t, int64(500), result[1].LastSync)
}
func TestAddSourceStats_MultipleSourceIDs(t *testing.T) {
t.Parallel()
result := make(map[portainer.SourceID]SourceStats)
addSourceStats(result, []portainer.SourceID{1, 2}, []portainer.EndpointID{10}, 100)
require.Equal(t, 1, result[1].WorkflowCount)
require.Equal(t, 1, result[2].WorkflowCount)
require.True(t, result[1].EndpointIDs[10])
require.True(t, result[2].EndpointIDs[10])
}
func TestFetchWorkflows_ReturnsOnlyGitopsStacks(t *testing.T) {
t.Parallel()
_, store := datastore.MustNewTestStore(t, false, true)
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
mustCreateGitWorkflow(t, tx, &portainer.Stack{
ID: 1,
Name: "gitops-stack",
GitConfig: &gittypes.RepoConfig{URL: "https://github.com/x/repo"},
})
require.NoError(t, tx.Stack().Create(&portainer.Stack{ID: 2, Name: "plain-stack"}))
return tx.User().Create(&portainer.User{ID: 1, Role: portainer.AdministratorRole})
}))
var items []Workflow
require.NoError(t, store.ViewTx(func(tx dataservices.DataStoreTx) error {
var err error
items, err = FetchWorkflows(t.Context(), tx, nil, nil, adminContext(), nil)
return err
}))
require.Len(t, items, 1)
require.Equal(t, "gitops-stack", items[0].Name)
}
func TestFetchWorkflows_FiltersByEndpointID(t *testing.T) {
t.Parallel()
_, store := datastore.MustNewTestStore(t, false, true)
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
for i := 1; i <= 3; i++ {
mustCreateGitWorkflow(t, tx, &portainer.Stack{
ID: portainer.StackID(i),
Name: "stack-" + strconv.Itoa(i),
EndpointID: portainer.EndpointID(i),
GitConfig: &gittypes.RepoConfig{URL: "https://github.com/x/" + strconv.Itoa(i)},
})
}
return tx.User().Create(&portainer.User{ID: 1, Role: portainer.AdministratorRole})
}))
var items []Workflow
require.NoError(t, store.ViewTx(func(tx dataservices.DataStoreTx) error {
var err error
items, err = FetchWorkflows(t.Context(), tx, nil, nil, adminContext(), set.ToSet([]portainer.EndpointID{1, 2}))
return err
}))
require.Len(t, items, 2)
names := []string{items[0].Name, items[1].Name}
require.Contains(t, names, "stack-1")
require.Contains(t, names, "stack-2")
}
func TestFetchWorkflows_EmptyWhenNoGitopsStacks(t *testing.T) {
t.Parallel()
_, store := datastore.MustNewTestStore(t, false, true)
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
require.NoError(t, tx.Stack().Create(&portainer.Stack{ID: 1, Name: "plain-1"}))
require.NoError(t, tx.Stack().Create(&portainer.Stack{ID: 2, Name: "plain-2"}))
return tx.User().Create(&portainer.User{ID: 1, Role: portainer.AdministratorRole})
}))
var items []Workflow
require.NoError(t, store.ViewTx(func(tx dataservices.DataStoreTx) error {
var err error
items, err = FetchWorkflows(t.Context(), tx, nil, nil, adminContext(), nil)
return err
}))
require.Empty(t, items)
}
func TestFetchWorkflows_NilEndpointSetReturnsAll(t *testing.T) {
t.Parallel()
_, store := datastore.MustNewTestStore(t, false, true)
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
for i := 1; i <= 3; i++ {
mustCreateGitWorkflow(t, tx, &portainer.Stack{
ID: portainer.StackID(i),
Name: "stack-" + strconv.Itoa(i),
EndpointID: portainer.EndpointID(i),
GitConfig: &gittypes.RepoConfig{URL: "https://github.com/x/" + strconv.Itoa(i)},
})
}
return tx.User().Create(&portainer.User{ID: 1, Role: portainer.AdministratorRole})
}))
var items []Workflow
require.NoError(t, store.ViewTx(func(tx dataservices.DataStoreTx) error {
var err error
items, err = FetchWorkflows(t.Context(), tx, nil, nil, adminContext(), nil)
return err
}))
require.Len(t, items, 3)
}
func TestFetchSourceStats_ReturnsAllSources(t *testing.T) {
t.Parallel()
_, store := datastore.MustNewTestStore(t, false, true)
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
require.NoError(t, tx.Source().Create(&portainer.Source{Name: "source-1", Type: portainer.SourceTypeGit}))
require.NoError(t, tx.Source().Create(&portainer.Source{Name: "source-2", Type: portainer.SourceTypeGit}))
return tx.User().Create(&portainer.User{ID: 1, Role: portainer.AdministratorRole})
}))
var sources []portainer.Source
require.NoError(t, store.ViewTx(func(tx dataservices.DataStoreTx) error {
var err error
sources, _, err = FetchSourceStats(tx, nil, adminContext())
return err
}))
require.Len(t, sources, 2)
}
func TestFetchSourceStats_TracksWorkflowCountAndEndpoints(t *testing.T) {
t.Parallel()
_, store := datastore.MustNewTestStore(t, false, true)
var srcID portainer.SourceID
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
src := &portainer.Source{Name: "shared", Type: portainer.SourceTypeGit}
require.NoError(t, tx.Source().Create(src))
srcID = src.ID
for i := 1; i <= 2; i++ {
wf := &portainer.Workflow{Artifacts: []portainer.Artifact{{Files: []portainer.ArtifactFile{{SourceID: srcID}}}}}
require.NoError(t, tx.Workflow().Create(wf))
require.NoError(t, tx.Stack().Create(&portainer.Stack{
ID: portainer.StackID(i),
Name: "stack-" + strconv.Itoa(i),
EndpointID: portainer.EndpointID(i),
WorkflowID: wf.ID,
}))
}
return tx.User().Create(&portainer.User{ID: 1, Role: portainer.AdministratorRole})
}))
var stats map[portainer.SourceID]SourceStats
require.NoError(t, store.ViewTx(func(tx dataservices.DataStoreTx) error {
var err error
_, stats, err = FetchSourceStats(tx, nil, adminContext())
return err
}))
st := stats[srcID]
require.Equal(t, 2, st.WorkflowCount)
require.Len(t, st.EndpointIDs, 2)
}
func TestFetchSourceStats_UnusedSourceHasZeroStats(t *testing.T) {
t.Parallel()
_, store := datastore.MustNewTestStore(t, false, true)
var unusedID portainer.SourceID
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
src := &portainer.Source{Name: "unused", Type: portainer.SourceTypeGit}
require.NoError(t, tx.Source().Create(src))
unusedID = src.ID
return tx.User().Create(&portainer.User{ID: 1, Role: portainer.AdministratorRole})
}))
var stats map[portainer.SourceID]SourceStats
require.NoError(t, store.ViewTx(func(tx dataservices.DataStoreTx) error {
var err error
_, stats, err = FetchSourceStats(tx, nil, adminContext())
return err
}))
st := stats[unusedID]
require.Zero(t, st.WorkflowCount)
require.Empty(t, st.EndpointIDs)
}