mirror of
https://github.com/portainer/portainer.git
synced 2026-06-23 06:40:13 +00:00
fcdd6b4510
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
134 lines
4.2 KiB
Go
134 lines
4.2 KiB
Go
package stackbuilders
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
portainer "github.com/portainer/portainer/api"
|
|
"github.com/portainer/portainer/api/datastore"
|
|
gittypes "github.com/portainer/portainer/api/git/types"
|
|
"github.com/portainer/portainer/api/gitops/workflows"
|
|
"github.com/portainer/portainer/api/internal/testhelpers"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// stubFileService satisfies portainer.FileService for git builder tests.
|
|
type stubFileService struct {
|
|
portainer.FileService
|
|
}
|
|
|
|
func (s *stubFileService) GetStackProjectPath(stackIdentifier string) string {
|
|
return "/data/compose/" + stackIdentifier
|
|
}
|
|
|
|
func newGitMethodBuilder(t *testing.T, commitHash string) *GitMethodStackBuilder {
|
|
t.Helper()
|
|
_, store := datastore.MustNewTestStore(t, false, false)
|
|
require.NoError(t, store.User().Create(&portainer.User{ID: 1, Username: "testuser"}))
|
|
return &GitMethodStackBuilder{
|
|
StackBuilder: StackBuilder{
|
|
stack: &portainer.Stack{},
|
|
fileService: &stubFileService{},
|
|
dataStore: store,
|
|
},
|
|
gitService: testhelpers.NewGitService(nil, commitHash),
|
|
}
|
|
}
|
|
|
|
func TestGitMethodStackBuilder_WithSourceID_ReferencesExistingSource(t *testing.T) {
|
|
t.Parallel()
|
|
builder := newGitMethodBuilder(t, "abc123")
|
|
builder.stack.ID = 1
|
|
|
|
src := &portainer.Source{
|
|
Name: "my-repo",
|
|
Type: portainer.SourceTypeGit,
|
|
Git: &gittypes.RepoConfig{
|
|
URL: "https://github.com/org/private-repo",
|
|
Authentication: &gittypes.GitAuthentication{
|
|
Username: "git-user",
|
|
Password: "git-token",
|
|
},
|
|
},
|
|
}
|
|
require.NoError(t, builder.dataStore.Source().Create(src))
|
|
|
|
payload := &StackPayload{
|
|
RepositoryConfigPayload: RepositoryConfigPayload{
|
|
SourceID: src.ID,
|
|
ReferenceName: "refs/heads/main",
|
|
},
|
|
}
|
|
|
|
err := builder.prepare(context.Background(), payload, portainer.UserID(1))
|
|
require.NoError(t, err)
|
|
|
|
// Workflow Artifact must reference the existing Source — not a new one.
|
|
referencedSourceID := builderWorkflowSourceID(t, builder)
|
|
assert.Equal(t, src.ID, referencedSourceID)
|
|
|
|
// Only one Source exists — no duplicate was created.
|
|
allSources, err := builder.dataStore.Source().ReadAll()
|
|
require.NoError(t, err)
|
|
assert.Len(t, allSources, 1)
|
|
|
|
// The merged git config picks up the Source URL/auth.
|
|
readSrc, artifact, err := workflows.GitSourceAndArtifactForStack(builder.dataStore, builder.stack.WorkflowID, builder.stack.ID)
|
|
require.NoError(t, err)
|
|
merged := workflows.MergeSourceAndFile(readSrc, artifact)
|
|
assert.Equal(t, "https://github.com/org/private-repo", merged.URL)
|
|
assert.Equal(t, "refs/heads/main", merged.ReferenceName)
|
|
require.NotNil(t, merged.Authentication)
|
|
assert.Equal(t, "git-user", merged.Authentication.Username)
|
|
}
|
|
|
|
func TestGitMethodStackBuilder_WithMissingSourceID_ReturnsError(t *testing.T) {
|
|
t.Parallel()
|
|
builder := newGitMethodBuilder(t, "abc123")
|
|
builder.stack.ID = 2
|
|
|
|
payload := &StackPayload{
|
|
RepositoryConfigPayload: RepositoryConfigPayload{
|
|
SourceID: portainer.SourceID(999), // does not exist
|
|
},
|
|
}
|
|
|
|
err := builder.prepare(context.Background(), payload, portainer.UserID(1))
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestGitMethodStackBuilder_WithoutSourceID_InlinePathStillWorks(t *testing.T) {
|
|
t.Parallel()
|
|
builder := newGitMethodBuilder(t, "feedcafe")
|
|
builder.stack.ID = 4
|
|
|
|
payload := &StackPayload{
|
|
RepositoryConfigPayload: RepositoryConfigPayload{
|
|
URL: "https://github.com/org/public-repo",
|
|
ReferenceName: "refs/heads/main",
|
|
},
|
|
}
|
|
|
|
err := builder.prepare(context.Background(), payload, portainer.UserID(1))
|
|
require.NoError(t, err)
|
|
|
|
// A Source was created via the inline path.
|
|
allSources, err := builder.dataStore.Source().ReadAll()
|
|
require.NoError(t, err)
|
|
assert.Len(t, allSources, 1)
|
|
assert.Equal(t, "https://github.com/org/public-repo", allSources[0].Git.URL)
|
|
}
|
|
|
|
// builderWorkflowSourceID returns the first SourceID referenced by the Workflow Artifact for this stack.
|
|
func builderWorkflowSourceID(t *testing.T, builder *GitMethodStackBuilder) portainer.SourceID {
|
|
t.Helper()
|
|
require.NotZero(t, builder.stack.WorkflowID)
|
|
|
|
wf, err := builder.dataStore.Workflow().Read(builder.stack.WorkflowID)
|
|
require.NoError(t, err)
|
|
require.Len(t, wf.Artifacts, 1)
|
|
require.Len(t, wf.Artifacts[0].Files, 1)
|
|
return wf.Artifacts[0].Files[0].SourceID
|
|
}
|