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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 0 additions & 26 deletions internal/changelog/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,29 +158,3 @@ func (r *graphQLResponse) UnmarshalJSON(data []byte) error {

return nil
}

// LookupConventionalCommitUsernames looks up GitHub usernames for conventional commits
func LookupConventionalCommitUsernames(commits []ConventionalCommit, repo string) []ConventionalCommit {
if repo == "" || len(commits) == 0 {
return commits
}

// Convert to git.Commit for batch lookup
gitCommits := make([]git.Commit, len(commits))
for i, c := range commits {
gitCommits[i] = git.Commit{
Hash: c.FullHash,
AuthorEmail: c.AuthorEmail,
}
}

// Perform batch lookup
gitCommits = LookupGitHubUsernames(gitCommits, repo)

// Copy usernames back
for i := range commits {
commits[i].GitHubUsername = gitCommits[i].GitHubUsername
}

return commits
}
14 changes: 0 additions & 14 deletions internal/changelog/github_coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,6 @@ func TestUnmarshalJSON_TopLevelError(t *testing.T) {
assert.Error(t, err)
}

func TestLookupConventionalCommitUsernames_WithCommits(t *testing.T) {
// Verify the conversion loop and the username copy-back loop both execute.
// The gh CLI call will fail in the test environment; the important thing is
// that the function does not panic and returns the same number of commits.
commits := []ConventionalCommit{
{FullHash: "abc1234567", AuthorEmail: "alice@example.com", Description: "first change"},
{FullHash: "def1234567", AuthorEmail: "bob@example.com", Description: "second change"},
}
result := LookupConventionalCommitUsernames(commits, "owner/repo")
require.Len(t, result, 2)
assert.Equal(t, "first change", result[0].Description)
assert.Equal(t, "second change", result[1].Description)
}

func TestLookupGitHubUsernames_InvalidRepoFormat(t *testing.T) {
// A repo without a slash propagates to lookupAuthorsBatch which returns empty,
// so no commit gets a username.
Expand Down
35 changes: 0 additions & 35 deletions internal/changelog/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,41 +157,6 @@ func TestGraphQLResponseUnmarshal_EmptyRepository(t *testing.T) {
}
}

func TestLookupConventionalCommitUsernames_EmptyInputs(t *testing.T) {
tests := []struct {
name string
commits []ConventionalCommit
repo string
}{
{
name: "empty commits",
commits: []ConventionalCommit{},
repo: "owner/repo",
},
{
name: "nil commits",
commits: nil,
repo: "owner/repo",
},
{
name: "empty repo",
commits: []ConventionalCommit{
{FullHash: "abc123", AuthorEmail: "test@example.com"},
},
repo: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := LookupConventionalCommitUsernames(tt.commits, tt.repo)
if len(result) != len(tt.commits) {
t.Errorf("LookupConventionalCommitUsernames() changed slice length: got %d, want %d", len(result), len(tt.commits))
}
})
}
}

func TestLookupGitHubUsernames_NoEmails(t *testing.T) {
// Commits without author emails should not trigger API calls
commits := []git.Commit{
Expand Down
8 changes: 0 additions & 8 deletions internal/changes/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,3 @@ func Detect(cfg *config.TrunkConfig, baseSHA, headSHA string) (*DetectResult, er
func isTriggered(triggers []string, changedFiles []string) bool {
return config.MatchAnyTrigger(triggers, changedFiles)
}

// matchGlob reports whether a single glob pattern matches a path. Negation is
// handled at the pattern-list level by config.MatchTrigger; this helper matches
// the bare glob and is retained for the existing unit coverage. A leading "!"
// is stripped before matching.
func matchGlob(pattern, path string) bool {
return config.MatchGlobPattern(pattern, path)
}
45 changes: 0 additions & 45 deletions internal/changes/detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,6 @@ import (
"testing"
)

func TestMatchGlob(t *testing.T) {
tests := []struct {
pattern string
path string
want bool
}{
// Simple patterns
{"*.go", "main.go", true},
{"*.go", "main.txt", false},
{"src/*.go", "src/main.go", true},
{"src/*.go", "src/sub/main.go", false},

// Double star patterns
{"src/**", "src/main.go", true},
{"src/**", "src/sub/main.go", true},
{"src/**/*.go", "src/main.go", true},
{"src/**/*.go", "src/sub/main.go", true},
{"src/**/*.go", "src/sub/deep/main.go", true},
{"**/*.go", "main.go", true},
{"**/*.go", "src/main.go", true},
{"**", "anything/at/all.txt", true},

// Question mark: GitHub Actions grammar, zero or one of the
// preceding character. A leading "?" has nothing preceding it and is
// treated as a literal character.
{"*.jsx?", "page.js", true},
{"*.jsx?", "page.jsx", true},
{"*.jsx?", "page.jsxx", false},
{"?.go", "a.go", false},

// No match
{"src/**", "other/main.go", false},
{"*.yaml", "config.json", false},
}

for _, tt := range tests {
t.Run(tt.pattern+"_"+tt.path, func(t *testing.T) {
got := matchGlob(tt.pattern, tt.path)
if got != tt.want {
t.Errorf("matchGlob(%q, %q) = %v, want %v", tt.pattern, tt.path, got, tt.want)
}
})
}
}

func TestIsTriggered(t *testing.T) {
tests := []struct {
name string
Expand Down
14 changes: 0 additions & 14 deletions internal/generate/env_gates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,20 +299,6 @@ func TestEnvGates_Promote_ProdDeployJob_OnlyFinalEnvGated(t *testing.T) {
assert.Contains(t, singleBlock, "environment:", "deploy-svc single job must carry job-level environment: since staging has gha_environment")
}

// TestEnvGates_Helper_envGHAName verifies the envGHAName helper returns the
// gha_environment value when configured and falls back to the cascade env name.
func TestEnvGates_Helper_envGHAName(t *testing.T) {
cfg := &config.TrunkConfig{
Environments: []config.EnvironmentEntry{
{Name: "prod", EnvironmentConfig: config.EnvironmentConfig{GHAEnvironment: "production"}},
},
}

assert.Equal(t, "production", envGHAName(cfg, "prod"), "should return gha_environment when configured")
assert.Equal(t, "dev", envGHAName(cfg, "dev"), "should fall back to cascade env name when not configured")
assert.Equal(t, "staging", envGHAName(cfg, "staging"), "should fall back to cascade env name for unconfigured env")
}

// TestEnvGates_Helper_anyEnvHasGHAConfig verifies anyEnvHasGHAConfig reports
// correctly for configs with and without gha_environment.
func TestEnvGates_Helper_anyEnvHasGHAConfig(t *testing.T) {
Expand Down
11 changes: 0 additions & 11 deletions internal/generate/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,6 @@ func workflowDispatchTarget(path string) string {
return filepath.Base(path)
}

// envGHAName returns the GitHub Environment name for a given cascade environment
// name. When the config has an EnvironmentConfig entry for that env whose
// GHAEnvironment field is non-empty, that value is returned; otherwise the
// cascade env name itself is used as the GitHub Environment name.
func envGHAName(cfg *config.TrunkConfig, cascadeEnvName string) string {
if ec, ok := cfg.EnvConfig(cascadeEnvName); ok && ec.GHAEnvironment != "" {
return ec.GHAEnvironment
}
return cascadeEnvName
}

// anyEnvHasGHAConfig reports whether any environment in the config has an
// EnvironmentConfig entry with a non-empty GHAEnvironment field.
func anyEnvHasGHAConfig(cfg *config.TrunkConfig) bool {
Expand Down
20 changes: 0 additions & 20 deletions internal/generate/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,26 +235,6 @@ func (g *DependencyGraph) TopologicalSort() ([]string, error) {
return result, nil
}

// GetAllDependencies returns all transitive dependencies for a node
func (g *DependencyGraph) GetAllDependencies(node string) []string {
visited := make(map[string]bool)
var deps []string

var collect func(n string)
collect = func(n string) {
for _, dep := range g.Edges[n] {
if !visited[dep] {
visited[dep] = true
deps = append(deps, dep)
collect(dep)
}
}
}

collect(node)
return deps
}

// GetDirectDependencies returns only direct (hard) dependencies for a node
func (g *DependencyGraph) GetDirectDependencies(node string) []string {
return g.Edges[node]
Expand Down
22 changes: 0 additions & 22 deletions internal/generate/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,6 @@ func TestTopologicalSort(t *testing.T) {
assert.Less(t, apiIdx, servicesIdx)
}

func TestGetAllDependencies(t *testing.T) {
cfg := &config.TrunkConfig{
Builds: []config.BuildConfig{
{Name: "base", DependsOn: []string{}},
{Name: "app", DependsOn: []string{"base"}},
{Name: "worker", DependsOn: []string{"app"}},
},
}

graph := BuildDependencyGraph(cfg)

// worker depends on app and base (transitively) - using prefixed job IDs
deps := graph.GetAllDependencies("build-worker")
assert.Contains(t, deps, "build-app")
assert.Contains(t, deps, "build-base")

// app depends only on base
deps = graph.GetAllDependencies("build-app")
assert.Contains(t, deps, "build-base")
assert.NotContains(t, deps, "build-worker")
}

func TestGetDirectDependencies(t *testing.T) {
cfg := &config.TrunkConfig{
Builds: []config.BuildConfig{
Expand Down
20 changes: 0 additions & 20 deletions internal/git/boundary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"os"
"path/filepath"
"testing"
"time"

"github.com/stablekernel/cascade/internal/taggrammar"
)
Expand Down Expand Up @@ -57,25 +56,6 @@ func TestGetLatestReleaseTagSpec_NonRepoDirDoesNotReadEnclosingRepo(t *testing.T
}
}

// TestCommitAndPushWithRetry_NonRepoDirDoesNotCommitToEnclosingRepo pins the
// boundary on the write path: a WithDir target that is not itself a repository
// must fail before staging anything, never record a commit in the enclosing
// repository.
func TestCommitAndPushWithRetry_NonRepoDirDoesNotCommitToEnclosingRepo(t *testing.T) {
outer, inner := newEnclosedNonRepoDir(t)
writeFileAt(t, inner, "state.yaml", "state: v1\n")

err := CommitAndPushWithRetry("state.yaml", "chore: escaped state write",
WithDir(inner), WithBackoff(time.Millisecond))
if err == nil {
t.Fatal("CommitAndPushWithRetry in a non-repository dir = nil, want an error")
}

if got := gitOutAt(t, outer, "rev-list", "--count", "HEAD"); got != "1" {
t.Errorf("enclosing repository commit count = %s, want 1: the escaped write landed on the wrong repo", got)
}
}

// TestRefetchAndReset_NonRepoDirErrors pins the boundary on the reset path: a
// non-repository dir must error, not fetch and hard-reset the enclosing
// repository's working tree (which would destroy unrelated local state).
Expand Down
Loading