test: increase coverage across packages
- pkg/container: 65.6% → 85.7% (hypervisor, linuxkit, templates tests) - pkg/release/publishers: 13.3% → 41.7% (homebrew, aur, npm, scoop, chocolatey tests) - Fix flaky test cleanup in TestLinuxKitManager_Stop_Good_ContextCancelled Overall coverage: 29.2% → 40.6% Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a7ee58d29e
commit
50f6839c51
8 changed files with 2386 additions and 32 deletions
358
pkg/container/hypervisor_test.go
Normal file
358
pkg/container/hypervisor_test.go
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestQemuHypervisor_Available_Good(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
// Check if qemu is available on this system
|
||||
available := q.Available()
|
||||
|
||||
// We just verify it returns a boolean without error
|
||||
// The actual availability depends on the system
|
||||
assert.IsType(t, true, available)
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_Available_Bad_InvalidBinary(t *testing.T) {
|
||||
q := &QemuHypervisor{
|
||||
Binary: "nonexistent-qemu-binary-that-does-not-exist",
|
||||
}
|
||||
|
||||
available := q.Available()
|
||||
|
||||
assert.False(t, available)
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_Available_Good(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
available := h.Available()
|
||||
|
||||
// On non-darwin systems, should always be false
|
||||
if runtime.GOOS != "darwin" {
|
||||
assert.False(t, available)
|
||||
} else {
|
||||
// On darwin, just verify it returns a boolean
|
||||
assert.IsType(t, true, available)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_Available_Bad_NotDarwin(t *testing.T) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
t.Skip("This test only runs on non-darwin systems")
|
||||
}
|
||||
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
available := h.Available()
|
||||
|
||||
assert.False(t, available, "Hyperkit should not be available on non-darwin systems")
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_Available_Bad_InvalidBinary(t *testing.T) {
|
||||
h := &HyperkitHypervisor{
|
||||
Binary: "nonexistent-hyperkit-binary-that-does-not-exist",
|
||||
}
|
||||
|
||||
available := h.Available()
|
||||
|
||||
assert.False(t, available)
|
||||
}
|
||||
|
||||
func TestIsKVMAvailable_Good(t *testing.T) {
|
||||
// This test verifies the function runs without error
|
||||
// The actual result depends on the system
|
||||
result := isKVMAvailable()
|
||||
|
||||
// On non-linux systems, should be false
|
||||
if runtime.GOOS != "linux" {
|
||||
assert.False(t, result, "KVM should not be available on non-linux systems")
|
||||
} else {
|
||||
// On linux, just verify it returns a boolean
|
||||
assert.IsType(t, true, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectHypervisor_Good(t *testing.T) {
|
||||
// DetectHypervisor tries to find an available hypervisor
|
||||
hv, err := DetectHypervisor()
|
||||
|
||||
// This test may pass or fail depending on system configuration
|
||||
// If no hypervisor is available, it should return an error
|
||||
if err != nil {
|
||||
assert.Nil(t, hv)
|
||||
assert.Contains(t, err.Error(), "no hypervisor available")
|
||||
} else {
|
||||
assert.NotNil(t, hv)
|
||||
assert.NotEmpty(t, hv.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHypervisor_Good_Qemu(t *testing.T) {
|
||||
hv, err := GetHypervisor("qemu")
|
||||
|
||||
// Depends on whether qemu is installed
|
||||
if err != nil {
|
||||
assert.Contains(t, err.Error(), "not available")
|
||||
} else {
|
||||
assert.NotNil(t, hv)
|
||||
assert.Equal(t, "qemu", hv.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHypervisor_Good_QemuUppercase(t *testing.T) {
|
||||
hv, err := GetHypervisor("QEMU")
|
||||
|
||||
// Depends on whether qemu is installed
|
||||
if err != nil {
|
||||
assert.Contains(t, err.Error(), "not available")
|
||||
} else {
|
||||
assert.NotNil(t, hv)
|
||||
assert.Equal(t, "qemu", hv.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHypervisor_Good_Hyperkit(t *testing.T) {
|
||||
hv, err := GetHypervisor("hyperkit")
|
||||
|
||||
// On non-darwin systems, should always fail
|
||||
if runtime.GOOS != "darwin" {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not available")
|
||||
} else {
|
||||
// On darwin, depends on whether hyperkit is installed
|
||||
if err != nil {
|
||||
assert.Contains(t, err.Error(), "not available")
|
||||
} else {
|
||||
assert.NotNil(t, hv)
|
||||
assert.Equal(t, "hyperkit", hv.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHypervisor_Bad_Unknown(t *testing.T) {
|
||||
_, err := GetHypervisor("unknown-hypervisor")
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unknown hypervisor")
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_BuildCommand_Good_WithPortsAndVolumes(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{
|
||||
Memory: 2048,
|
||||
CPUs: 4,
|
||||
SSHPort: 2222,
|
||||
Ports: map[int]int{8080: 80, 443: 443},
|
||||
Volumes: map[string]string{
|
||||
"/host/data": "/container/data",
|
||||
"/host/logs": "/container/logs",
|
||||
},
|
||||
Detach: true,
|
||||
}
|
||||
|
||||
cmd, err := q.BuildCommand(ctx, "/path/to/image.iso", opts)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, cmd)
|
||||
|
||||
// Verify command includes all expected args
|
||||
args := cmd.Args
|
||||
assert.Contains(t, args, "-m")
|
||||
assert.Contains(t, args, "2048")
|
||||
assert.Contains(t, args, "-smp")
|
||||
assert.Contains(t, args, "4")
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_BuildCommand_Good_QCow2Format(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{Memory: 1024, CPUs: 1}
|
||||
|
||||
cmd, err := q.BuildCommand(ctx, "/path/to/image.qcow2", opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that the drive format is qcow2
|
||||
found := false
|
||||
for _, arg := range cmd.Args {
|
||||
if arg == "file=/path/to/image.qcow2,format=qcow2" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "Should have qcow2 drive argument")
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_BuildCommand_Good_VMDKFormat(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{Memory: 1024, CPUs: 1}
|
||||
|
||||
cmd, err := q.BuildCommand(ctx, "/path/to/image.vmdk", opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that the drive format is vmdk
|
||||
found := false
|
||||
for _, arg := range cmd.Args {
|
||||
if arg == "file=/path/to/image.vmdk,format=vmdk" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "Should have vmdk drive argument")
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_BuildCommand_Good_RawFormat(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{Memory: 1024, CPUs: 1}
|
||||
|
||||
cmd, err := q.BuildCommand(ctx, "/path/to/image.raw", opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that the drive format is raw
|
||||
found := false
|
||||
for _, arg := range cmd.Args {
|
||||
if arg == "file=/path/to/image.raw,format=raw" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "Should have raw drive argument")
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Good_WithPorts(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{
|
||||
Memory: 1024,
|
||||
CPUs: 2,
|
||||
SSHPort: 2222,
|
||||
Ports: map[int]int{8080: 80},
|
||||
}
|
||||
|
||||
cmd, err := h.BuildCommand(ctx, "/path/to/image.iso", opts)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, cmd)
|
||||
|
||||
// Verify it creates a command with memory and CPU args
|
||||
args := cmd.Args
|
||||
assert.Contains(t, args, "-m")
|
||||
assert.Contains(t, args, "1024M")
|
||||
assert.Contains(t, args, "-c")
|
||||
assert.Contains(t, args, "2")
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Good_QCow2Format(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{Memory: 1024, CPUs: 1}
|
||||
|
||||
cmd, err := h.BuildCommand(ctx, "/path/to/image.qcow2", opts)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, cmd)
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Good_RawFormat(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{Memory: 1024, CPUs: 1}
|
||||
|
||||
cmd, err := h.BuildCommand(ctx, "/path/to/image.raw", opts)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, cmd)
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Good_NoPorts(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{
|
||||
Memory: 512,
|
||||
CPUs: 1,
|
||||
SSHPort: 0, // No SSH port
|
||||
Ports: nil,
|
||||
}
|
||||
|
||||
cmd, err := h.BuildCommand(ctx, "/path/to/image.iso", opts)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, cmd)
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_BuildCommand_Good_NoSSHPort(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{
|
||||
Memory: 512,
|
||||
CPUs: 1,
|
||||
SSHPort: 0, // No SSH port
|
||||
Ports: nil,
|
||||
}
|
||||
|
||||
cmd, err := q.BuildCommand(ctx, "/path/to/image.iso", opts)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, cmd)
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_BuildCommand_Bad_UnknownFormat(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{Memory: 1024, CPUs: 1}
|
||||
|
||||
_, err := q.BuildCommand(ctx, "/path/to/image.txt", opts)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unknown image format")
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Bad_UnknownFormat(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{Memory: 1024, CPUs: 1}
|
||||
|
||||
_, err := h.BuildCommand(ctx, "/path/to/image.unknown", opts)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unknown image format")
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_Name_Good(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
assert.Equal(t, "hyperkit", h.Name())
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Good_ISOFormat(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{
|
||||
Memory: 1024,
|
||||
CPUs: 2,
|
||||
SSHPort: 2222,
|
||||
}
|
||||
|
||||
cmd, err := h.BuildCommand(ctx, "/path/to/image.iso", opts)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, cmd)
|
||||
|
||||
args := cmd.Args
|
||||
assert.Contains(t, args, "-m")
|
||||
assert.Contains(t, args, "1024M")
|
||||
assert.Contains(t, args, "-c")
|
||||
assert.Contains(t, args, "2")
|
||||
}
|
||||
|
|
@ -410,56 +410,362 @@ func TestQemuHypervisor_BuildCommand_Good(t *testing.T) {
|
|||
assert.Contains(t, args, "-nographic")
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_BuildCommand_Bad_UnknownFormat(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
func TestLinuxKitManager_Logs_Good_Follow(t *testing.T) {
|
||||
manager, _, _ := newTestManager(t)
|
||||
|
||||
// Create a unique container ID
|
||||
uniqueID, _ := GenerateID()
|
||||
container := &Container{ID: uniqueID}
|
||||
manager.State().Add(container)
|
||||
|
||||
// Create a log file at the expected location
|
||||
logPath, err := LogPath(uniqueID)
|
||||
require.NoError(t, err)
|
||||
os.MkdirAll(filepath.Dir(logPath), 0755)
|
||||
|
||||
// Write initial content
|
||||
err = os.WriteFile(logPath, []byte("initial log content\n"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a cancellable context
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// Get the follow reader
|
||||
reader, err := manager.Logs(ctx, uniqueID, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Cancel the context to stop the follow
|
||||
cancel()
|
||||
|
||||
// Read should return EOF after context cancellation
|
||||
buf := make([]byte, 1024)
|
||||
_, readErr := reader.Read(buf)
|
||||
// After context cancel, Read should return EOF
|
||||
assert.Equal(t, "EOF", readErr.Error())
|
||||
|
||||
// Close the reader
|
||||
err = reader.Close()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestFollowReader_Read_Good_WithData(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
logPath := filepath.Join(tmpDir, "test.log")
|
||||
|
||||
// Create log file with content
|
||||
content := "test log line 1\ntest log line 2\n"
|
||||
err := os.WriteFile(logPath, []byte(content), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
reader, err := newFollowReader(ctx, logPath)
|
||||
require.NoError(t, err)
|
||||
defer reader.Close()
|
||||
|
||||
// The followReader seeks to end, so we need to append more content
|
||||
f, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
require.NoError(t, err)
|
||||
_, err = f.WriteString("new line\n")
|
||||
require.NoError(t, err)
|
||||
f.Close()
|
||||
|
||||
// Give the reader time to poll
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, err := reader.Read(buf)
|
||||
if err == nil {
|
||||
assert.Greater(t, n, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFollowReader_Read_Good_ContextCancel(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
logPath := filepath.Join(tmpDir, "test.log")
|
||||
|
||||
// Create log file
|
||||
err := os.WriteFile(logPath, []byte("initial content\n"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
reader, err := newFollowReader(ctx, logPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Cancel the context
|
||||
cancel()
|
||||
|
||||
// Read should return EOF
|
||||
buf := make([]byte, 1024)
|
||||
_, readErr := reader.Read(buf)
|
||||
assert.Equal(t, "EOF", readErr.Error())
|
||||
|
||||
reader.Close()
|
||||
}
|
||||
|
||||
func TestFollowReader_Close_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
logPath := filepath.Join(tmpDir, "test.log")
|
||||
|
||||
err := os.WriteFile(logPath, []byte("content\n"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{Memory: 1024, CPUs: 1}
|
||||
reader, err := newFollowReader(ctx, logPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Reading after close should fail or return EOF
|
||||
buf := make([]byte, 1024)
|
||||
_, readErr := reader.Read(buf)
|
||||
assert.Error(t, readErr)
|
||||
}
|
||||
|
||||
func TestNewFollowReader_Bad_FileNotFound(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
_, err := newFollowReader(ctx, "/nonexistent/path/to/file.log")
|
||||
|
||||
_, err := q.BuildCommand(ctx, "/path/to/image.txt", opts)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unknown image format")
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_Name_Good(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
assert.Equal(t, "hyperkit", h.Name())
|
||||
}
|
||||
func TestLinuxKitManager_Run_Bad_BuildCommandError(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Good(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
// Create a test image file
|
||||
imagePath := filepath.Join(tmpDir, "test.iso")
|
||||
err := os.WriteFile(imagePath, []byte("fake image"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Configure mock to return an error
|
||||
mock.buildErr = assert.AnError
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{
|
||||
Memory: 1024,
|
||||
CPUs: 2,
|
||||
SSHPort: 2222,
|
||||
opts := RunOptions{Detach: true}
|
||||
|
||||
_, err = manager.Run(ctx, imagePath, opts)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to build hypervisor command")
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Good_Foreground(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
// Create a test image file
|
||||
imagePath := filepath.Join(tmpDir, "test.iso")
|
||||
err := os.WriteFile(imagePath, []byte("fake image"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Use echo which exits quickly
|
||||
mock.commandToRun = "echo"
|
||||
|
||||
ctx := context.Background()
|
||||
opts := RunOptions{
|
||||
Name: "test-foreground",
|
||||
Detach: false, // Run in foreground
|
||||
Memory: 512,
|
||||
CPUs: 1,
|
||||
}
|
||||
|
||||
cmd, err := h.BuildCommand(ctx, "/path/to/image.iso", opts)
|
||||
container, err := manager.Run(ctx, imagePath, opts)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, cmd)
|
||||
|
||||
args := cmd.Args
|
||||
assert.Contains(t, args, "-m")
|
||||
assert.Contains(t, args, "1024M")
|
||||
assert.Contains(t, args, "-c")
|
||||
assert.Contains(t, args, "2")
|
||||
assert.NotEmpty(t, container.ID)
|
||||
assert.Equal(t, "test-foreground", container.Name)
|
||||
// Foreground process should have completed
|
||||
assert.Equal(t, StatusStopped, container.Status)
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Bad_UnknownFormat(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
func TestLinuxKitManager_Stop_Good_ContextCancelled(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
// Create a test image file
|
||||
imagePath := filepath.Join(tmpDir, "test.iso")
|
||||
err := os.WriteFile(imagePath, []byte("fake image"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Use a command that takes a long time
|
||||
mock.commandToRun = "sleep"
|
||||
|
||||
// Start a container
|
||||
ctx := context.Background()
|
||||
opts := RunOptions{
|
||||
Name: "test-cancel",
|
||||
Detach: true,
|
||||
}
|
||||
|
||||
container, err := manager.Run(ctx, imagePath, opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Ensure cleanup happens regardless of test outcome
|
||||
t.Cleanup(func() {
|
||||
_ = manager.Stop(context.Background(), container.ID)
|
||||
})
|
||||
|
||||
// Create a context that's already cancelled
|
||||
cancelCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
// Stop with cancelled context
|
||||
err = manager.Stop(cancelCtx, container.ID)
|
||||
// Should return context error
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, context.Canceled, err)
|
||||
}
|
||||
|
||||
func TestIsProcessRunning_Good_ExistingProcess(t *testing.T) {
|
||||
// Use our own PID which definitely exists
|
||||
running := isProcessRunning(os.Getpid())
|
||||
assert.True(t, running)
|
||||
}
|
||||
|
||||
func TestIsProcessRunning_Bad_NonexistentProcess(t *testing.T) {
|
||||
// Use a PID that almost certainly doesn't exist
|
||||
running := isProcessRunning(999999)
|
||||
assert.False(t, running)
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Good_WithPortsAndVolumes(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
imagePath := filepath.Join(tmpDir, "test.iso")
|
||||
err := os.WriteFile(imagePath, []byte("fake image"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
opts := &HypervisorOptions{Memory: 1024, CPUs: 1}
|
||||
opts := RunOptions{
|
||||
Name: "test-ports",
|
||||
Detach: true,
|
||||
Memory: 512,
|
||||
CPUs: 1,
|
||||
SSHPort: 2223,
|
||||
Ports: map[int]int{8080: 80, 443: 443},
|
||||
Volumes: map[string]string{"/host/data": "/container/data"},
|
||||
}
|
||||
|
||||
_, err := h.BuildCommand(ctx, "/path/to/image.unknown", opts)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unknown image format")
|
||||
container, err := manager.Run(ctx, imagePath, opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEmpty(t, container.ID)
|
||||
assert.Equal(t, map[int]int{8080: 80, 443: 443}, container.Ports)
|
||||
assert.Equal(t, 2223, mock.lastOpts.SSHPort)
|
||||
assert.Equal(t, map[string]string{"/host/data": "/container/data"}, mock.lastOpts.Volumes)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestGetHypervisor_Bad_Unknown(t *testing.T) {
|
||||
_, err := GetHypervisor("unknown-hypervisor")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unknown hypervisor")
|
||||
func TestFollowReader_Read_Good_ReaderError(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
logPath := filepath.Join(tmpDir, "test.log")
|
||||
|
||||
// Create log file
|
||||
err := os.WriteFile(logPath, []byte("content\n"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
reader, err := newFollowReader(ctx, logPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Close the underlying file to cause read errors
|
||||
reader.file.Close()
|
||||
|
||||
// Read should return an error
|
||||
buf := make([]byte, 1024)
|
||||
_, readErr := reader.Read(buf)
|
||||
assert.Error(t, readErr)
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Bad_StartError(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
imagePath := filepath.Join(tmpDir, "test.iso")
|
||||
err := os.WriteFile(imagePath, []byte("fake image"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Use a command that doesn't exist to cause Start() to fail
|
||||
mock.commandToRun = "/nonexistent/command/that/does/not/exist"
|
||||
|
||||
ctx := context.Background()
|
||||
opts := RunOptions{
|
||||
Name: "test-start-error",
|
||||
Detach: true,
|
||||
}
|
||||
|
||||
_, err = manager.Run(ctx, imagePath, opts)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to start VM")
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Bad_ForegroundStartError(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
imagePath := filepath.Join(tmpDir, "test.iso")
|
||||
err := os.WriteFile(imagePath, []byte("fake image"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Use a command that doesn't exist to cause Start() to fail
|
||||
mock.commandToRun = "/nonexistent/command/that/does/not/exist"
|
||||
|
||||
ctx := context.Background()
|
||||
opts := RunOptions{
|
||||
Name: "test-foreground-error",
|
||||
Detach: false,
|
||||
}
|
||||
|
||||
_, err = manager.Run(ctx, imagePath, opts)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to start VM")
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Good_ForegroundWithError(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
imagePath := filepath.Join(tmpDir, "test.iso")
|
||||
err := os.WriteFile(imagePath, []byte("fake image"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Use a command that exits with error
|
||||
mock.commandToRun = "false" // false command exits with code 1
|
||||
|
||||
ctx := context.Background()
|
||||
opts := RunOptions{
|
||||
Name: "test-foreground-exit-error",
|
||||
Detach: false,
|
||||
}
|
||||
|
||||
container, err := manager.Run(ctx, imagePath, opts)
|
||||
require.NoError(t, err) // Run itself should succeed
|
||||
|
||||
// Container should be in error state since process exited with error
|
||||
assert.Equal(t, StatusError, container.Status)
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Stop_Good_ProcessExitedWhileRunning(t *testing.T) {
|
||||
manager, _, _ := newTestManager(t)
|
||||
|
||||
// Add a "running" container with a process that has already exited
|
||||
// This simulates the race condition where process exits between status check
|
||||
// and signal send
|
||||
container := &Container{
|
||||
ID: "test1234",
|
||||
Status: StatusRunning,
|
||||
PID: 999999, // Non-existent PID
|
||||
StartedAt: time.Now(),
|
||||
}
|
||||
manager.State().Add(container)
|
||||
|
||||
ctx := context.Background()
|
||||
err := manager.Stop(ctx, "test1234")
|
||||
|
||||
// Stop should succeed gracefully
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Container should be stopped
|
||||
c, ok := manager.State().Get("test1234")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, StatusStopped, c.Status)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -383,3 +383,201 @@ func TestVariablePatternEdgeCases_Good(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTemplates_Good_WithUserTemplates(t *testing.T) {
|
||||
// Create a workspace directory with user templates
|
||||
tmpDir := t.TempDir()
|
||||
coreDir := filepath.Join(tmpDir, ".core", "linuxkit")
|
||||
err := os.MkdirAll(coreDir, 0755)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a user template
|
||||
templateContent := `# Custom user template
|
||||
kernel:
|
||||
image: linuxkit/kernel:6.6
|
||||
`
|
||||
err = os.WriteFile(filepath.Join(coreDir, "user-custom.yml"), []byte(templateContent), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Change to the temp directory
|
||||
oldWd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
err = os.Chdir(tmpDir)
|
||||
require.NoError(t, err)
|
||||
defer os.Chdir(oldWd)
|
||||
|
||||
templates := ListTemplates()
|
||||
|
||||
// Should have at least the builtin templates plus the user template
|
||||
assert.GreaterOrEqual(t, len(templates), 3)
|
||||
|
||||
// Check that user template is included
|
||||
found := false
|
||||
for _, tmpl := range templates {
|
||||
if tmpl.Name == "user-custom" {
|
||||
found = true
|
||||
assert.Equal(t, "Custom user template", tmpl.Description)
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "user-custom template should exist")
|
||||
}
|
||||
|
||||
func TestGetTemplate_Good_UserTemplate(t *testing.T) {
|
||||
// Create a workspace directory with user templates
|
||||
tmpDir := t.TempDir()
|
||||
coreDir := filepath.Join(tmpDir, ".core", "linuxkit")
|
||||
err := os.MkdirAll(coreDir, 0755)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a user template
|
||||
templateContent := `# My user template
|
||||
kernel:
|
||||
image: linuxkit/kernel:6.6
|
||||
services:
|
||||
- name: test
|
||||
`
|
||||
err = os.WriteFile(filepath.Join(coreDir, "my-user-template.yml"), []byte(templateContent), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Change to the temp directory
|
||||
oldWd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
err = os.Chdir(tmpDir)
|
||||
require.NoError(t, err)
|
||||
defer os.Chdir(oldWd)
|
||||
|
||||
content, err := GetTemplate("my-user-template")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, content, "kernel:")
|
||||
assert.Contains(t, content, "My user template")
|
||||
}
|
||||
|
||||
func TestScanUserTemplates_Good_SkipsBuiltinNames(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create a template with a builtin name (should be skipped)
|
||||
err := os.WriteFile(filepath.Join(tmpDir, "core-dev.yml"), []byte("# Duplicate\nkernel:"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a unique template
|
||||
err = os.WriteFile(filepath.Join(tmpDir, "unique.yml"), []byte("# Unique\nkernel:"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
templates := scanUserTemplates(tmpDir)
|
||||
|
||||
// Should only have the unique template, not the builtin name
|
||||
assert.Len(t, templates, 1)
|
||||
assert.Equal(t, "unique", templates[0].Name)
|
||||
}
|
||||
|
||||
func TestScanUserTemplates_Good_SkipsDirectories(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create a subdirectory (should be skipped)
|
||||
err := os.MkdirAll(filepath.Join(tmpDir, "subdir"), 0755)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a valid template
|
||||
err = os.WriteFile(filepath.Join(tmpDir, "valid.yml"), []byte("# Valid\nkernel:"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
templates := scanUserTemplates(tmpDir)
|
||||
|
||||
assert.Len(t, templates, 1)
|
||||
assert.Equal(t, "valid", templates[0].Name)
|
||||
}
|
||||
|
||||
func TestScanUserTemplates_Good_YamlExtension(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create templates with both extensions
|
||||
err := os.WriteFile(filepath.Join(tmpDir, "template1.yml"), []byte("# Template 1\nkernel:"), 0644)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(filepath.Join(tmpDir, "template2.yaml"), []byte("# Template 2\nkernel:"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
templates := scanUserTemplates(tmpDir)
|
||||
|
||||
assert.Len(t, templates, 2)
|
||||
|
||||
names := make(map[string]bool)
|
||||
for _, tmpl := range templates {
|
||||
names[tmpl.Name] = true
|
||||
}
|
||||
assert.True(t, names["template1"])
|
||||
assert.True(t, names["template2"])
|
||||
}
|
||||
|
||||
func TestExtractTemplateDescription_Good_EmptyComment(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
path := filepath.Join(tmpDir, "test.yml")
|
||||
|
||||
// First comment is empty, second has content
|
||||
content := `#
|
||||
# Actual description here
|
||||
kernel:
|
||||
image: test
|
||||
`
|
||||
err := os.WriteFile(path, []byte(content), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
desc := extractTemplateDescription(path)
|
||||
|
||||
assert.Equal(t, "Actual description here", desc)
|
||||
}
|
||||
|
||||
func TestExtractTemplateDescription_Good_MultipleEmptyComments(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
path := filepath.Join(tmpDir, "test.yml")
|
||||
|
||||
// Multiple empty comments before actual content
|
||||
content := `#
|
||||
#
|
||||
#
|
||||
# Real description
|
||||
kernel:
|
||||
image: test
|
||||
`
|
||||
err := os.WriteFile(path, []byte(content), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
desc := extractTemplateDescription(path)
|
||||
|
||||
assert.Equal(t, "Real description", desc)
|
||||
}
|
||||
|
||||
func TestGetUserTemplatesDir_Good_NoDirectory(t *testing.T) {
|
||||
// Save current working directory
|
||||
oldWd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a temp directory without .core/linuxkit
|
||||
tmpDir := t.TempDir()
|
||||
err = os.Chdir(tmpDir)
|
||||
require.NoError(t, err)
|
||||
defer os.Chdir(oldWd)
|
||||
|
||||
dir := getUserTemplatesDir()
|
||||
|
||||
// Should return empty string since no templates dir exists
|
||||
// (unless home dir has one)
|
||||
assert.True(t, dir == "" || strings.Contains(dir, "linuxkit"))
|
||||
}
|
||||
|
||||
func TestScanUserTemplates_Good_DefaultDescription(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create a template without comments
|
||||
content := `kernel:
|
||||
image: test
|
||||
`
|
||||
err := os.WriteFile(filepath.Join(tmpDir, "nocomment.yml"), []byte(content), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
templates := scanUserTemplates(tmpDir)
|
||||
|
||||
assert.Len(t, templates, 1)
|
||||
assert.Equal(t, "User-defined template", templates[0].Description)
|
||||
}
|
||||
|
|
|
|||
223
pkg/release/publishers/aur_test.go
Normal file
223
pkg/release/publishers/aur_test.go
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
package publishers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAURPublisher_Name_Good(t *testing.T) {
|
||||
t.Run("returns aur", func(t *testing.T) {
|
||||
p := NewAURPublisher()
|
||||
assert.Equal(t, "aur", p.Name())
|
||||
})
|
||||
}
|
||||
|
||||
func TestAURPublisher_ParseConfig_Good(t *testing.T) {
|
||||
p := NewAURPublisher()
|
||||
|
||||
t.Run("uses defaults when no extended config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{Type: "aur"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Package)
|
||||
assert.Empty(t, cfg.Maintainer)
|
||||
assert.Nil(t, cfg.Official)
|
||||
})
|
||||
|
||||
t.Run("parses package and maintainer from extended config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "aur",
|
||||
Extended: map[string]any{
|
||||
"package": "mypackage",
|
||||
"maintainer": "John Doe <john@example.com>",
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Equal(t, "mypackage", cfg.Package)
|
||||
assert.Equal(t, "John Doe <john@example.com>", cfg.Maintainer)
|
||||
})
|
||||
|
||||
t.Run("parses official config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "aur",
|
||||
Extended: map[string]any{
|
||||
"official": map[string]any{
|
||||
"enabled": true,
|
||||
"output": "dist/aur-files",
|
||||
},
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
require.NotNil(t, cfg.Official)
|
||||
assert.True(t, cfg.Official.Enabled)
|
||||
assert.Equal(t, "dist/aur-files", cfg.Official.Output)
|
||||
})
|
||||
|
||||
t.Run("handles missing official fields", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "aur",
|
||||
Extended: map[string]any{
|
||||
"official": map[string]any{},
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
require.NotNil(t, cfg.Official)
|
||||
assert.False(t, cfg.Official.Enabled)
|
||||
assert.Empty(t, cfg.Official.Output)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAURPublisher_RenderTemplate_Good(t *testing.T) {
|
||||
p := NewAURPublisher()
|
||||
|
||||
t.Run("renders PKGBUILD template with data", func(t *testing.T) {
|
||||
data := aurTemplateData{
|
||||
PackageName: "myapp",
|
||||
Description: "My awesome CLI",
|
||||
Repository: "owner/myapp",
|
||||
Version: "1.2.3",
|
||||
License: "MIT",
|
||||
BinaryName: "myapp",
|
||||
Maintainer: "John Doe <john@example.com>",
|
||||
Checksums: ChecksumMap{
|
||||
LinuxAmd64: "abc123",
|
||||
LinuxArm64: "def456",
|
||||
},
|
||||
}
|
||||
|
||||
result, err := p.renderTemplate("templates/aur/PKGBUILD.tmpl", data)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, result, "# Maintainer: John Doe <john@example.com>")
|
||||
assert.Contains(t, result, "pkgname=myapp-bin")
|
||||
assert.Contains(t, result, "pkgver=1.2.3")
|
||||
assert.Contains(t, result, `pkgdesc="My awesome CLI"`)
|
||||
assert.Contains(t, result, "url=\"https://github.com/owner/myapp\"")
|
||||
assert.Contains(t, result, "license=('MIT')")
|
||||
assert.Contains(t, result, "sha256sums_x86_64=('abc123')")
|
||||
assert.Contains(t, result, "sha256sums_aarch64=('def456')")
|
||||
})
|
||||
|
||||
t.Run("renders .SRCINFO template with data", func(t *testing.T) {
|
||||
data := aurTemplateData{
|
||||
PackageName: "myapp",
|
||||
Description: "My CLI",
|
||||
Repository: "owner/myapp",
|
||||
Version: "1.0.0",
|
||||
License: "MIT",
|
||||
BinaryName: "myapp",
|
||||
Maintainer: "Test <test@test.com>",
|
||||
Checksums: ChecksumMap{
|
||||
LinuxAmd64: "checksum1",
|
||||
LinuxArm64: "checksum2",
|
||||
},
|
||||
}
|
||||
|
||||
result, err := p.renderTemplate("templates/aur/.SRCINFO.tmpl", data)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, result, "pkgbase = myapp-bin")
|
||||
assert.Contains(t, result, "pkgdesc = My CLI")
|
||||
assert.Contains(t, result, "pkgver = 1.0.0")
|
||||
assert.Contains(t, result, "arch = x86_64")
|
||||
assert.Contains(t, result, "arch = aarch64")
|
||||
assert.Contains(t, result, "sha256sums_x86_64 = checksum1")
|
||||
assert.Contains(t, result, "sha256sums_aarch64 = checksum2")
|
||||
assert.Contains(t, result, "pkgname = myapp-bin")
|
||||
})
|
||||
}
|
||||
|
||||
func TestAURPublisher_RenderTemplate_Bad(t *testing.T) {
|
||||
p := NewAURPublisher()
|
||||
|
||||
t.Run("returns error for non-existent template", func(t *testing.T) {
|
||||
data := aurTemplateData{}
|
||||
_, err := p.renderTemplate("templates/aur/nonexistent.tmpl", data)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to read template")
|
||||
})
|
||||
}
|
||||
|
||||
func TestAURPublisher_DryRunPublish_Good(t *testing.T) {
|
||||
p := NewAURPublisher()
|
||||
|
||||
t.Run("outputs expected dry run information", func(t *testing.T) {
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
data := aurTemplateData{
|
||||
PackageName: "myapp",
|
||||
Version: "1.0.0",
|
||||
Maintainer: "John Doe <john@example.com>",
|
||||
Repository: "owner/repo",
|
||||
BinaryName: "myapp",
|
||||
Checksums: ChecksumMap{},
|
||||
}
|
||||
cfg := AURConfig{
|
||||
Maintainer: "John Doe <john@example.com>",
|
||||
}
|
||||
|
||||
err := p.dryRunPublish(data, cfg)
|
||||
|
||||
w.Close()
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.ReadFrom(r)
|
||||
os.Stdout = oldStdout
|
||||
|
||||
require.NoError(t, err)
|
||||
output := buf.String()
|
||||
|
||||
assert.Contains(t, output, "DRY RUN: AUR Publish")
|
||||
assert.Contains(t, output, "Package: myapp-bin")
|
||||
assert.Contains(t, output, "Version: 1.0.0")
|
||||
assert.Contains(t, output, "Maintainer: John Doe <john@example.com>")
|
||||
assert.Contains(t, output, "Repository: owner/repo")
|
||||
assert.Contains(t, output, "Generated PKGBUILD:")
|
||||
assert.Contains(t, output, "Generated .SRCINFO:")
|
||||
assert.Contains(t, output, "Would push to AUR: ssh://aur@aur.archlinux.org/myapp-bin.git")
|
||||
assert.Contains(t, output, "END DRY RUN")
|
||||
})
|
||||
}
|
||||
|
||||
func TestAURPublisher_Publish_Bad(t *testing.T) {
|
||||
p := NewAURPublisher()
|
||||
|
||||
t.Run("fails when maintainer not configured", func(t *testing.T) {
|
||||
release := &Release{
|
||||
Version: "v1.0.0",
|
||||
ProjectDir: "/project",
|
||||
}
|
||||
pubCfg := PublisherConfig{Type: "aur"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
|
||||
err := p.Publish(nil, release, pubCfg, relCfg, false)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "maintainer is required")
|
||||
})
|
||||
}
|
||||
|
||||
func TestAURConfig_Defaults_Good(t *testing.T) {
|
||||
t.Run("has sensible defaults", func(t *testing.T) {
|
||||
p := NewAURPublisher()
|
||||
pubCfg := PublisherConfig{Type: "aur"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Package)
|
||||
assert.Empty(t, cfg.Maintainer)
|
||||
assert.Nil(t, cfg.Official)
|
||||
})
|
||||
}
|
||||
320
pkg/release/publishers/chocolatey_test.go
Normal file
320
pkg/release/publishers/chocolatey_test.go
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
package publishers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestChocolateyPublisher_Name_Good(t *testing.T) {
|
||||
t.Run("returns chocolatey", func(t *testing.T) {
|
||||
p := NewChocolateyPublisher()
|
||||
assert.Equal(t, "chocolatey", p.Name())
|
||||
})
|
||||
}
|
||||
|
||||
func TestChocolateyPublisher_ParseConfig_Good(t *testing.T) {
|
||||
p := NewChocolateyPublisher()
|
||||
|
||||
t.Run("uses defaults when no extended config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{Type: "chocolatey"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Package)
|
||||
assert.False(t, cfg.Push)
|
||||
assert.Nil(t, cfg.Official)
|
||||
})
|
||||
|
||||
t.Run("parses package and push from extended config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "chocolatey",
|
||||
Extended: map[string]any{
|
||||
"package": "mypackage",
|
||||
"push": true,
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Equal(t, "mypackage", cfg.Package)
|
||||
assert.True(t, cfg.Push)
|
||||
})
|
||||
|
||||
t.Run("parses official config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "chocolatey",
|
||||
Extended: map[string]any{
|
||||
"official": map[string]any{
|
||||
"enabled": true,
|
||||
"output": "dist/choco",
|
||||
},
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
require.NotNil(t, cfg.Official)
|
||||
assert.True(t, cfg.Official.Enabled)
|
||||
assert.Equal(t, "dist/choco", cfg.Official.Output)
|
||||
})
|
||||
|
||||
t.Run("handles missing official fields", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "chocolatey",
|
||||
Extended: map[string]any{
|
||||
"official": map[string]any{},
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
require.NotNil(t, cfg.Official)
|
||||
assert.False(t, cfg.Official.Enabled)
|
||||
assert.Empty(t, cfg.Official.Output)
|
||||
})
|
||||
|
||||
t.Run("handles nil extended config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "chocolatey",
|
||||
Extended: nil,
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Package)
|
||||
assert.False(t, cfg.Push)
|
||||
assert.Nil(t, cfg.Official)
|
||||
})
|
||||
|
||||
t.Run("defaults push to false when not specified", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "chocolatey",
|
||||
Extended: map[string]any{
|
||||
"package": "mypackage",
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.False(t, cfg.Push)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChocolateyPublisher_RenderTemplate_Good(t *testing.T) {
|
||||
p := NewChocolateyPublisher()
|
||||
|
||||
t.Run("renders nuspec template with data", func(t *testing.T) {
|
||||
data := chocolateyTemplateData{
|
||||
PackageName: "myapp",
|
||||
Title: "MyApp CLI",
|
||||
Description: "My awesome CLI",
|
||||
Repository: "owner/myapp",
|
||||
Version: "1.2.3",
|
||||
License: "MIT",
|
||||
BinaryName: "myapp",
|
||||
Authors: "owner",
|
||||
Tags: "cli myapp",
|
||||
Checksums: ChecksumMap{},
|
||||
}
|
||||
|
||||
result, err := p.renderTemplate("templates/chocolatey/package.nuspec.tmpl", data)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, result, `<id>myapp</id>`)
|
||||
assert.Contains(t, result, `<version>1.2.3</version>`)
|
||||
assert.Contains(t, result, `<title>MyApp CLI</title>`)
|
||||
assert.Contains(t, result, `<authors>owner</authors>`)
|
||||
assert.Contains(t, result, `<description>My awesome CLI</description>`)
|
||||
assert.Contains(t, result, `<tags>cli myapp</tags>`)
|
||||
assert.Contains(t, result, "projectUrl>https://github.com/owner/myapp")
|
||||
assert.Contains(t, result, "releaseNotes>https://github.com/owner/myapp/releases/tag/v1.2.3")
|
||||
})
|
||||
|
||||
t.Run("renders install script template with data", func(t *testing.T) {
|
||||
data := chocolateyTemplateData{
|
||||
PackageName: "myapp",
|
||||
Repository: "owner/myapp",
|
||||
Version: "1.2.3",
|
||||
BinaryName: "myapp",
|
||||
Checksums: ChecksumMap{
|
||||
WindowsAmd64: "abc123def456",
|
||||
},
|
||||
}
|
||||
|
||||
result, err := p.renderTemplate("templates/chocolatey/tools/chocolateyinstall.ps1.tmpl", data)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, result, "$ErrorActionPreference = 'Stop'")
|
||||
assert.Contains(t, result, "https://github.com/owner/myapp/releases/download/v1.2.3/myapp-windows-amd64.zip")
|
||||
assert.Contains(t, result, "packageName = 'myapp'")
|
||||
assert.Contains(t, result, "checksum64 = 'abc123def456'")
|
||||
assert.Contains(t, result, "checksumType64 = 'sha256'")
|
||||
assert.Contains(t, result, "Install-ChocolateyZipPackage")
|
||||
})
|
||||
}
|
||||
|
||||
func TestChocolateyPublisher_RenderTemplate_Bad(t *testing.T) {
|
||||
p := NewChocolateyPublisher()
|
||||
|
||||
t.Run("returns error for non-existent template", func(t *testing.T) {
|
||||
data := chocolateyTemplateData{}
|
||||
_, err := p.renderTemplate("templates/chocolatey/nonexistent.tmpl", data)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to read template")
|
||||
})
|
||||
}
|
||||
|
||||
func TestChocolateyPublisher_DryRunPublish_Good(t *testing.T) {
|
||||
p := NewChocolateyPublisher()
|
||||
|
||||
t.Run("outputs expected dry run information", func(t *testing.T) {
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
data := chocolateyTemplateData{
|
||||
PackageName: "myapp",
|
||||
Version: "1.0.0",
|
||||
Repository: "owner/repo",
|
||||
BinaryName: "myapp",
|
||||
Authors: "owner",
|
||||
Tags: "cli myapp",
|
||||
Checksums: ChecksumMap{},
|
||||
}
|
||||
cfg := ChocolateyConfig{
|
||||
Push: false,
|
||||
}
|
||||
|
||||
err := p.dryRunPublish(data, cfg)
|
||||
|
||||
w.Close()
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.ReadFrom(r)
|
||||
os.Stdout = oldStdout
|
||||
|
||||
require.NoError(t, err)
|
||||
output := buf.String()
|
||||
|
||||
assert.Contains(t, output, "DRY RUN: Chocolatey Publish")
|
||||
assert.Contains(t, output, "Package: myapp")
|
||||
assert.Contains(t, output, "Version: 1.0.0")
|
||||
assert.Contains(t, output, "Push: false")
|
||||
assert.Contains(t, output, "Repository: owner/repo")
|
||||
assert.Contains(t, output, "Generated package.nuspec:")
|
||||
assert.Contains(t, output, "Generated chocolateyinstall.ps1:")
|
||||
assert.Contains(t, output, "Would generate package files only (push=false)")
|
||||
assert.Contains(t, output, "END DRY RUN")
|
||||
})
|
||||
|
||||
t.Run("shows push message when push is enabled", func(t *testing.T) {
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
data := chocolateyTemplateData{
|
||||
PackageName: "myapp",
|
||||
Version: "1.0.0",
|
||||
BinaryName: "myapp",
|
||||
Authors: "owner",
|
||||
Tags: "cli",
|
||||
Checksums: ChecksumMap{},
|
||||
}
|
||||
cfg := ChocolateyConfig{
|
||||
Push: true,
|
||||
}
|
||||
|
||||
err := p.dryRunPublish(data, cfg)
|
||||
|
||||
w.Close()
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.ReadFrom(r)
|
||||
os.Stdout = oldStdout
|
||||
|
||||
require.NoError(t, err)
|
||||
output := buf.String()
|
||||
assert.Contains(t, output, "Push: true")
|
||||
assert.Contains(t, output, "Would push to Chocolatey community repo")
|
||||
})
|
||||
}
|
||||
|
||||
func TestChocolateyPublisher_ExecutePublish_Bad(t *testing.T) {
|
||||
p := NewChocolateyPublisher()
|
||||
|
||||
t.Run("fails when CHOCOLATEY_API_KEY not set for push", func(t *testing.T) {
|
||||
// Ensure CHOCOLATEY_API_KEY is not set
|
||||
oldKey := os.Getenv("CHOCOLATEY_API_KEY")
|
||||
os.Unsetenv("CHOCOLATEY_API_KEY")
|
||||
defer func() {
|
||||
if oldKey != "" {
|
||||
os.Setenv("CHOCOLATEY_API_KEY", oldKey)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create a temp directory for the test
|
||||
tmpDir, err := os.MkdirTemp("", "choco-test-*")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
data := chocolateyTemplateData{
|
||||
PackageName: "testpkg",
|
||||
Version: "1.0.0",
|
||||
BinaryName: "testpkg",
|
||||
Repository: "owner/repo",
|
||||
Authors: "owner",
|
||||
Tags: "cli",
|
||||
Checksums: ChecksumMap{},
|
||||
}
|
||||
|
||||
err = p.pushToChocolatey(nil, tmpDir, data)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "CHOCOLATEY_API_KEY environment variable is required")
|
||||
})
|
||||
}
|
||||
|
||||
func TestChocolateyConfig_Defaults_Good(t *testing.T) {
|
||||
t.Run("has sensible defaults", func(t *testing.T) {
|
||||
p := NewChocolateyPublisher()
|
||||
pubCfg := PublisherConfig{Type: "chocolatey"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Package)
|
||||
assert.False(t, cfg.Push)
|
||||
assert.Nil(t, cfg.Official)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChocolateyTemplateData_Good(t *testing.T) {
|
||||
t.Run("struct has all expected fields", func(t *testing.T) {
|
||||
data := chocolateyTemplateData{
|
||||
PackageName: "myapp",
|
||||
Title: "MyApp CLI",
|
||||
Description: "description",
|
||||
Repository: "org/repo",
|
||||
Version: "1.0.0",
|
||||
License: "MIT",
|
||||
BinaryName: "myapp",
|
||||
Authors: "org",
|
||||
Tags: "cli tool",
|
||||
Checksums: ChecksumMap{
|
||||
WindowsAmd64: "hash1",
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, "myapp", data.PackageName)
|
||||
assert.Equal(t, "MyApp CLI", data.Title)
|
||||
assert.Equal(t, "description", data.Description)
|
||||
assert.Equal(t, "org/repo", data.Repository)
|
||||
assert.Equal(t, "1.0.0", data.Version)
|
||||
assert.Equal(t, "MIT", data.License)
|
||||
assert.Equal(t, "myapp", data.BinaryName)
|
||||
assert.Equal(t, "org", data.Authors)
|
||||
assert.Equal(t, "cli tool", data.Tags)
|
||||
assert.Equal(t, "hash1", data.Checksums.WindowsAmd64)
|
||||
})
|
||||
}
|
||||
344
pkg/release/publishers/homebrew_test.go
Normal file
344
pkg/release/publishers/homebrew_test.go
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
package publishers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/host-uk/core/pkg/build"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHomebrewPublisher_Name_Good(t *testing.T) {
|
||||
t.Run("returns homebrew", func(t *testing.T) {
|
||||
p := NewHomebrewPublisher()
|
||||
assert.Equal(t, "homebrew", p.Name())
|
||||
})
|
||||
}
|
||||
|
||||
func TestHomebrewPublisher_ParseConfig_Good(t *testing.T) {
|
||||
p := NewHomebrewPublisher()
|
||||
|
||||
t.Run("uses defaults when no extended config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{Type: "homebrew"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Tap)
|
||||
assert.Empty(t, cfg.Formula)
|
||||
assert.Nil(t, cfg.Official)
|
||||
})
|
||||
|
||||
t.Run("parses tap and formula from extended config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "homebrew",
|
||||
Extended: map[string]any{
|
||||
"tap": "host-uk/homebrew-tap",
|
||||
"formula": "myformula",
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Equal(t, "host-uk/homebrew-tap", cfg.Tap)
|
||||
assert.Equal(t, "myformula", cfg.Formula)
|
||||
})
|
||||
|
||||
t.Run("parses official config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "homebrew",
|
||||
Extended: map[string]any{
|
||||
"official": map[string]any{
|
||||
"enabled": true,
|
||||
"output": "dist/brew",
|
||||
},
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
require.NotNil(t, cfg.Official)
|
||||
assert.True(t, cfg.Official.Enabled)
|
||||
assert.Equal(t, "dist/brew", cfg.Official.Output)
|
||||
})
|
||||
|
||||
t.Run("handles missing official fields", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "homebrew",
|
||||
Extended: map[string]any{
|
||||
"official": map[string]any{},
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
require.NotNil(t, cfg.Official)
|
||||
assert.False(t, cfg.Official.Enabled)
|
||||
assert.Empty(t, cfg.Official.Output)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHomebrewPublisher_ToFormulaClass_Good(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "simple name",
|
||||
input: "core",
|
||||
expected: "Core",
|
||||
},
|
||||
{
|
||||
name: "kebab case",
|
||||
input: "my-cli-tool",
|
||||
expected: "MyCliTool",
|
||||
},
|
||||
{
|
||||
name: "already capitalised",
|
||||
input: "CLI",
|
||||
expected: "CLI",
|
||||
},
|
||||
{
|
||||
name: "single letter",
|
||||
input: "x",
|
||||
expected: "X",
|
||||
},
|
||||
{
|
||||
name: "multiple dashes",
|
||||
input: "my-super-cool-app",
|
||||
expected: "MySuperCoolApp",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := toFormulaClass(tc.input)
|
||||
assert.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHomebrewPublisher_BuildChecksumMap_Good(t *testing.T) {
|
||||
t.Run("maps artifacts to checksums by platform", func(t *testing.T) {
|
||||
artifacts := []build.Artifact{
|
||||
{Path: "/dist/myapp-darwin-amd64.tar.gz", OS: "darwin", Arch: "amd64", Checksum: "abc123"},
|
||||
{Path: "/dist/myapp-darwin-arm64.tar.gz", OS: "darwin", Arch: "arm64", Checksum: "def456"},
|
||||
{Path: "/dist/myapp-linux-amd64.tar.gz", OS: "linux", Arch: "amd64", Checksum: "ghi789"},
|
||||
{Path: "/dist/myapp-linux-arm64.tar.gz", OS: "linux", Arch: "arm64", Checksum: "jkl012"},
|
||||
{Path: "/dist/myapp-windows-amd64.zip", OS: "windows", Arch: "amd64", Checksum: "mno345"},
|
||||
{Path: "/dist/myapp-windows-arm64.zip", OS: "windows", Arch: "arm64", Checksum: "pqr678"},
|
||||
}
|
||||
|
||||
checksums := buildChecksumMap(artifacts)
|
||||
|
||||
assert.Equal(t, "abc123", checksums.DarwinAmd64)
|
||||
assert.Equal(t, "def456", checksums.DarwinArm64)
|
||||
assert.Equal(t, "ghi789", checksums.LinuxAmd64)
|
||||
assert.Equal(t, "jkl012", checksums.LinuxArm64)
|
||||
assert.Equal(t, "mno345", checksums.WindowsAmd64)
|
||||
assert.Equal(t, "pqr678", checksums.WindowsArm64)
|
||||
})
|
||||
|
||||
t.Run("handles empty artifacts", func(t *testing.T) {
|
||||
checksums := buildChecksumMap([]build.Artifact{})
|
||||
|
||||
assert.Empty(t, checksums.DarwinAmd64)
|
||||
assert.Empty(t, checksums.DarwinArm64)
|
||||
assert.Empty(t, checksums.LinuxAmd64)
|
||||
assert.Empty(t, checksums.LinuxArm64)
|
||||
})
|
||||
|
||||
t.Run("handles partial platform coverage", func(t *testing.T) {
|
||||
artifacts := []build.Artifact{
|
||||
{Path: "/dist/myapp-darwin-arm64.tar.gz", Checksum: "def456"},
|
||||
{Path: "/dist/myapp-linux-amd64.tar.gz", Checksum: "ghi789"},
|
||||
}
|
||||
|
||||
checksums := buildChecksumMap(artifacts)
|
||||
|
||||
assert.Empty(t, checksums.DarwinAmd64)
|
||||
assert.Equal(t, "def456", checksums.DarwinArm64)
|
||||
assert.Equal(t, "ghi789", checksums.LinuxAmd64)
|
||||
assert.Empty(t, checksums.LinuxArm64)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHomebrewPublisher_RenderTemplate_Good(t *testing.T) {
|
||||
p := NewHomebrewPublisher()
|
||||
|
||||
t.Run("renders formula template with data", func(t *testing.T) {
|
||||
data := homebrewTemplateData{
|
||||
FormulaClass: "MyApp",
|
||||
Description: "My awesome CLI",
|
||||
Repository: "owner/myapp",
|
||||
Version: "1.2.3",
|
||||
License: "MIT",
|
||||
BinaryName: "myapp",
|
||||
Checksums: ChecksumMap{
|
||||
DarwinAmd64: "abc123",
|
||||
DarwinArm64: "def456",
|
||||
LinuxAmd64: "ghi789",
|
||||
LinuxArm64: "jkl012",
|
||||
},
|
||||
}
|
||||
|
||||
result, err := p.renderTemplate("templates/homebrew/formula.rb.tmpl", data)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, result, "class MyApp < Formula")
|
||||
assert.Contains(t, result, `desc "My awesome CLI"`)
|
||||
assert.Contains(t, result, `version "1.2.3"`)
|
||||
assert.Contains(t, result, `license "MIT"`)
|
||||
assert.Contains(t, result, "owner/myapp")
|
||||
assert.Contains(t, result, "abc123")
|
||||
assert.Contains(t, result, "def456")
|
||||
assert.Contains(t, result, "ghi789")
|
||||
assert.Contains(t, result, "jkl012")
|
||||
assert.Contains(t, result, `bin.install "myapp"`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHomebrewPublisher_RenderTemplate_Bad(t *testing.T) {
|
||||
p := NewHomebrewPublisher()
|
||||
|
||||
t.Run("returns error for non-existent template", func(t *testing.T) {
|
||||
data := homebrewTemplateData{}
|
||||
_, err := p.renderTemplate("templates/homebrew/nonexistent.tmpl", data)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to read template")
|
||||
})
|
||||
}
|
||||
|
||||
func TestHomebrewPublisher_DryRunPublish_Good(t *testing.T) {
|
||||
p := NewHomebrewPublisher()
|
||||
|
||||
t.Run("outputs expected dry run information", func(t *testing.T) {
|
||||
// Capture stdout
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
data := homebrewTemplateData{
|
||||
FormulaClass: "MyApp",
|
||||
Description: "My CLI",
|
||||
Repository: "owner/repo",
|
||||
Version: "1.0.0",
|
||||
License: "MIT",
|
||||
BinaryName: "myapp",
|
||||
Checksums: ChecksumMap{},
|
||||
}
|
||||
cfg := HomebrewConfig{
|
||||
Tap: "owner/homebrew-tap",
|
||||
}
|
||||
|
||||
err := p.dryRunPublish(data, cfg)
|
||||
|
||||
w.Close()
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.ReadFrom(r)
|
||||
os.Stdout = oldStdout
|
||||
|
||||
require.NoError(t, err)
|
||||
output := buf.String()
|
||||
|
||||
assert.Contains(t, output, "DRY RUN: Homebrew Publish")
|
||||
assert.Contains(t, output, "Formula: MyApp")
|
||||
assert.Contains(t, output, "Version: 1.0.0")
|
||||
assert.Contains(t, output, "Tap: owner/homebrew-tap")
|
||||
assert.Contains(t, output, "Repository: owner/repo")
|
||||
assert.Contains(t, output, "Would commit to tap: owner/homebrew-tap")
|
||||
assert.Contains(t, output, "END DRY RUN")
|
||||
})
|
||||
|
||||
t.Run("shows official output path when enabled", func(t *testing.T) {
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
data := homebrewTemplateData{
|
||||
FormulaClass: "MyApp",
|
||||
Version: "1.0.0",
|
||||
BinaryName: "myapp",
|
||||
Checksums: ChecksumMap{},
|
||||
}
|
||||
cfg := HomebrewConfig{
|
||||
Official: &OfficialConfig{
|
||||
Enabled: true,
|
||||
Output: "custom/path",
|
||||
},
|
||||
}
|
||||
|
||||
err := p.dryRunPublish(data, cfg)
|
||||
|
||||
w.Close()
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.ReadFrom(r)
|
||||
os.Stdout = oldStdout
|
||||
|
||||
require.NoError(t, err)
|
||||
output := buf.String()
|
||||
assert.Contains(t, output, "Would write files for official PR to: custom/path")
|
||||
})
|
||||
|
||||
t.Run("uses default official output path when not specified", func(t *testing.T) {
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
data := homebrewTemplateData{
|
||||
FormulaClass: "MyApp",
|
||||
Version: "1.0.0",
|
||||
BinaryName: "myapp",
|
||||
Checksums: ChecksumMap{},
|
||||
}
|
||||
cfg := HomebrewConfig{
|
||||
Official: &OfficialConfig{
|
||||
Enabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
err := p.dryRunPublish(data, cfg)
|
||||
|
||||
w.Close()
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.ReadFrom(r)
|
||||
os.Stdout = oldStdout
|
||||
|
||||
require.NoError(t, err)
|
||||
output := buf.String()
|
||||
assert.Contains(t, output, "Would write files for official PR to: dist/homebrew")
|
||||
})
|
||||
}
|
||||
|
||||
func TestHomebrewPublisher_Publish_Bad(t *testing.T) {
|
||||
p := NewHomebrewPublisher()
|
||||
|
||||
t.Run("fails when tap not configured and not official mode", func(t *testing.T) {
|
||||
release := &Release{
|
||||
Version: "v1.0.0",
|
||||
ProjectDir: "/project",
|
||||
}
|
||||
pubCfg := PublisherConfig{Type: "homebrew"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
|
||||
err := p.Publish(nil, release, pubCfg, relCfg, false)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "tap is required")
|
||||
})
|
||||
}
|
||||
|
||||
func TestHomebrewConfig_Defaults_Good(t *testing.T) {
|
||||
t.Run("has sensible defaults", func(t *testing.T) {
|
||||
p := NewHomebrewPublisher()
|
||||
pubCfg := PublisherConfig{Type: "homebrew"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Tap)
|
||||
assert.Empty(t, cfg.Formula)
|
||||
assert.Nil(t, cfg.Official)
|
||||
})
|
||||
}
|
||||
298
pkg/release/publishers/npm_test.go
Normal file
298
pkg/release/publishers/npm_test.go
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
package publishers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNpmPublisher_Name_Good(t *testing.T) {
|
||||
t.Run("returns npm", func(t *testing.T) {
|
||||
p := NewNpmPublisher()
|
||||
assert.Equal(t, "npm", p.Name())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNpmPublisher_ParseConfig_Good(t *testing.T) {
|
||||
p := NewNpmPublisher()
|
||||
|
||||
t.Run("uses defaults when no extended config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{Type: "npm"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Package)
|
||||
assert.Equal(t, "public", cfg.Access)
|
||||
})
|
||||
|
||||
t.Run("parses package and access from extended config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "npm",
|
||||
Extended: map[string]any{
|
||||
"package": "@myorg/mypackage",
|
||||
"access": "restricted",
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Equal(t, "@myorg/mypackage", cfg.Package)
|
||||
assert.Equal(t, "restricted", cfg.Access)
|
||||
})
|
||||
|
||||
t.Run("keeps default access when not specified", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "npm",
|
||||
Extended: map[string]any{
|
||||
"package": "@myorg/mypackage",
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Equal(t, "@myorg/mypackage", cfg.Package)
|
||||
assert.Equal(t, "public", cfg.Access)
|
||||
})
|
||||
|
||||
t.Run("handles nil extended config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "npm",
|
||||
Extended: nil,
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Package)
|
||||
assert.Equal(t, "public", cfg.Access)
|
||||
})
|
||||
|
||||
t.Run("handles empty strings in config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "npm",
|
||||
Extended: map[string]any{
|
||||
"package": "",
|
||||
"access": "",
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Package)
|
||||
assert.Equal(t, "public", cfg.Access)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNpmPublisher_RenderTemplate_Good(t *testing.T) {
|
||||
p := NewNpmPublisher()
|
||||
|
||||
t.Run("renders package.json template with data", func(t *testing.T) {
|
||||
data := npmTemplateData{
|
||||
Package: "@myorg/mycli",
|
||||
Version: "1.2.3",
|
||||
Description: "My awesome CLI",
|
||||
License: "MIT",
|
||||
Repository: "owner/myapp",
|
||||
BinaryName: "myapp",
|
||||
ProjectName: "myapp",
|
||||
Access: "public",
|
||||
}
|
||||
|
||||
result, err := p.renderTemplate("templates/npm/package.json.tmpl", data)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, result, `"name": "@myorg/mycli"`)
|
||||
assert.Contains(t, result, `"version": "1.2.3"`)
|
||||
assert.Contains(t, result, `"description": "My awesome CLI"`)
|
||||
assert.Contains(t, result, `"license": "MIT"`)
|
||||
assert.Contains(t, result, "owner/myapp")
|
||||
assert.Contains(t, result, `"myapp": "./bin/run.js"`)
|
||||
assert.Contains(t, result, `"access": "public"`)
|
||||
})
|
||||
|
||||
t.Run("renders restricted access correctly", func(t *testing.T) {
|
||||
data := npmTemplateData{
|
||||
Package: "@private/cli",
|
||||
Version: "1.0.0",
|
||||
Description: "Private CLI",
|
||||
License: "MIT",
|
||||
Repository: "org/repo",
|
||||
BinaryName: "cli",
|
||||
ProjectName: "cli",
|
||||
Access: "restricted",
|
||||
}
|
||||
|
||||
result, err := p.renderTemplate("templates/npm/package.json.tmpl", data)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, result, `"access": "restricted"`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNpmPublisher_RenderTemplate_Bad(t *testing.T) {
|
||||
p := NewNpmPublisher()
|
||||
|
||||
t.Run("returns error for non-existent template", func(t *testing.T) {
|
||||
data := npmTemplateData{}
|
||||
_, err := p.renderTemplate("templates/npm/nonexistent.tmpl", data)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to read template")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNpmPublisher_DryRunPublish_Good(t *testing.T) {
|
||||
p := NewNpmPublisher()
|
||||
|
||||
t.Run("outputs expected dry run information", func(t *testing.T) {
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
data := npmTemplateData{
|
||||
Package: "@myorg/mycli",
|
||||
Version: "1.0.0",
|
||||
Access: "public",
|
||||
Repository: "owner/repo",
|
||||
BinaryName: "mycli",
|
||||
Description: "My CLI",
|
||||
}
|
||||
cfg := &NpmConfig{
|
||||
Package: "@myorg/mycli",
|
||||
Access: "public",
|
||||
}
|
||||
|
||||
err := p.dryRunPublish(data, cfg)
|
||||
|
||||
w.Close()
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.ReadFrom(r)
|
||||
os.Stdout = oldStdout
|
||||
|
||||
require.NoError(t, err)
|
||||
output := buf.String()
|
||||
|
||||
assert.Contains(t, output, "DRY RUN: npm Publish")
|
||||
assert.Contains(t, output, "Package: @myorg/mycli")
|
||||
assert.Contains(t, output, "Version: 1.0.0")
|
||||
assert.Contains(t, output, "Access: public")
|
||||
assert.Contains(t, output, "Repository: owner/repo")
|
||||
assert.Contains(t, output, "Binary: mycli")
|
||||
assert.Contains(t, output, "Generated package.json:")
|
||||
assert.Contains(t, output, "Would run: npm publish --access public")
|
||||
assert.Contains(t, output, "END DRY RUN")
|
||||
})
|
||||
|
||||
t.Run("shows restricted access correctly", func(t *testing.T) {
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
data := npmTemplateData{
|
||||
Package: "@private/cli",
|
||||
Version: "2.0.0",
|
||||
Access: "restricted",
|
||||
Repository: "org/repo",
|
||||
BinaryName: "cli",
|
||||
}
|
||||
cfg := &NpmConfig{
|
||||
Package: "@private/cli",
|
||||
Access: "restricted",
|
||||
}
|
||||
|
||||
err := p.dryRunPublish(data, cfg)
|
||||
|
||||
w.Close()
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.ReadFrom(r)
|
||||
os.Stdout = oldStdout
|
||||
|
||||
require.NoError(t, err)
|
||||
output := buf.String()
|
||||
|
||||
assert.Contains(t, output, "Access: restricted")
|
||||
assert.Contains(t, output, "Would run: npm publish --access restricted")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNpmPublisher_Publish_Bad(t *testing.T) {
|
||||
p := NewNpmPublisher()
|
||||
|
||||
t.Run("fails when package name not configured", func(t *testing.T) {
|
||||
release := &Release{
|
||||
Version: "v1.0.0",
|
||||
ProjectDir: "/project",
|
||||
}
|
||||
pubCfg := PublisherConfig{Type: "npm"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
|
||||
err := p.Publish(nil, release, pubCfg, relCfg, false)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "package name is required")
|
||||
})
|
||||
|
||||
t.Run("fails when NPM_TOKEN not set in non-dry-run", func(t *testing.T) {
|
||||
// Ensure NPM_TOKEN is not set
|
||||
oldToken := os.Getenv("NPM_TOKEN")
|
||||
os.Unsetenv("NPM_TOKEN")
|
||||
defer func() {
|
||||
if oldToken != "" {
|
||||
os.Setenv("NPM_TOKEN", oldToken)
|
||||
}
|
||||
}()
|
||||
|
||||
release := &Release{
|
||||
Version: "v1.0.0",
|
||||
ProjectDir: "/project",
|
||||
}
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "npm",
|
||||
Extended: map[string]any{
|
||||
"package": "@test/package",
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
|
||||
err := p.Publish(nil, release, pubCfg, relCfg, false)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "NPM_TOKEN environment variable is required")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNpmConfig_Defaults_Good(t *testing.T) {
|
||||
t.Run("has sensible defaults", func(t *testing.T) {
|
||||
p := NewNpmPublisher()
|
||||
pubCfg := PublisherConfig{Type: "npm"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Package)
|
||||
assert.Equal(t, "public", cfg.Access)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNpmTemplateData_Good(t *testing.T) {
|
||||
t.Run("struct has all expected fields", func(t *testing.T) {
|
||||
data := npmTemplateData{
|
||||
Package: "@myorg/package",
|
||||
Version: "1.0.0",
|
||||
Description: "description",
|
||||
License: "MIT",
|
||||
Repository: "org/repo",
|
||||
BinaryName: "cli",
|
||||
ProjectName: "cli",
|
||||
Access: "public",
|
||||
}
|
||||
|
||||
assert.Equal(t, "@myorg/package", data.Package)
|
||||
assert.Equal(t, "1.0.0", data.Version)
|
||||
assert.Equal(t, "description", data.Description)
|
||||
assert.Equal(t, "MIT", data.License)
|
||||
assert.Equal(t, "org/repo", data.Repository)
|
||||
assert.Equal(t, "cli", data.BinaryName)
|
||||
assert.Equal(t, "cli", data.ProjectName)
|
||||
assert.Equal(t, "public", data.Access)
|
||||
})
|
||||
}
|
||||
307
pkg/release/publishers/scoop_test.go
Normal file
307
pkg/release/publishers/scoop_test.go
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
package publishers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestScoopPublisher_Name_Good(t *testing.T) {
|
||||
t.Run("returns scoop", func(t *testing.T) {
|
||||
p := NewScoopPublisher()
|
||||
assert.Equal(t, "scoop", p.Name())
|
||||
})
|
||||
}
|
||||
|
||||
func TestScoopPublisher_ParseConfig_Good(t *testing.T) {
|
||||
p := NewScoopPublisher()
|
||||
|
||||
t.Run("uses defaults when no extended config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{Type: "scoop"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Bucket)
|
||||
assert.Nil(t, cfg.Official)
|
||||
})
|
||||
|
||||
t.Run("parses bucket from extended config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "scoop",
|
||||
Extended: map[string]any{
|
||||
"bucket": "host-uk/scoop-bucket",
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Equal(t, "host-uk/scoop-bucket", cfg.Bucket)
|
||||
})
|
||||
|
||||
t.Run("parses official config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "scoop",
|
||||
Extended: map[string]any{
|
||||
"official": map[string]any{
|
||||
"enabled": true,
|
||||
"output": "dist/scoop-manifest",
|
||||
},
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
require.NotNil(t, cfg.Official)
|
||||
assert.True(t, cfg.Official.Enabled)
|
||||
assert.Equal(t, "dist/scoop-manifest", cfg.Official.Output)
|
||||
})
|
||||
|
||||
t.Run("handles missing official fields", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "scoop",
|
||||
Extended: map[string]any{
|
||||
"official": map[string]any{},
|
||||
},
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
require.NotNil(t, cfg.Official)
|
||||
assert.False(t, cfg.Official.Enabled)
|
||||
assert.Empty(t, cfg.Official.Output)
|
||||
})
|
||||
|
||||
t.Run("handles nil extended config", func(t *testing.T) {
|
||||
pubCfg := PublisherConfig{
|
||||
Type: "scoop",
|
||||
Extended: nil,
|
||||
}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Bucket)
|
||||
assert.Nil(t, cfg.Official)
|
||||
})
|
||||
}
|
||||
|
||||
func TestScoopPublisher_RenderTemplate_Good(t *testing.T) {
|
||||
p := NewScoopPublisher()
|
||||
|
||||
t.Run("renders manifest template with data", func(t *testing.T) {
|
||||
data := scoopTemplateData{
|
||||
PackageName: "myapp",
|
||||
Description: "My awesome CLI",
|
||||
Repository: "owner/myapp",
|
||||
Version: "1.2.3",
|
||||
License: "MIT",
|
||||
BinaryName: "myapp",
|
||||
Checksums: ChecksumMap{
|
||||
WindowsAmd64: "abc123",
|
||||
WindowsArm64: "def456",
|
||||
},
|
||||
}
|
||||
|
||||
result, err := p.renderTemplate("templates/scoop/manifest.json.tmpl", data)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, result, `"version": "1.2.3"`)
|
||||
assert.Contains(t, result, `"description": "My awesome CLI"`)
|
||||
assert.Contains(t, result, `"homepage": "https://github.com/owner/myapp"`)
|
||||
assert.Contains(t, result, `"license": "MIT"`)
|
||||
assert.Contains(t, result, `"64bit"`)
|
||||
assert.Contains(t, result, `"arm64"`)
|
||||
assert.Contains(t, result, "myapp-windows-amd64.zip")
|
||||
assert.Contains(t, result, "myapp-windows-arm64.zip")
|
||||
assert.Contains(t, result, `"hash": "abc123"`)
|
||||
assert.Contains(t, result, `"hash": "def456"`)
|
||||
assert.Contains(t, result, `"bin": "myapp.exe"`)
|
||||
})
|
||||
|
||||
t.Run("includes autoupdate configuration", func(t *testing.T) {
|
||||
data := scoopTemplateData{
|
||||
PackageName: "tool",
|
||||
Description: "A tool",
|
||||
Repository: "org/tool",
|
||||
Version: "2.0.0",
|
||||
License: "Apache-2.0",
|
||||
BinaryName: "tool",
|
||||
Checksums: ChecksumMap{},
|
||||
}
|
||||
|
||||
result, err := p.renderTemplate("templates/scoop/manifest.json.tmpl", data)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, result, `"checkver"`)
|
||||
assert.Contains(t, result, `"github": "https://github.com/org/tool"`)
|
||||
assert.Contains(t, result, `"autoupdate"`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestScoopPublisher_RenderTemplate_Bad(t *testing.T) {
|
||||
p := NewScoopPublisher()
|
||||
|
||||
t.Run("returns error for non-existent template", func(t *testing.T) {
|
||||
data := scoopTemplateData{}
|
||||
_, err := p.renderTemplate("templates/scoop/nonexistent.tmpl", data)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to read template")
|
||||
})
|
||||
}
|
||||
|
||||
func TestScoopPublisher_DryRunPublish_Good(t *testing.T) {
|
||||
p := NewScoopPublisher()
|
||||
|
||||
t.Run("outputs expected dry run information", func(t *testing.T) {
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
data := scoopTemplateData{
|
||||
PackageName: "myapp",
|
||||
Version: "1.0.0",
|
||||
Repository: "owner/repo",
|
||||
BinaryName: "myapp",
|
||||
Checksums: ChecksumMap{},
|
||||
}
|
||||
cfg := ScoopConfig{
|
||||
Bucket: "owner/scoop-bucket",
|
||||
}
|
||||
|
||||
err := p.dryRunPublish(data, cfg)
|
||||
|
||||
w.Close()
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.ReadFrom(r)
|
||||
os.Stdout = oldStdout
|
||||
|
||||
require.NoError(t, err)
|
||||
output := buf.String()
|
||||
|
||||
assert.Contains(t, output, "DRY RUN: Scoop Publish")
|
||||
assert.Contains(t, output, "Package: myapp")
|
||||
assert.Contains(t, output, "Version: 1.0.0")
|
||||
assert.Contains(t, output, "Bucket: owner/scoop-bucket")
|
||||
assert.Contains(t, output, "Repository: owner/repo")
|
||||
assert.Contains(t, output, "Generated manifest.json:")
|
||||
assert.Contains(t, output, "Would commit to bucket: owner/scoop-bucket")
|
||||
assert.Contains(t, output, "END DRY RUN")
|
||||
})
|
||||
|
||||
t.Run("shows official output path when enabled", func(t *testing.T) {
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
data := scoopTemplateData{
|
||||
PackageName: "myapp",
|
||||
Version: "1.0.0",
|
||||
BinaryName: "myapp",
|
||||
Checksums: ChecksumMap{},
|
||||
}
|
||||
cfg := ScoopConfig{
|
||||
Official: &OfficialConfig{
|
||||
Enabled: true,
|
||||
Output: "custom/scoop/path",
|
||||
},
|
||||
}
|
||||
|
||||
err := p.dryRunPublish(data, cfg)
|
||||
|
||||
w.Close()
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.ReadFrom(r)
|
||||
os.Stdout = oldStdout
|
||||
|
||||
require.NoError(t, err)
|
||||
output := buf.String()
|
||||
assert.Contains(t, output, "Would write files for official PR to: custom/scoop/path")
|
||||
})
|
||||
|
||||
t.Run("uses default official output path when not specified", func(t *testing.T) {
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
data := scoopTemplateData{
|
||||
PackageName: "myapp",
|
||||
Version: "1.0.0",
|
||||
BinaryName: "myapp",
|
||||
Checksums: ChecksumMap{},
|
||||
}
|
||||
cfg := ScoopConfig{
|
||||
Official: &OfficialConfig{
|
||||
Enabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
err := p.dryRunPublish(data, cfg)
|
||||
|
||||
w.Close()
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.ReadFrom(r)
|
||||
os.Stdout = oldStdout
|
||||
|
||||
require.NoError(t, err)
|
||||
output := buf.String()
|
||||
assert.Contains(t, output, "Would write files for official PR to: dist/scoop")
|
||||
})
|
||||
}
|
||||
|
||||
func TestScoopPublisher_Publish_Bad(t *testing.T) {
|
||||
p := NewScoopPublisher()
|
||||
|
||||
t.Run("fails when bucket not configured and not official mode", func(t *testing.T) {
|
||||
release := &Release{
|
||||
Version: "v1.0.0",
|
||||
ProjectDir: "/project",
|
||||
}
|
||||
pubCfg := PublisherConfig{Type: "scoop"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
|
||||
err := p.Publish(nil, release, pubCfg, relCfg, false)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "bucket is required")
|
||||
})
|
||||
}
|
||||
|
||||
func TestScoopConfig_Defaults_Good(t *testing.T) {
|
||||
t.Run("has sensible defaults", func(t *testing.T) {
|
||||
p := NewScoopPublisher()
|
||||
pubCfg := PublisherConfig{Type: "scoop"}
|
||||
relCfg := &mockReleaseConfig{repository: "owner/repo"}
|
||||
|
||||
cfg := p.parseConfig(pubCfg, relCfg)
|
||||
|
||||
assert.Empty(t, cfg.Bucket)
|
||||
assert.Nil(t, cfg.Official)
|
||||
})
|
||||
}
|
||||
|
||||
func TestScoopTemplateData_Good(t *testing.T) {
|
||||
t.Run("struct has all expected fields", func(t *testing.T) {
|
||||
data := scoopTemplateData{
|
||||
PackageName: "myapp",
|
||||
Description: "description",
|
||||
Repository: "org/repo",
|
||||
Version: "1.0.0",
|
||||
License: "MIT",
|
||||
BinaryName: "myapp",
|
||||
Checksums: ChecksumMap{
|
||||
WindowsAmd64: "hash1",
|
||||
WindowsArm64: "hash2",
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, "myapp", data.PackageName)
|
||||
assert.Equal(t, "description", data.Description)
|
||||
assert.Equal(t, "org/repo", data.Repository)
|
||||
assert.Equal(t, "1.0.0", data.Version)
|
||||
assert.Equal(t, "MIT", data.License)
|
||||
assert.Equal(t, "myapp", data.BinaryName)
|
||||
assert.Equal(t, "hash1", data.Checksums.WindowsAmd64)
|
||||
assert.Equal(t, "hash2", data.Checksums.WindowsArm64)
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue