314 lines
9.6 KiB
Go
314 lines
9.6 KiB
Go
package vociferate
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type stubFileSystem struct {
|
|
files map[string][]byte
|
|
}
|
|
|
|
func newStubFileSystem(files map[string]string) *stubFileSystem {
|
|
backing := make(map[string][]byte, len(files))
|
|
for path, contents := range files {
|
|
backing[path] = []byte(contents)
|
|
}
|
|
return &stubFileSystem{files: backing}
|
|
}
|
|
|
|
func (fs *stubFileSystem) ReadFile(path string) ([]byte, error) {
|
|
contents, ok := fs.files[path]
|
|
if !ok {
|
|
return nil, os.ErrNotExist
|
|
}
|
|
clone := make([]byte, len(contents))
|
|
copy(clone, contents)
|
|
return clone, nil
|
|
}
|
|
|
|
func (fs *stubFileSystem) WriteFile(path string, data []byte, _ os.FileMode) error {
|
|
clone := make([]byte, len(data))
|
|
copy(clone, data)
|
|
fs.files[path] = clone
|
|
return nil
|
|
}
|
|
|
|
type stubEnvironment map[string]string
|
|
|
|
func (env stubEnvironment) Getenv(key string) string {
|
|
return env[key]
|
|
}
|
|
|
|
type stubGitRunner struct {
|
|
commit string
|
|
ok bool
|
|
}
|
|
|
|
func (runner stubGitRunner) FirstCommitShortHash(_ string) (string, bool) {
|
|
return runner.commit, runner.ok
|
|
}
|
|
|
|
func TestNormalizeRepoURL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
remoteURL string
|
|
wantURL string
|
|
wantOK bool
|
|
}{
|
|
{name: "https", remoteURL: "https://git.hrafn.xyz/aether/vociferate.git", wantURL: "https://git.hrafn.xyz/aether/vociferate", wantOK: true},
|
|
{name: "https trailing slash", remoteURL: "https://git.hrafn.xyz/aether/vociferate/", wantURL: "https://git.hrafn.xyz/aether/vociferate", wantOK: true},
|
|
{name: "http", remoteURL: "http://teapot:3000/aether/vociferate.git", wantURL: "http://teapot:3000/aether/vociferate", wantOK: true},
|
|
{name: "http trailing slash", remoteURL: "http://teapot:3000/aether/vociferate/", wantURL: "http://teapot:3000/aether/vociferate", wantOK: true},
|
|
{name: "ssh with scheme", remoteURL: "ssh://git@git.hrafn.xyz/aether/vociferate.git", wantURL: "https://git.hrafn.xyz/aether/vociferate", wantOK: true},
|
|
{name: "scp style", remoteURL: "git@git.hrafn.xyz:aether/vociferate.git", wantURL: "https://git.hrafn.xyz/aether/vociferate", wantOK: true},
|
|
{name: "empty", remoteURL: "", wantURL: "", wantOK: false},
|
|
{name: "unsupported scheme", remoteURL: "ftp://example.com/repo.git", wantURL: "", wantOK: false},
|
|
{name: "invalid ssh missing user", remoteURL: "ssh://git.hrafn.xyz/aether/vociferate.git", wantURL: "", wantOK: false},
|
|
{name: "invalid scp style", remoteURL: "git.hrafn.xyz:aether/vociferate.git", wantURL: "", wantOK: false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
gotURL, gotOK := normalizeRepoURL(tt.remoteURL)
|
|
if gotOK != tt.wantOK {
|
|
t.Fatalf("normalizeRepoURL(%q) ok = %v, want %v", tt.remoteURL, gotOK, tt.wantOK)
|
|
}
|
|
if gotURL != tt.wantURL {
|
|
t.Fatalf("normalizeRepoURL(%q) url = %q, want %q", tt.remoteURL, gotURL, tt.wantURL)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseSemver(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
want semver
|
|
wantErr bool
|
|
}{
|
|
{name: "valid", input: "1.2.3", want: semver{major: 1, minor: 2, patch: 3}, wantErr: false},
|
|
{name: "missing part", input: "1.2", wantErr: true},
|
|
{name: "non numeric", input: "1.two.3", wantErr: true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
got, err := parseSemver(tt.input)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Fatalf("parseSemver(%q) expected error", tt.input)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("parseSemver(%q) unexpected error: %v", tt.input, err)
|
|
}
|
|
if got != tt.want {
|
|
t.Fatalf("parseSemver(%q) = %+v, want %+v", tt.input, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOriginRemoteURLFromGitConfig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("origin exists", func(t *testing.T) {
|
|
t.Parallel()
|
|
config := "[core]\n\trepositoryformatversion = 0\n[remote \"origin\"]\n\turl = git@git.hrafn.xyz:aether/vociferate.git\n"
|
|
url, ok := originRemoteURLFromGitConfig(config)
|
|
if !ok {
|
|
t.Fatal("expected origin url to be found")
|
|
}
|
|
if url != "git@git.hrafn.xyz:aether/vociferate.git" {
|
|
t.Fatalf("unexpected url: %q", url)
|
|
}
|
|
})
|
|
|
|
t.Run("origin missing", func(t *testing.T) {
|
|
t.Parallel()
|
|
config := "[core]\n\trepositoryformatversion = 0\n[remote \"upstream\"]\n\turl = git@git.hrafn.xyz:aether/vociferate.git\n"
|
|
_, ok := originRemoteURLFromGitConfig(config)
|
|
if ok {
|
|
t.Fatal("expected origin url to be absent")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestDeriveRepositoryURLFromGitConfigFallback(t *testing.T) {
|
|
t.Setenv("GITHUB_SERVER_URL", "")
|
|
t.Setenv("GITHUB_REPOSITORY", "")
|
|
|
|
root := t.TempDir()
|
|
configPath := filepath.Join(root, ".git", "config")
|
|
if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
|
|
t.Fatalf("mkdir .git: %v", err)
|
|
}
|
|
if err := os.WriteFile(configPath, []byte("[remote \"origin\"]\n\turl = git@git.hrafn.xyz:aether/vociferate.git\n"), 0o644); err != nil {
|
|
t.Fatalf("write git config: %v", err)
|
|
}
|
|
|
|
url, ok := deriveRepositoryURL(root)
|
|
if !ok {
|
|
t.Fatal("expected repository URL from git config")
|
|
}
|
|
if url != "https://git.hrafn.xyz/aether/vociferate" {
|
|
t.Fatalf("unexpected repository URL: %q", url)
|
|
}
|
|
}
|
|
|
|
func TestDeriveRepositoryURL_UsesOverrideAsHighestPriority(t *testing.T) {
|
|
t.Setenv("VOCIFERATE_REPOSITORY_URL", "https://git.hrafn.xyz/git")
|
|
t.Setenv("GITHUB_SERVER_URL", "http://teapot:3000")
|
|
t.Setenv("GITHUB_REPOSITORY", "aether/vociferate")
|
|
|
|
root := t.TempDir()
|
|
configPath := filepath.Join(root, ".git", "config")
|
|
if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
|
|
t.Fatalf("mkdir .git: %v", err)
|
|
}
|
|
if err := os.WriteFile(configPath, []byte("[remote \"origin\"]\n\turl = git@different.host:org/other.git\n"), 0o644); err != nil {
|
|
t.Fatalf("write git config: %v", err)
|
|
}
|
|
|
|
url, ok := deriveRepositoryURL(root)
|
|
if !ok {
|
|
t.Fatal("expected repository URL from override")
|
|
}
|
|
if url != "https://git.hrafn.xyz/git/aether/vociferate" {
|
|
t.Fatalf("unexpected repository URL: %q", url)
|
|
}
|
|
}
|
|
|
|
func TestResolveOptions_UsesUppercaseChangelogDefault(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
resolved, err := resolveOptions(Options{})
|
|
if err != nil {
|
|
t.Fatalf("resolveOptions returned unexpected error: %v", err)
|
|
}
|
|
|
|
if resolved.Changelog != "CHANGELOG.md" {
|
|
t.Fatalf("resolved changelog = %q, want %q", resolved.Changelog, "CHANGELOG.md")
|
|
}
|
|
}
|
|
|
|
func TestNewService_ValidatesDependencies(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
validFS := newStubFileSystem(nil)
|
|
validEnv := stubEnvironment{}
|
|
validGit := stubGitRunner{}
|
|
|
|
tests := []struct {
|
|
name string
|
|
deps Dependencies
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "missing file system",
|
|
deps: Dependencies{Environment: validEnv, Git: validGit},
|
|
wantErr: "file system dependency must not be nil",
|
|
},
|
|
{
|
|
name: "missing environment",
|
|
deps: Dependencies{FileSystem: validFS, Git: validGit},
|
|
wantErr: "environment dependency must not be nil",
|
|
},
|
|
{
|
|
name: "missing git runner",
|
|
deps: Dependencies{FileSystem: validFS, Environment: validEnv},
|
|
wantErr: "git runner dependency must not be nil",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
_, err := NewService(tt.deps)
|
|
if err == nil || err.Error() != tt.wantErr {
|
|
t.Fatalf("NewService() error = %v, want %q", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestServicePrepare_UsesInjectedEnvironmentForRepositoryLinks(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rootDir := "/repo"
|
|
fs := newStubFileSystem(map[string]string{
|
|
filepath.Join(rootDir, "release-version"): "1.1.6\n",
|
|
filepath.Join(rootDir, "CHANGELOG.md"): "# Changelog\n\n## [Unreleased]\n\n### Added\n\n- New thing.\n\n## [1.1.6] - 2017-12-20\n\n### Fixed\n\n- Historical note.\n",
|
|
})
|
|
|
|
svc, err := NewService(Dependencies{
|
|
FileSystem: fs,
|
|
Environment: stubEnvironment{"GITHUB_SERVER_URL": "https://git.hrafn.xyz", "GITHUB_REPOSITORY": "aether/vociferate"},
|
|
Git: stubGitRunner{commit: "deadbee", ok: true},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewService() unexpected error: %v", err)
|
|
}
|
|
|
|
err = svc.Prepare(rootDir, "1.1.7", "2026-03-21", Options{})
|
|
if err != nil {
|
|
t.Fatalf("Prepare() unexpected error: %v", err)
|
|
}
|
|
|
|
updated := string(fs.files[filepath.Join(rootDir, "CHANGELOG.md")])
|
|
if !contains(updated, "[Unreleased]: https://git.hrafn.xyz/aether/vociferate/compare/v1.1.7...main") {
|
|
t.Fatalf("Prepare() changelog missing injected environment link:\n%s", updated)
|
|
}
|
|
if !contains(updated, "[1.1.7]: https://git.hrafn.xyz/aether/vociferate/compare/v1.1.6...v1.1.7") {
|
|
t.Fatalf("Prepare() changelog missing injected release link:\n%s", updated)
|
|
}
|
|
}
|
|
|
|
func TestServicePrepare_UsesInjectedGitRunnerForFirstCommitLink(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rootDir := "/repo"
|
|
fs := newStubFileSystem(map[string]string{
|
|
filepath.Join(rootDir, "release-version"): "1.1.6\n",
|
|
filepath.Join(rootDir, "CHANGELOG.md"): "# Changelog\n\n## [Unreleased]\n\n### Added\n\n- New thing.\n\n## [1.1.6] - 2017-12-20\n\n### Fixed\n\n- Historical note.\n",
|
|
filepath.Join(rootDir, ".git", "config"): "[remote \"origin\"]\n\turl = git@git.hrafn.xyz:aether/vociferate.git\n",
|
|
})
|
|
|
|
svc, err := NewService(Dependencies{
|
|
FileSystem: fs,
|
|
Environment: stubEnvironment{},
|
|
Git: stubGitRunner{commit: "abc1234", ok: true},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewService() unexpected error: %v", err)
|
|
}
|
|
|
|
err = svc.Prepare(rootDir, "1.1.7", "2026-03-21", Options{})
|
|
if err != nil {
|
|
t.Fatalf("Prepare() unexpected error: %v", err)
|
|
}
|
|
|
|
updated := string(fs.files[filepath.Join(rootDir, "CHANGELOG.md")])
|
|
if !contains(updated, "[1.1.6]: https://git.hrafn.xyz/aether/vociferate/compare/abc1234...v1.1.6") {
|
|
t.Fatalf("Prepare() changelog missing injected git link:\n%s", updated)
|
|
}
|
|
}
|
|
|
|
func contains(text, fragment string) bool {
|
|
return len(fragment) > 0 && strings.Contains(text, fragment)
|
|
}
|