2026-01-29 14:28:23 +00:00
|
|
|
package release
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/host-uk/core/pkg/build"
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestFindArtifacts_Good(t *testing.T) {
|
|
|
|
|
t.Run("finds tar.gz artifacts", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
|
|
|
|
|
// Create test artifact files
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-linux-amd64.tar.gz"), []byte("test"), 0644))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-darwin-arm64.tar.gz"), []byte("test"), 0644))
|
|
|
|
|
|
|
|
|
|
artifacts, err := findArtifacts(distDir)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Len(t, artifacts, 2)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("finds zip artifacts", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-windows-amd64.zip"), []byte("test"), 0644))
|
|
|
|
|
|
|
|
|
|
artifacts, err := findArtifacts(distDir)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Len(t, artifacts, 1)
|
|
|
|
|
assert.Contains(t, artifacts[0].Path, "app-windows-amd64.zip")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("finds checksum files", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "CHECKSUMS.txt"), []byte("checksums"), 0644))
|
|
|
|
|
|
|
|
|
|
artifacts, err := findArtifacts(distDir)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Len(t, artifacts, 1)
|
|
|
|
|
assert.Contains(t, artifacts[0].Path, "CHECKSUMS.txt")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("finds signature files", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz.sig"), []byte("signature"), 0644))
|
|
|
|
|
|
|
|
|
|
artifacts, err := findArtifacts(distDir)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Len(t, artifacts, 1)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("finds mixed artifact types", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-linux.tar.gz"), []byte("test"), 0644))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-windows.zip"), []byte("test"), 0644))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "CHECKSUMS.txt"), []byte("checksums"), 0644))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.sig"), []byte("sig"), 0644))
|
|
|
|
|
|
|
|
|
|
artifacts, err := findArtifacts(distDir)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Len(t, artifacts, 4)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("ignores non-artifact files", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "README.md"), []byte("readme"), 0644))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.exe"), []byte("binary"), 0644))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("artifact"), 0644))
|
|
|
|
|
|
|
|
|
|
artifacts, err := findArtifacts(distDir)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Len(t, artifacts, 1)
|
|
|
|
|
assert.Contains(t, artifacts[0].Path, "app.tar.gz")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("ignores subdirectories", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
require.NoError(t, os.MkdirAll(filepath.Join(distDir, "subdir"), 0755))
|
|
|
|
|
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("artifact"), 0644))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "subdir", "nested.tar.gz"), []byte("nested"), 0644))
|
|
|
|
|
|
|
|
|
|
artifacts, err := findArtifacts(distDir)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Should only find the top-level artifact
|
|
|
|
|
assert.Len(t, artifacts, 1)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("returns empty slice for empty dist directory", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
|
|
|
|
|
artifacts, err := findArtifacts(distDir)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Empty(t, artifacts)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestFindArtifacts_Bad(t *testing.T) {
|
|
|
|
|
t.Run("returns error when dist directory does not exist", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
|
|
|
|
|
_, err := findArtifacts(distDir)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "dist/ directory not found")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("returns error when dist directory is unreadable", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
|
|
|
|
|
// Create a file that looks like dist but will cause ReadDir to fail
|
|
|
|
|
// by making the directory unreadable
|
|
|
|
|
require.NoError(t, os.Chmod(distDir, 0000))
|
|
|
|
|
defer func() { _ = os.Chmod(distDir, 0755) }()
|
|
|
|
|
|
|
|
|
|
_, err := findArtifacts(distDir)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "failed to read dist/")
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetBuilder_Good(t *testing.T) {
|
|
|
|
|
t.Run("returns Go builder for go project type", func(t *testing.T) {
|
|
|
|
|
builder, err := getBuilder(build.ProjectTypeGo)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.NotNil(t, builder)
|
|
|
|
|
assert.Equal(t, "go", builder.Name())
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("returns Wails builder for wails project type", func(t *testing.T) {
|
|
|
|
|
builder, err := getBuilder(build.ProjectTypeWails)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.NotNil(t, builder)
|
|
|
|
|
assert.Equal(t, "wails", builder.Name())
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetBuilder_Bad(t *testing.T) {
|
|
|
|
|
t.Run("returns error for Node project type", func(t *testing.T) {
|
|
|
|
|
_, err := getBuilder(build.ProjectTypeNode)
|
|
|
|
|
assert.Error(t, err)
|
feat: infrastructure packages and lint cleanup (#281)
* ci: consolidate duplicate workflows and merge CodeQL configs
Remove 17 duplicate workflow files that were split copies of the
combined originals. Each family (CI, CodeQL, Coverage, PR Build,
Alpha Release) had the same job duplicated across separate
push/pull_request/schedule/manual trigger files.
Merge codeql.yml and codescan.yml into a single codeql.yml with
a language matrix covering go, javascript-typescript, python,
and actions — matching the previous default setup coverage.
Remaining workflows (one per family):
- ci.yml (push + PR + manual)
- codeql.yml (push + PR + schedule, all languages)
- coverage.yml (push + PR + manual)
- alpha-release.yml (push + manual)
- pr-build.yml (PR + manual)
- release.yml (tag push)
- agent-verify.yml, auto-label.yml, auto-project.yml
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add collect, config, crypt, plugin packages and fix all lint issues
Add four new infrastructure packages with CLI commands:
- pkg/config: layered configuration (defaults → file → env → flags)
- pkg/crypt: crypto primitives (Argon2id, AES-GCM, ChaCha20, HMAC, checksums)
- pkg/plugin: plugin system with GitHub-based install/update/remove
- pkg/collect: collection subsystem (GitHub, BitcoinTalk, market, papers, excavate)
Fix all golangci-lint issues across the entire codebase (~100 errcheck,
staticcheck SA1012/SA1019/ST1005, unused, ineffassign fixes) so that
`core go qa` passes with 0 issues.
Closes #167, #168, #170, #250, #251, #252, #253, #254, #255, #256
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:34:43 +00:00
|
|
|
assert.Contains(t, err.Error(), "node.js builder not yet implemented")
|
2026-01-29 14:28:23 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("returns error for PHP project type", func(t *testing.T) {
|
|
|
|
|
_, err := getBuilder(build.ProjectTypePHP)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "PHP builder not yet implemented")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("returns error for unsupported project type", func(t *testing.T) {
|
|
|
|
|
_, err := getBuilder(build.ProjectType("unknown"))
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "unsupported project type")
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetPublisher_Good(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
pubType string
|
|
|
|
|
expectedName string
|
|
|
|
|
}{
|
|
|
|
|
{"github", "github"},
|
|
|
|
|
{"linuxkit", "linuxkit"},
|
|
|
|
|
{"docker", "docker"},
|
|
|
|
|
{"npm", "npm"},
|
|
|
|
|
{"homebrew", "homebrew"},
|
|
|
|
|
{"scoop", "scoop"},
|
|
|
|
|
{"aur", "aur"},
|
|
|
|
|
{"chocolatey", "chocolatey"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tc := range tests {
|
|
|
|
|
t.Run(tc.pubType, func(t *testing.T) {
|
|
|
|
|
publisher, err := getPublisher(tc.pubType)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.NotNil(t, publisher)
|
|
|
|
|
assert.Equal(t, tc.expectedName, publisher.Name())
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetPublisher_Bad(t *testing.T) {
|
|
|
|
|
t.Run("returns error for unsupported publisher type", func(t *testing.T) {
|
|
|
|
|
_, err := getPublisher("unsupported")
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "unsupported publisher type: unsupported")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("returns error for empty publisher type", func(t *testing.T) {
|
|
|
|
|
_, err := getPublisher("")
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "unsupported publisher type")
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBuildExtendedConfig_Good(t *testing.T) {
|
|
|
|
|
t.Run("returns empty map for minimal config", func(t *testing.T) {
|
|
|
|
|
cfg := PublisherConfig{
|
|
|
|
|
Type: "github",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ext := buildExtendedConfig(cfg)
|
|
|
|
|
assert.Empty(t, ext)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("includes LinuxKit config", func(t *testing.T) {
|
|
|
|
|
cfg := PublisherConfig{
|
|
|
|
|
Type: "linuxkit",
|
|
|
|
|
Config: "linuxkit.yaml",
|
|
|
|
|
Formats: []string{"iso", "qcow2"},
|
|
|
|
|
Platforms: []string{"linux/amd64", "linux/arm64"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ext := buildExtendedConfig(cfg)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "linuxkit.yaml", ext["config"])
|
|
|
|
|
assert.Equal(t, []any{"iso", "qcow2"}, ext["formats"])
|
|
|
|
|
assert.Equal(t, []any{"linux/amd64", "linux/arm64"}, ext["platforms"])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("includes Docker config", func(t *testing.T) {
|
|
|
|
|
cfg := PublisherConfig{
|
|
|
|
|
Type: "docker",
|
|
|
|
|
Registry: "ghcr.io",
|
|
|
|
|
Image: "owner/repo",
|
|
|
|
|
Dockerfile: "Dockerfile.prod",
|
|
|
|
|
Tags: []string{"latest", "v1.0.0"},
|
|
|
|
|
BuildArgs: map[string]string{"VERSION": "1.0.0"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ext := buildExtendedConfig(cfg)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "ghcr.io", ext["registry"])
|
|
|
|
|
assert.Equal(t, "owner/repo", ext["image"])
|
|
|
|
|
assert.Equal(t, "Dockerfile.prod", ext["dockerfile"])
|
|
|
|
|
assert.Equal(t, []any{"latest", "v1.0.0"}, ext["tags"])
|
|
|
|
|
buildArgs := ext["build_args"].(map[string]any)
|
|
|
|
|
assert.Equal(t, "1.0.0", buildArgs["VERSION"])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("includes npm config", func(t *testing.T) {
|
|
|
|
|
cfg := PublisherConfig{
|
|
|
|
|
Type: "npm",
|
|
|
|
|
Package: "@host-uk/core",
|
|
|
|
|
Access: "public",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ext := buildExtendedConfig(cfg)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "@host-uk/core", ext["package"])
|
|
|
|
|
assert.Equal(t, "public", ext["access"])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("includes Homebrew config", func(t *testing.T) {
|
|
|
|
|
cfg := PublisherConfig{
|
|
|
|
|
Type: "homebrew",
|
|
|
|
|
Tap: "host-uk/tap",
|
|
|
|
|
Formula: "core",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ext := buildExtendedConfig(cfg)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "host-uk/tap", ext["tap"])
|
|
|
|
|
assert.Equal(t, "core", ext["formula"])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("includes Scoop config", func(t *testing.T) {
|
|
|
|
|
cfg := PublisherConfig{
|
|
|
|
|
Type: "scoop",
|
|
|
|
|
Bucket: "host-uk/bucket",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ext := buildExtendedConfig(cfg)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "host-uk/bucket", ext["bucket"])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("includes AUR config", func(t *testing.T) {
|
|
|
|
|
cfg := PublisherConfig{
|
|
|
|
|
Type: "aur",
|
|
|
|
|
Maintainer: "John Doe <john@example.com>",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ext := buildExtendedConfig(cfg)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "John Doe <john@example.com>", ext["maintainer"])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("includes Chocolatey config", func(t *testing.T) {
|
|
|
|
|
cfg := PublisherConfig{
|
|
|
|
|
Type: "chocolatey",
|
|
|
|
|
Push: true,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ext := buildExtendedConfig(cfg)
|
|
|
|
|
|
|
|
|
|
assert.True(t, ext["push"].(bool))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("includes Official config", func(t *testing.T) {
|
|
|
|
|
cfg := PublisherConfig{
|
|
|
|
|
Type: "homebrew",
|
|
|
|
|
Official: &OfficialConfig{
|
|
|
|
|
Enabled: true,
|
|
|
|
|
Output: "/path/to/output",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ext := buildExtendedConfig(cfg)
|
|
|
|
|
|
|
|
|
|
official := ext["official"].(map[string]any)
|
|
|
|
|
assert.True(t, official["enabled"].(bool))
|
|
|
|
|
assert.Equal(t, "/path/to/output", official["output"])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("Official config without output", func(t *testing.T) {
|
|
|
|
|
cfg := PublisherConfig{
|
|
|
|
|
Type: "scoop",
|
|
|
|
|
Official: &OfficialConfig{
|
|
|
|
|
Enabled: true,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ext := buildExtendedConfig(cfg)
|
|
|
|
|
|
|
|
|
|
official := ext["official"].(map[string]any)
|
|
|
|
|
assert.True(t, official["enabled"].(bool))
|
|
|
|
|
_, hasOutput := official["output"]
|
|
|
|
|
assert.False(t, hasOutput)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestToAnySlice_Good(t *testing.T) {
|
|
|
|
|
t.Run("converts string slice to any slice", func(t *testing.T) {
|
|
|
|
|
input := []string{"a", "b", "c"}
|
|
|
|
|
|
|
|
|
|
result := toAnySlice(input)
|
|
|
|
|
|
|
|
|
|
assert.Len(t, result, 3)
|
|
|
|
|
assert.Equal(t, "a", result[0])
|
|
|
|
|
assert.Equal(t, "b", result[1])
|
|
|
|
|
assert.Equal(t, "c", result[2])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("handles empty slice", func(t *testing.T) {
|
|
|
|
|
input := []string{}
|
|
|
|
|
|
|
|
|
|
result := toAnySlice(input)
|
|
|
|
|
|
|
|
|
|
assert.Empty(t, result)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("handles single element", func(t *testing.T) {
|
|
|
|
|
input := []string{"only"}
|
|
|
|
|
|
|
|
|
|
result := toAnySlice(input)
|
|
|
|
|
|
|
|
|
|
assert.Len(t, result, 1)
|
|
|
|
|
assert.Equal(t, "only", result[0])
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPublish_Good(t *testing.T) {
|
|
|
|
|
t.Run("returns release with version from config", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
|
|
|
|
|
|
|
|
|
|
cfg := DefaultConfig()
|
|
|
|
|
cfg.SetProjectDir(dir)
|
|
|
|
|
cfg.SetVersion("v1.0.0")
|
|
|
|
|
cfg.Publishers = nil // No publishers to avoid network calls
|
|
|
|
|
|
|
|
|
|
release, err := Publish(context.Background(), cfg, true)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "v1.0.0", release.Version)
|
|
|
|
|
assert.Len(t, release.Artifacts, 1)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("finds artifacts in dist directory", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-linux.tar.gz"), []byte("test"), 0644))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app-darwin.tar.gz"), []byte("test"), 0644))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "CHECKSUMS.txt"), []byte("checksums"), 0644))
|
|
|
|
|
|
|
|
|
|
cfg := DefaultConfig()
|
|
|
|
|
cfg.SetProjectDir(dir)
|
|
|
|
|
cfg.SetVersion("v1.0.0")
|
|
|
|
|
cfg.Publishers = nil
|
|
|
|
|
|
|
|
|
|
release, err := Publish(context.Background(), cfg, true)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Len(t, release.Artifacts, 3)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPublish_Bad(t *testing.T) {
|
|
|
|
|
t.Run("returns error when config is nil", func(t *testing.T) {
|
|
|
|
|
_, err := Publish(context.Background(), nil, true)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "config is nil")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("returns error when dist directory missing", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
|
|
|
|
|
cfg := DefaultConfig()
|
|
|
|
|
cfg.SetProjectDir(dir)
|
|
|
|
|
cfg.SetVersion("v1.0.0")
|
|
|
|
|
|
|
|
|
|
_, err := Publish(context.Background(), cfg, true)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "dist/ directory not found")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("returns error when no artifacts found", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
|
|
|
|
|
cfg := DefaultConfig()
|
|
|
|
|
cfg.SetProjectDir(dir)
|
|
|
|
|
cfg.SetVersion("v1.0.0")
|
|
|
|
|
|
|
|
|
|
_, err := Publish(context.Background(), cfg, true)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "no artifacts found")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("returns error for unsupported publisher", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
|
|
|
|
|
|
|
|
|
|
cfg := DefaultConfig()
|
|
|
|
|
cfg.SetProjectDir(dir)
|
|
|
|
|
cfg.SetVersion("v1.0.0")
|
|
|
|
|
cfg.Publishers = []PublisherConfig{
|
|
|
|
|
{Type: "unsupported"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err := Publish(context.Background(), cfg, true)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "unsupported publisher type")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("returns error when version determination fails in non-git dir", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
|
|
|
|
|
|
|
|
|
|
cfg := DefaultConfig()
|
|
|
|
|
cfg.SetProjectDir(dir)
|
|
|
|
|
// Don't set version - let it try to determine from git
|
|
|
|
|
cfg.Publishers = nil
|
|
|
|
|
|
|
|
|
|
// In a non-git directory, DetermineVersion returns v0.0.1 as default
|
|
|
|
|
// so we verify that the publish proceeds without error
|
|
|
|
|
release, err := Publish(context.Background(), cfg, true)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "v0.0.1", release.Version)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRun_Good(t *testing.T) {
|
|
|
|
|
t.Run("returns release with version from config", func(t *testing.T) {
|
|
|
|
|
// Create a minimal Go project for testing
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
|
|
|
|
|
// Create go.mod
|
|
|
|
|
goMod := `module testapp
|
|
|
|
|
|
|
|
|
|
go 1.21
|
|
|
|
|
`
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0644))
|
|
|
|
|
|
|
|
|
|
// Create main.go
|
|
|
|
|
mainGo := `package main
|
|
|
|
|
|
|
|
|
|
func main() {}
|
|
|
|
|
`
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(dir, "main.go"), []byte(mainGo), 0644))
|
|
|
|
|
|
|
|
|
|
cfg := DefaultConfig()
|
|
|
|
|
cfg.SetProjectDir(dir)
|
|
|
|
|
cfg.SetVersion("v1.0.0")
|
|
|
|
|
cfg.Project.Name = "testapp"
|
|
|
|
|
cfg.Build.Targets = []TargetConfig{} // Empty targets to use defaults
|
|
|
|
|
cfg.Publishers = nil // No publishers to avoid network calls
|
|
|
|
|
|
|
|
|
|
// Note: This test will actually try to build, which may fail in CI
|
|
|
|
|
// So we just test that the function accepts the config properly
|
|
|
|
|
release, err := Run(context.Background(), cfg, true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Build might fail in test environment, but we still verify the error message
|
|
|
|
|
assert.Contains(t, err.Error(), "build")
|
|
|
|
|
} else {
|
|
|
|
|
assert.Equal(t, "v1.0.0", release.Version)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRun_Bad(t *testing.T) {
|
|
|
|
|
t.Run("returns error when config is nil", func(t *testing.T) {
|
|
|
|
|
_, err := Run(context.Background(), nil, true)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Contains(t, err.Error(), "config is nil")
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRelease_Structure(t *testing.T) {
|
|
|
|
|
t.Run("Release struct holds expected fields", func(t *testing.T) {
|
|
|
|
|
release := &Release{
|
|
|
|
|
Version: "v1.0.0",
|
|
|
|
|
Artifacts: []build.Artifact{{Path: "/path/to/artifact"}},
|
|
|
|
|
Changelog: "## v1.0.0\n\nChanges",
|
|
|
|
|
ProjectDir: "/project",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "v1.0.0", release.Version)
|
|
|
|
|
assert.Len(t, release.Artifacts, 1)
|
|
|
|
|
assert.Contains(t, release.Changelog, "v1.0.0")
|
|
|
|
|
assert.Equal(t, "/project", release.ProjectDir)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPublish_VersionFromGit(t *testing.T) {
|
|
|
|
|
t.Run("determines version from git when not set", func(t *testing.T) {
|
|
|
|
|
dir := setupPublishGitRepo(t)
|
|
|
|
|
createPublishCommit(t, dir, "feat: initial commit")
|
|
|
|
|
createPublishTag(t, dir, "v1.2.3")
|
|
|
|
|
|
|
|
|
|
// Create dist directory with artifact
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
|
|
|
|
|
|
|
|
|
|
cfg := DefaultConfig()
|
|
|
|
|
cfg.SetProjectDir(dir)
|
|
|
|
|
// Don't set version - let it be determined from git
|
|
|
|
|
cfg.Publishers = nil
|
|
|
|
|
|
|
|
|
|
release, err := Publish(context.Background(), cfg, true)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, "v1.2.3", release.Version)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPublish_ChangelogGeneration(t *testing.T) {
|
|
|
|
|
t.Run("generates changelog from git commits when available", func(t *testing.T) {
|
|
|
|
|
dir := setupPublishGitRepo(t)
|
|
|
|
|
createPublishCommit(t, dir, "feat: add feature")
|
|
|
|
|
createPublishTag(t, dir, "v1.0.0")
|
|
|
|
|
createPublishCommit(t, dir, "fix: fix bug")
|
|
|
|
|
createPublishTag(t, dir, "v1.0.1")
|
|
|
|
|
|
|
|
|
|
// Create dist directory with artifact
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
|
|
|
|
|
|
|
|
|
|
cfg := DefaultConfig()
|
|
|
|
|
cfg.SetProjectDir(dir)
|
|
|
|
|
cfg.SetVersion("v1.0.1")
|
|
|
|
|
cfg.Publishers = nil
|
|
|
|
|
|
|
|
|
|
release, err := Publish(context.Background(), cfg, true)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Changelog should contain either the commit message or the version
|
|
|
|
|
assert.Contains(t, release.Changelog, "v1.0.1")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("uses fallback changelog on error", func(t *testing.T) {
|
|
|
|
|
dir := t.TempDir() // Not a git repo
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
|
|
|
|
|
|
|
|
|
|
cfg := DefaultConfig()
|
|
|
|
|
cfg.SetProjectDir(dir)
|
|
|
|
|
cfg.SetVersion("v1.0.0")
|
|
|
|
|
cfg.Publishers = nil
|
|
|
|
|
|
|
|
|
|
release, err := Publish(context.Background(), cfg, true)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Should use fallback changelog
|
|
|
|
|
assert.Contains(t, release.Changelog, "Release v1.0.0")
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPublish_DefaultProjectDir(t *testing.T) {
|
|
|
|
|
t.Run("uses current directory when projectDir is empty", func(t *testing.T) {
|
|
|
|
|
// Create artifacts in current directory's dist folder
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
distDir := filepath.Join(dir, "dist")
|
|
|
|
|
require.NoError(t, os.MkdirAll(distDir, 0755))
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(distDir, "app.tar.gz"), []byte("test"), 0644))
|
|
|
|
|
|
|
|
|
|
cfg := DefaultConfig()
|
|
|
|
|
cfg.SetProjectDir(dir)
|
|
|
|
|
cfg.SetVersion("v1.0.0")
|
|
|
|
|
cfg.Publishers = nil
|
|
|
|
|
|
|
|
|
|
release, err := Publish(context.Background(), cfg, true)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assert.NotEmpty(t, release.ProjectDir)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper functions for publish tests
|
|
|
|
|
func setupPublishGitRepo(t *testing.T) string {
|
|
|
|
|
t.Helper()
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
|
|
|
|
|
cmd := exec.Command("git", "init")
|
|
|
|
|
cmd.Dir = dir
|
|
|
|
|
require.NoError(t, cmd.Run())
|
|
|
|
|
|
|
|
|
|
cmd = exec.Command("git", "config", "user.email", "test@example.com")
|
|
|
|
|
cmd.Dir = dir
|
|
|
|
|
require.NoError(t, cmd.Run())
|
|
|
|
|
|
|
|
|
|
cmd = exec.Command("git", "config", "user.name", "Test User")
|
|
|
|
|
cmd.Dir = dir
|
|
|
|
|
require.NoError(t, cmd.Run())
|
|
|
|
|
|
|
|
|
|
return dir
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func createPublishCommit(t *testing.T, dir, message string) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
|
|
filePath := filepath.Join(dir, "publish_test.txt")
|
|
|
|
|
content, _ := os.ReadFile(filePath)
|
|
|
|
|
content = append(content, []byte(message+"\n")...)
|
|
|
|
|
require.NoError(t, os.WriteFile(filePath, content, 0644))
|
|
|
|
|
|
|
|
|
|
cmd := exec.Command("git", "add", ".")
|
|
|
|
|
cmd.Dir = dir
|
|
|
|
|
require.NoError(t, cmd.Run())
|
|
|
|
|
|
|
|
|
|
cmd = exec.Command("git", "commit", "-m", message)
|
|
|
|
|
cmd.Dir = dir
|
|
|
|
|
require.NoError(t, cmd.Run())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func createPublishTag(t *testing.T, dir, tag string) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
cmd := exec.Command("git", "tag", tag)
|
|
|
|
|
cmd.Dir = dir
|
|
|
|
|
require.NoError(t, cmd.Run())
|
|
|
|
|
}
|