Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions pkg/bundler/bundler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package bundler

import (
"fmt"

"github.com/windsorcli/cli/pkg/di"
"github.com/windsorcli/cli/pkg/shell"
)

// The Bundler provides an interface for adding content to artifacts during the bundling process.
// It provides a unified approach for different content types (templates, kustomize, terraform)
// to contribute their files to the artifact build directory. The Bundler serves as a composable
// component that can validate and bundle specific types of content into distributable artifacts.

// =============================================================================
// Interfaces
// =============================================================================

// Bundler defines the interface for content bundling operations
type Bundler interface {
Initialize(injector di.Injector) error
Bundle(artifact Artifact) error
}

// =============================================================================
// Types
// =============================================================================

// BaseBundler provides common functionality for bundler implementations
type BaseBundler struct {
injector di.Injector
shims *Shims
shell shell.Shell
}

// =============================================================================
// Constructor
// =============================================================================

// NewBaseBundler creates a new BaseBundler instance
func NewBaseBundler() *BaseBundler {
return &BaseBundler{
shims: NewShims(),
}
}

// =============================================================================
// Public Methods
// =============================================================================

// Initialize initializes the BaseBundler with dependency injection
func (b *BaseBundler) Initialize(injector di.Injector) error {
b.injector = injector

shell, ok := injector.Resolve("shell").(shell.Shell)
if !ok {
return fmt.Errorf("failed to resolve shell from injector")
}
b.shell = shell

return nil
}

// Bundle provides a default implementation that can be overridden by concrete bundlers
func (b *BaseBundler) Bundle(artifact Artifact) error {
return nil
}

// Ensure BaseBundler implements Bundler interface
var _ Bundler = (*BaseBundler)(nil)
147 changes: 147 additions & 0 deletions pkg/bundler/bundler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package bundler

import (
"os"
"path/filepath"
"testing"

"github.com/windsorcli/cli/pkg/di"
"github.com/windsorcli/cli/pkg/shell"
)

// =============================================================================
// Test Setup
// =============================================================================

type BundlerMocks struct {
Injector di.Injector
Shell *shell.MockShell
Shims *Shims
Artifact *MockArtifact
}

func setupBundlerMocks(t *testing.T) *BundlerMocks {
t.Helper()

// Create temp directory
tmpDir := t.TempDir()

// Create injector
injector := di.NewInjector()

// Set up shell
mockShell := shell.NewMockShell(injector)
mockShell.GetProjectRootFunc = func() (string, error) {
return tmpDir, nil
}
injector.Register("shell", mockShell)

// Create test-friendly shims
shims := NewShims()
shims.Stat = func(name string) (os.FileInfo, error) {
return &mockFileInfo{name: name, isDir: true}, nil
}
shims.Walk = func(root string, fn filepath.WalkFunc) error {
return nil
}
shims.FilepathRel = func(basepath, targpath string) (string, error) {
return "test.txt", nil
}
shims.ReadFile = func(filename string) ([]byte, error) {
return []byte("test content"), nil
}

// Create mock artifact
artifact := NewMockArtifact()

return &BundlerMocks{
Injector: injector,
Shell: mockShell,
Shims: shims,
Artifact: artifact,
}
}

// =============================================================================
// Test BaseBundler
// =============================================================================

func TestBaseBundler_NewBaseBundler(t *testing.T) {
t.Run("Success", func(t *testing.T) {
// Given no preconditions
// When creating a new base bundler
bundler := NewBaseBundler()

// Then it should not be nil
if bundler == nil {
t.Fatal("Expected non-nil bundler")
}
// And shims should be initialized
if bundler.shims == nil {
t.Error("Expected shims to be initialized")
}
// And other fields should be nil until Initialize
if bundler.shell != nil {
t.Error("Expected shell to be nil before Initialize")
}
})
}

func TestBaseBundler_Initialize(t *testing.T) {
t.Run("Success", func(t *testing.T) {
// Given a base bundler and mocks
mocks := setupBundlerMocks(t)
bundler := NewBaseBundler()

// When calling Initialize
err := bundler.Initialize(mocks.Injector)

// Then no error should be returned
if err != nil {
t.Errorf("Expected nil error, got %v", err)
}
// And shell should be injected
if bundler.shell == nil {
t.Error("Expected shell to be set after Initialize")
}
// And injector should be stored
if bundler.injector == nil {
t.Error("Expected injector to be stored")
}
})

t.Run("ErrorWhenShellNotFound", func(t *testing.T) {
// Given a bundler and injector without shell
bundler := NewBaseBundler()
injector := di.NewInjector()
injector.Register("shell", "not-a-shell")

// When calling Initialize
err := bundler.Initialize(injector)

// Then an error should be returned
if err == nil {
t.Error("Expected error when shell not found")
}
if err.Error() != "failed to resolve shell from injector" {
t.Errorf("Expected shell resolution error, got: %v", err)
}
})
}

func TestBaseBundler_Bundle(t *testing.T) {
t.Run("Success", func(t *testing.T) {
// Given a base bundler
mocks := setupBundlerMocks(t)
bundler := NewBaseBundler()
bundler.Initialize(mocks.Injector)

// When calling Bundle
err := bundler.Bundle(mocks.Artifact)

// Then no error should be returned (default implementation)
if err != nil {
t.Errorf("Expected nil error, got %v", err)
}
})
}
52 changes: 52 additions & 0 deletions pkg/bundler/mock_bundler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package bundler

import (
"github.com/windsorcli/cli/pkg/di"
)

// The MockBundler is a mock implementation of the Bundler interface for testing.
// It provides function fields that can be overridden to control behavior during tests.
// It serves as a test double for the Bundler interface in unit tests.
// It enables isolation and verification of component interactions with the bundler system.

// =============================================================================
// Types
// =============================================================================

// MockBundler is a mock implementation of the Bundler interface
type MockBundler struct {
InitializeFunc func(injector di.Injector) error
BundleFunc func(artifact Artifact) error
}

// =============================================================================
// Constructor
// =============================================================================

// NewMockBundler creates a new MockBundler instance
func NewMockBundler() *MockBundler {
return &MockBundler{}
}

// =============================================================================
// Public Methods
// =============================================================================

// Initialize calls the mock InitializeFunc if set, otherwise returns nil
func (m *MockBundler) Initialize(injector di.Injector) error {
if m.InitializeFunc != nil {
return m.InitializeFunc(injector)
}
return nil
}

// Bundle calls the mock BundleFunc if set, otherwise returns nil
func (m *MockBundler) Bundle(artifact Artifact) error {
if m.BundleFunc != nil {
return m.BundleFunc(artifact)
}
return nil
}

// Ensure MockBundler implements Bundler interface
var _ Bundler = (*MockBundler)(nil)
98 changes: 98 additions & 0 deletions pkg/bundler/mock_bundler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package bundler

import (
"testing"

"github.com/windsorcli/cli/pkg/di"
)

// =============================================================================
// Test Public Methods
// =============================================================================

func TestMockBundler_NewMockBundler(t *testing.T) {
t.Run("Success", func(t *testing.T) {
// Given no preconditions
// When creating a new mock bundler
mock := NewMockBundler()

// Then it should not be nil
if mock == nil {
t.Fatal("Expected non-nil mock bundler")
}
})
}

func TestMockBundler_Initialize(t *testing.T) {
t.Run("Success", func(t *testing.T) {
// Given a mock with a custom initialize function
mock := NewMockBundler()
called := false
mock.InitializeFunc = func(injector di.Injector) error {
called = true
return nil
}

// When calling Initialize
err := mock.Initialize(di.NewInjector())

// Then the mock function should be called
if !called {
t.Error("Expected InitializeFunc to be called")
}
if err != nil {
t.Errorf("Expected nil error, got %v", err)
}
})

t.Run("NotImplemented", func(t *testing.T) {
// Given a mock with no custom initialize function
mock := NewMockBundler()

// When calling Initialize
err := mock.Initialize(di.NewInjector())

// Then no error should be returned
if err != nil {
t.Errorf("Expected nil error, got %v", err)
}
})
}

func TestMockBundler_Bundle(t *testing.T) {
t.Run("Success", func(t *testing.T) {
// Given a mock with a custom bundle function
mock := NewMockBundler()
called := false
mock.BundleFunc = func(artifact Artifact) error {
called = true
return nil
}

// When calling Bundle
artifact := NewMockArtifact()
err := mock.Bundle(artifact)

// Then the mock function should be called
if !called {
t.Error("Expected BundleFunc to be called")
}
if err != nil {
t.Errorf("Expected nil error, got %v", err)
}
})

t.Run("NotImplemented", func(t *testing.T) {
// Given a mock with no custom bundle function
mock := NewMockBundler()

// When calling Bundle
artifact := NewMockArtifact()
err := mock.Bundle(artifact)

// Then no error should be returned
if err != nil {
t.Errorf("Expected nil error, got %v", err)
}
})
}
Loading
Loading