Skip to content
Merged
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
340 changes: 169 additions & 171 deletions pkg/workflow/action_reference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,105 +4,111 @@ package workflow

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestConvertToRemoteActionRef(t *testing.T) {
t.Run("local path with ./ prefix and version tag", func(t *testing.T) {
compiler := NewCompilerWithVersion("v1.2.3")
data := &WorkflowData{}
ref := compiler.convertToRemoteActionRef("./actions/create-issue", data)
expected := "github/gh-aw/actions/create-issue@v1.2.3"
if ref != expected {
t.Errorf("Expected %q, got %q", expected, ref)
}
})

t.Run("local path without ./ prefix and version tag", func(t *testing.T) {
compiler := NewCompilerWithVersion("v1.0.0")
data := &WorkflowData{}
ref := compiler.convertToRemoteActionRef("actions/create-issue", data)
expected := "github/gh-aw/actions/create-issue@v1.0.0"
if ref != expected {
t.Errorf("Expected %q, got %q", expected, ref)
}
})

t.Run("nested action path with version tag", func(t *testing.T) {
compiler := NewCompilerWithVersion("v2.0.0")
data := &WorkflowData{}
ref := compiler.convertToRemoteActionRef("./actions/nested/action", data)
expected := "github/gh-aw/actions/nested/action@v2.0.0"
if ref != expected {
t.Errorf("Expected %q, got %q", expected, ref)
}
})

t.Run("dev version returns empty", func(t *testing.T) {
compiler := NewCompilerWithVersion("dev")
data := &WorkflowData{}
ref := compiler.convertToRemoteActionRef("./actions/create-issue", data)
if ref != "" {
t.Errorf("Expected empty string with 'dev' version, got %q", ref)
}
})

t.Run("empty version returns empty", func(t *testing.T) {
compiler := NewCompiler()
data := &WorkflowData{}
ref := compiler.convertToRemoteActionRef("./actions/create-issue", data)
if ref != "" {
t.Errorf("Expected empty string with empty version, got %q", ref)
}
})

t.Run("action-tag overrides version", func(t *testing.T) {
compiler := NewCompilerWithVersion("v1.0.0")
data := &WorkflowData{Features: map[string]any{"action-tag": "latest"}}
ref := compiler.convertToRemoteActionRef("./actions/create-issue", data)
expected := "github/gh-aw/actions/create-issue@latest"
if ref != expected {
t.Errorf("Expected %q, got %q", expected, ref)
}
})
tests := []struct {
name string
version string
actionTag string
setEmptyTag bool
localPath string
nilData bool
expectedRef string
shouldBeEmpty bool
}{
{
name: "local path with ./ prefix and version tag",
version: "v1.2.3",
localPath: "./actions/create-issue",
expectedRef: "github/gh-aw/actions/create-issue@v1.2.3",
},
{
name: "local path without ./ prefix and version tag",
version: "v1.0.0",
localPath: "actions/create-issue",
expectedRef: "github/gh-aw/actions/create-issue@v1.0.0",
},
{
name: "nested action path with version tag",
version: "v2.0.0",
localPath: "./actions/nested/action",
expectedRef: "github/gh-aw/actions/nested/action@v2.0.0",
},
{
name: "dev version returns empty",
version: "dev",
localPath: "./actions/create-issue",
shouldBeEmpty: true,
},
{
name: "empty version returns empty",
version: "",
localPath: "./actions/create-issue",
shouldBeEmpty: true,
},
{
name: "action-tag overrides version",
version: "v1.0.0",
actionTag: "latest",
localPath: "./actions/create-issue",
expectedRef: "github/gh-aw/actions/create-issue@latest",
},
{
name: "action-tag with specific SHA",
version: "v1.0.0",
actionTag: "abc123def456",
localPath: "./actions/setup",
expectedRef: "github/gh-aw/actions/setup@abc123def456",
},
{
name: "action-tag with version tag format",
version: "v1.0.0",
actionTag: "v2.5.0",
localPath: "./actions/setup",
expectedRef: "github/gh-aw/actions/setup@v2.5.0",
},
{
name: "empty action-tag falls back to version",
version: "v1.5.0",
setEmptyTag: true,
localPath: "./actions/create-issue",
expectedRef: "github/gh-aw/actions/create-issue@v1.5.0",
},
{
name: "nil data falls back to version",
version: "v1.5.0",
nilData: true,
localPath: "./actions/create-issue",
expectedRef: "github/gh-aw/actions/create-issue@v1.5.0",
},
}

t.Run("action-tag with specific SHA", func(t *testing.T) {
compiler := NewCompilerWithVersion("v1.0.0")
data := &WorkflowData{Features: map[string]any{"action-tag": "abc123def456"}}
ref := compiler.convertToRemoteActionRef("./actions/setup", data)
expected := "github/gh-aw/actions/setup@abc123def456"
if ref != expected {
t.Errorf("Expected %q, got %q", expected, ref)
}
})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compiler := NewCompilerWithVersion(tt.version)

t.Run("action-tag with version tag format", func(t *testing.T) {
compiler := NewCompilerWithVersion("v1.0.0")
data := &WorkflowData{Features: map[string]any{"action-tag": "v2.5.0"}}
ref := compiler.convertToRemoteActionRef("./actions/setup", data)
expected := "github/gh-aw/actions/setup@v2.5.0"
if ref != expected {
t.Errorf("Expected %q, got %q", expected, ref)
}
})
var data *WorkflowData
if !tt.nilData {
data = &WorkflowData{}
if tt.actionTag != "" {
data.Features = map[string]any{"action-tag": tt.actionTag}
} else if tt.setEmptyTag {
data.Features = map[string]any{"action-tag": ""}
}
}

t.Run("empty action-tag falls back to version", func(t *testing.T) {
compiler := NewCompilerWithVersion("v1.5.0")
data := &WorkflowData{Features: map[string]any{"action-tag": ""}}
ref := compiler.convertToRemoteActionRef("./actions/create-issue", data)
expected := "github/gh-aw/actions/create-issue@v1.5.0"
if ref != expected {
t.Errorf("Expected %q, got %q", expected, ref)
}
})
ref := compiler.convertToRemoteActionRef(tt.localPath, data)

t.Run("nil data falls back to version", func(t *testing.T) {
compiler := NewCompilerWithVersion("v1.5.0")
ref := compiler.convertToRemoteActionRef("./actions/create-issue", nil)
expected := "github/gh-aw/actions/create-issue@v1.5.0"
if ref != expected {
t.Errorf("Expected %q, got %q", expected, ref)
}
})
if tt.shouldBeEmpty {
assert.Empty(t, ref, "should return empty string for invalid/dev version")
} else {
assert.Equal(t, tt.expectedRef, ref, "should construct correct remote reference")
}
})
}
}

func TestResolveActionReference(t *testing.T) {
Expand Down Expand Up @@ -181,85 +187,85 @@ func TestResolveActionReference(t *testing.T) {
ref := compiler.resolveActionReference(tt.localPath, data)

if tt.shouldBeEmpty {
if ref != "" {
t.Errorf("%s: expected empty string, got %q", tt.description, ref)
}
assert.Empty(t, ref, tt.description)
} else {
if ref != tt.expectedRef {
t.Errorf("%s: expected %q, got %q", tt.description, tt.expectedRef, ref)
}
assert.Equal(t, tt.expectedRef, ref, tt.description)
}
})
}
}

func TestCompilerActionTag(t *testing.T) {
t.Run("compiler actionTag overrides frontmatter action-tag", func(t *testing.T) {
compiler := NewCompilerWithVersion("v1.0.0")
compiler.SetActionMode(ActionModeRelease)
compiler.SetActionTag("v2.0.0")

// Frontmatter has action-tag but compiler actionTag should take precedence
data := &WorkflowData{Features: map[string]any{"action-tag": "v1.5.0"}}
ref := compiler.convertToRemoteActionRef("./actions/setup", data)
expected := "github/gh-aw/actions/setup@v2.0.0"
if ref != expected {
t.Errorf("Expected compiler actionTag to take precedence: got %q, want %q", ref, expected)
}
})

t.Run("compiler actionTag overrides version", func(t *testing.T) {
compiler := NewCompilerWithVersion("v1.0.0")
compiler.SetActionMode(ActionModeRelease)
compiler.SetActionTag("abc123def456")

data := &WorkflowData{}
ref := compiler.convertToRemoteActionRef("./actions/create-issue", data)
expected := "github/gh-aw/actions/create-issue@abc123def456"
if ref != expected {
t.Errorf("Expected %q, got %q", expected, ref)
}
})

t.Run("compiler actionTag with dev mode forces release behavior", func(t *testing.T) {
compiler := NewCompilerWithVersion("v1.0.0")
// When actionTag is set via --action-tag flag, setupActionMode sets mode to release
// So this test should reflect that behavior
compiler.SetActionTag("v2.0.0")
compiler.SetActionMode(ActionModeRelease) // This is what setupActionMode does

data := &WorkflowData{}
ref := compiler.resolveActionReference("./actions/setup", data)
expected := "github/gh-aw/actions/setup@v2.0.0"
if ref != expected {
t.Errorf("Expected compiler actionTag with release mode: got %q, want %q", ref, expected)
}
})
tests := []struct {
name string
version string
compilerActionTag string
frontmatterTag string
localPath string
useResolve bool
expectedRef string
}{
{
name: "compiler actionTag overrides frontmatter action-tag",
version: "v1.0.0",
compilerActionTag: "v2.0.0",
frontmatterTag: "v1.5.0",
localPath: "./actions/setup",
expectedRef: "github/gh-aw/actions/setup@v2.0.0",
},
{
name: "compiler actionTag overrides version",
version: "v1.0.0",
compilerActionTag: "abc123def456",
localPath: "./actions/create-issue",
expectedRef: "github/gh-aw/actions/create-issue@abc123def456",
},
{
name: "compiler actionTag with dev mode forces release behavior",
version: "v1.0.0",
compilerActionTag: "v2.0.0",
localPath: "./actions/setup",
useResolve: true,
expectedRef: "github/gh-aw/actions/setup@v2.0.0",
Comment on lines +224 to +229

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The table case name "compiler actionTag with dev mode forces release behavior" is misleading: the test always sets ActionModeRelease and the version is v1.0.0 (not a dev build). Rename this case to reflect what’s actually being asserted (e.g., action-tag flag priority/behavior in release mode) to avoid confusion when diagnosing failures.

Copilot uses AI. Check for mistakes.
},
{
name: "empty compiler actionTag falls back to frontmatter",
version: "v1.0.0",
frontmatterTag: "v1.5.0",
localPath: "./actions/setup",
expectedRef: "github/gh-aw/actions/setup@v1.5.0",
},
{
name: "empty compiler actionTag and no frontmatter uses version",
version: "v1.2.3",
localPath: "./actions/setup",
expectedRef: "github/gh-aw/actions/setup@v1.2.3",
},
}

t.Run("empty compiler actionTag falls back to frontmatter", func(t *testing.T) {
compiler := NewCompilerWithVersion("v1.0.0")
compiler.SetActionMode(ActionModeRelease)
// Don't set compiler actionTag
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compiler := NewCompilerWithVersion(tt.version)
compiler.SetActionMode(ActionModeRelease)
if tt.compilerActionTag != "" {
compiler.SetActionTag(tt.compilerActionTag)
}

data := &WorkflowData{Features: map[string]any{"action-tag": "v1.5.0"}}
ref := compiler.convertToRemoteActionRef("./actions/setup", data)
expected := "github/gh-aw/actions/setup@v1.5.0"
if ref != expected {
t.Errorf("Expected frontmatter action-tag to be used: got %q, want %q", ref, expected)
}
})
data := &WorkflowData{}
if tt.frontmatterTag != "" {
data.Features = map[string]any{"action-tag": tt.frontmatterTag}
}

t.Run("empty compiler actionTag and no frontmatter uses version", func(t *testing.T) {
compiler := NewCompilerWithVersion("v1.2.3")
compiler.SetActionMode(ActionModeRelease)
var ref string
if tt.useResolve {
ref = compiler.resolveActionReference(tt.localPath, data)
} else {
ref = compiler.convertToRemoteActionRef(tt.localPath, data)
}

data := &WorkflowData{}
ref := compiler.convertToRemoteActionRef("./actions/setup", data)
expected := "github/gh-aw/actions/setup@v1.2.3"
if ref != expected {
t.Errorf("Expected compiler version to be used: got %q, want %q", ref, expected)
}
})
assert.Equal(t, tt.expectedRef, ref, "should use correct tag priority order")
})
}
}

func TestResolveSetupActionReference(t *testing.T) {
Expand Down Expand Up @@ -333,9 +339,7 @@ func TestResolveSetupActionReference(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
// Pass nil for data to test backward compatibility with standalone usage
ref := ResolveSetupActionReference(tt.actionMode, tt.version, tt.actionTag, nil)
if ref != tt.expectedRef {
t.Errorf("%s: expected %q, got %q", tt.description, tt.expectedRef, ref)
}
assert.Equal(t, tt.expectedRef, ref, tt.description)
})
}
}
Expand All @@ -351,17 +355,11 @@ func TestResolveSetupActionReferenceWithData(t *testing.T) {
ref := ResolveSetupActionReference(ActionModeRelease, "v1.0.0", "", resolver)

// Without a valid pin or successful resolution, should return tag-based reference
expectedRef := "github/gh-aw/actions/setup@v1.0.0"
if ref != expectedRef {
t.Errorf("Expected %q, got %q", expectedRef, ref)
}
assert.Equal(t, "github/gh-aw/actions/setup@v1.0.0", ref, "should return tag-based reference when SHA resolution fails")
})

t.Run("release mode with nil resolver returns tag-based reference", func(t *testing.T) {
ref := ResolveSetupActionReference(ActionModeRelease, "v1.0.0", "", nil)
expectedRef := "github/gh-aw/actions/setup@v1.0.0"
if ref != expectedRef {
t.Errorf("Expected %q, got %q", expectedRef, ref)
}
assert.Equal(t, "github/gh-aw/actions/setup@v1.0.0", ref, "should return tag-based reference when no resolver provided")
})
}
Loading