go-build/pkg/build/workflow_test.go

524 lines
19 KiB
Go
Raw Permalink Normal View History

package build
import (
"testing"
"dappco.re/go/core/build/internal/ax"
"dappco.re/go/core/io"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestWorkflow_WriteReleaseWorkflow_Good(t *testing.T) {
t.Run("writes the embedded template to the default path", func(t *testing.T) {
fs := io.NewMockMedium()
err := WriteReleaseWorkflow(fs, "")
require.NoError(t, err)
content, err := fs.Read(DefaultReleaseWorkflowPath)
require.NoError(t, err)
template, err := releaseWorkflowTemplate.ReadFile("templates/release.yml")
require.NoError(t, err)
assert.Equal(t, string(template), content)
assert.Contains(t, content, "workflow_call:")
assert.Contains(t, content, "workflow_dispatch:")
assert.Contains(t, content, "core build --targets")
assert.Contains(t, content, "--archive-format")
assert.Contains(t, content, "actions/download-artifact@v4")
assert.Contains(t, content, "command: ci")
assert.Contains(t, content, "we-are-go-for-launch: true")
})
t.Run("writes to a custom path", func(t *testing.T) {
fs := io.NewMockMedium()
err := WriteReleaseWorkflow(fs, "custom/workflow.yml")
require.NoError(t, err)
content, err := fs.Read("custom/workflow.yml")
require.NoError(t, err)
assert.NotEmpty(t, content)
})
2026-04-01 23:00:40 +00:00
t.Run("trims surrounding whitespace from the output path", func(t *testing.T) {
fs := io.NewMockMedium()
err := WriteReleaseWorkflow(fs, " ci ")
require.NoError(t, err)
content, err := fs.Read("ci/release.yml")
require.NoError(t, err)
assert.NotEmpty(t, content)
})
t.Run("writes release.yml for a bare directory-style path", func(t *testing.T) {
fs := io.NewMockMedium()
err := WriteReleaseWorkflow(fs, "ci")
require.NoError(t, err)
content, err := fs.Read("ci/release.yml")
require.NoError(t, err)
assert.NotEmpty(t, content)
})
t.Run("writes release.yml inside an existing directory", func(t *testing.T) {
projectDir := t.TempDir()
outputDir := ax.Join(projectDir, "ci")
require.NoError(t, ax.MkdirAll(outputDir, 0o755))
err := WriteReleaseWorkflow(io.Local, outputDir)
require.NoError(t, err)
content, err := io.Local.Read(ax.Join(outputDir, DefaultReleaseWorkflowFileName))
require.NoError(t, err)
template, err := releaseWorkflowTemplate.ReadFile("templates/release.yml")
require.NoError(t, err)
assert.Equal(t, string(template), content)
})
t.Run("writes release.yml for directory-style output paths", func(t *testing.T) {
fs := io.NewMockMedium()
err := WriteReleaseWorkflow(fs, "ci/")
require.NoError(t, err)
content, err := fs.Read("ci/release.yml")
require.NoError(t, err)
assert.NotEmpty(t, content)
})
t.Run("creates parent directories on a real filesystem", func(t *testing.T) {
projectDir := t.TempDir()
path := ax.Join(projectDir, ".github", "workflows", "release.yml")
err := WriteReleaseWorkflow(io.Local, path)
require.NoError(t, err)
content, err := io.Local.Read(path)
require.NoError(t, err)
template, err := releaseWorkflowTemplate.ReadFile("templates/release.yml")
require.NoError(t, err)
assert.Equal(t, string(template), content)
})
}
func TestWorkflow_WriteReleaseWorkflow_Bad(t *testing.T) {
t.Run("rejects a nil filesystem medium", func(t *testing.T) {
err := WriteReleaseWorkflow(nil, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "filesystem medium is required")
})
}
func TestWorkflow_ReleaseWorkflowPath_Good(t *testing.T) {
assert.Equal(t, "/tmp/project/.github/workflows/release.yml", ReleaseWorkflowPath("/tmp/project"))
}
func TestWorkflow_ResolveReleaseWorkflowOutputPathWithMedium_Good(t *testing.T) {
t.Run("treats an existing directory as a workflow directory", func(t *testing.T) {
fs := io.NewMockMedium()
fs.Dirs["/tmp/project/ci"] = true
path := ResolveReleaseWorkflowOutputPathWithMedium(fs, "/tmp/project", "ci")
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("keeps explicit file paths unchanged", func(t *testing.T) {
fs := io.NewMockMedium()
path := ResolveReleaseWorkflowOutputPathWithMedium(fs, "/tmp/project", "ci/release.yml")
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
}
func TestWorkflow_ResolveReleaseWorkflowPath_Good(t *testing.T) {
t.Run("uses the conventional path when empty", func(t *testing.T) {
assert.Equal(t, "/tmp/project/.github/workflows/release.yml", ResolveReleaseWorkflowPath("/tmp/project", ""))
})
t.Run("joins relative paths to the project directory", func(t *testing.T) {
assert.Equal(t, "/tmp/project/ci/release.yml", ResolveReleaseWorkflowPath("/tmp/project", "ci/release.yml"))
})
t.Run("treats bare relative directory names as directories", func(t *testing.T) {
assert.Equal(t, "/tmp/project/ci/release.yml", ResolveReleaseWorkflowPath("/tmp/project", "ci"))
})
t.Run("treats current-directory-prefixed directory names as directories", func(t *testing.T) {
assert.Equal(t, "/tmp/project/ci/release.yml", ResolveReleaseWorkflowPath("/tmp/project", "./ci"))
})
t.Run("treats the conventional workflows directory as a directory", func(t *testing.T) {
assert.Equal(t, "/tmp/project/.github/workflows/release.yml", ResolveReleaseWorkflowPath("/tmp/project", ".github/workflows"))
})
t.Run("treats current-directory-prefixed workflows directories as directories", func(t *testing.T) {
assert.Equal(t, "/tmp/project/.github/workflows/release.yml", ResolveReleaseWorkflowPath("/tmp/project", "./.github/workflows"))
})
t.Run("keeps nested extensionless paths as files", func(t *testing.T) {
assert.Equal(t, "/tmp/project/ci/release", ResolveReleaseWorkflowPath("/tmp/project", "ci/release"))
})
t.Run("treats the current directory as a workflow directory", func(t *testing.T) {
assert.Equal(t, "/tmp/project/release.yml", ResolveReleaseWorkflowPath("/tmp/project", "."))
})
t.Run("treats trailing-slash relative paths as directories", func(t *testing.T) {
assert.Equal(t, "/tmp/project/ci/release.yml", ResolveReleaseWorkflowPath("/tmp/project", "ci/"))
})
t.Run("keeps absolute paths unchanged", func(t *testing.T) {
assert.Equal(t, "/tmp/release.yml", ResolveReleaseWorkflowPath("/tmp/project", "/tmp/release.yml"))
})
t.Run("treats trailing-slash absolute paths as directories", func(t *testing.T) {
assert.Equal(t, "/tmp/workflows/release.yml", ResolveReleaseWorkflowPath("/tmp/project", "/tmp/workflows/"))
})
}
func TestWorkflow_ResolveReleaseWorkflowInputPath_Good(t *testing.T) {
t.Run("uses the conventional path when both inputs are empty", func(t *testing.T) {
path, err := ResolveReleaseWorkflowInputPath("/tmp/project", "", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/.github/workflows/release.yml", path)
})
t.Run("accepts path as the primary input", func(t *testing.T) {
path, err := ResolveReleaseWorkflowInputPath("/tmp/project", "ci/release.yml", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("accepts bare directory-style path as the primary input", func(t *testing.T) {
path, err := ResolveReleaseWorkflowInputPath("/tmp/project", "ci", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("accepts current-directory-prefixed directory-style path as the primary input", func(t *testing.T) {
path, err := ResolveReleaseWorkflowInputPath("/tmp/project", "./ci", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("accepts the conventional workflows directory as the primary input", func(t *testing.T) {
path, err := ResolveReleaseWorkflowInputPath("/tmp/project", ".github/workflows", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/.github/workflows/release.yml", path)
})
t.Run("accepts current-directory-prefixed workflows directories as the primary input", func(t *testing.T) {
path, err := ResolveReleaseWorkflowInputPath("/tmp/project", "./.github/workflows", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/.github/workflows/release.yml", path)
})
t.Run("keeps nested extensionless paths as files", func(t *testing.T) {
path, err := ResolveReleaseWorkflowInputPath("/tmp/project", "ci/release", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release", path)
})
t.Run("accepts the current directory as the primary input", func(t *testing.T) {
path, err := ResolveReleaseWorkflowInputPath("/tmp/project", ".", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/release.yml", path)
})
t.Run("accepts output as an alias for path", func(t *testing.T) {
path, err := ResolveReleaseWorkflowInputPath("/tmp/project", "", "ci/release.yml")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
2026-04-01 23:00:40 +00:00
t.Run("trims surrounding whitespace from inputs", func(t *testing.T) {
path, err := ResolveReleaseWorkflowInputPath("/tmp/project", " ci ", " ")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("accepts matching path and output values", func(t *testing.T) {
path, err := ResolveReleaseWorkflowInputPath("/tmp/project", "ci/release.yml", "ci/release.yml")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("accepts matching directory-style path and output values", func(t *testing.T) {
path, err := ResolveReleaseWorkflowInputPath("/tmp/project", "ci/", "ci/")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
}
func TestWorkflow_ResolveReleaseWorkflowInputPath_Bad(t *testing.T) {
t.Run("rejects conflicting path and output values", func(t *testing.T) {
path, err := ResolveReleaseWorkflowInputPath("/tmp/project", "ci/release.yml", "ops/release.yml")
assert.Error(t, err)
assert.Empty(t, path)
assert.Contains(t, err.Error(), "path and output specify different locations")
})
}
2026-04-01 22:05:27 +00:00
func TestWorkflow_ResolveReleaseWorkflowInputPathWithMedium_Good(t *testing.T) {
t.Run("treats an existing directory as a workflow directory", func(t *testing.T) {
fs := io.NewMockMedium()
fs.Dirs["/tmp/project/ci"] = true
path, err := ResolveReleaseWorkflowInputPathWithMedium(fs, "/tmp/project", "ci", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("treats a bare directory-style path as a workflow directory", func(t *testing.T) {
fs := io.NewMockMedium()
path, err := ResolveReleaseWorkflowInputPathWithMedium(fs, "/tmp/project", "ci", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("treats a current-directory-prefixed directory-style path as a workflow directory", func(t *testing.T) {
fs := io.NewMockMedium()
path, err := ResolveReleaseWorkflowInputPathWithMedium(fs, "/tmp/project", "./ci", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("treats the conventional workflows directory as a workflow directory", func(t *testing.T) {
fs := io.NewMockMedium()
path, err := ResolveReleaseWorkflowInputPathWithMedium(fs, "/tmp/project", ".github/workflows", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/.github/workflows/release.yml", path)
})
t.Run("treats current-directory-prefixed workflows directories as workflow directories", func(t *testing.T) {
fs := io.NewMockMedium()
path, err := ResolveReleaseWorkflowInputPathWithMedium(fs, "/tmp/project", "./.github/workflows", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/.github/workflows/release.yml", path)
})
2026-04-01 22:05:27 +00:00
t.Run("keeps a file path unchanged when the target is not a directory", func(t *testing.T) {
fs := io.NewMockMedium()
path, err := ResolveReleaseWorkflowInputPathWithMedium(fs, "/tmp/project", "ci/release.yml", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("normalizes matching directory aliases", func(t *testing.T) {
fs := io.NewMockMedium()
fs.Dirs["/tmp/project/ci"] = true
path, err := ResolveReleaseWorkflowInputPathWithMedium(fs, "/tmp/project", "ci", "ci/")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
2026-04-01 23:00:40 +00:00
t.Run("trims surrounding whitespace before resolving", func(t *testing.T) {
fs := io.NewMockMedium()
fs.Dirs["/tmp/project/ci"] = true
path, err := ResolveReleaseWorkflowInputPathWithMedium(fs, "/tmp/project", " ci ", " ")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
2026-04-01 22:05:27 +00:00
}
2026-04-02 00:49:27 +00:00
func TestWorkflow_ResolveReleaseWorkflowInputPathAliases_Good(t *testing.T) {
t.Run("accepts the preferred path input", func(t *testing.T) {
fs := io.NewMockMedium()
path, err := ResolveReleaseWorkflowInputPathAliases(fs, "/tmp/project", "ci", "", "", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("accepts the workflowPath alias", func(t *testing.T) {
fs := io.NewMockMedium()
path, err := ResolveReleaseWorkflowInputPathAliases(fs, "/tmp/project", "", "ci", "", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("accepts the workflow_path alias", func(t *testing.T) {
fs := io.NewMockMedium()
path, err := ResolveReleaseWorkflowInputPathAliases(fs, "/tmp/project", "", "", "ci", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("accepts the workflow-path alias", func(t *testing.T) {
fs := io.NewMockMedium()
path, err := ResolveReleaseWorkflowInputPathAliases(fs, "/tmp/project", "", "", "", "ci")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
t.Run("normalises matching aliases", func(t *testing.T) {
fs := io.NewMockMedium()
fs.Dirs["/tmp/project/ci"] = true
path, err := ResolveReleaseWorkflowInputPathAliases(fs, "/tmp/project", "ci/", "./ci", "ci", "")
require.NoError(t, err)
assert.Equal(t, "/tmp/project/ci/release.yml", path)
})
}
func TestWorkflow_ResolveReleaseWorkflowInputPathAliases_Bad(t *testing.T) {
fs := io.NewMockMedium()
path, err := ResolveReleaseWorkflowInputPathAliases(fs, "/tmp/project", "ci/release.yml", "ops/release.yml", "", "")
assert.Error(t, err)
assert.Empty(t, path)
assert.Contains(t, err.Error(), "path aliases specify different locations")
}
func TestWorkflow_ResolveReleaseWorkflowOutputPath_Good(t *testing.T) {
t.Run("accepts the preferred output path", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPath("ci/release.yml", "", "")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
t.Run("accepts the snake_case output path alias", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPath("", "ci/release.yml", "")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
t.Run("accepts the hyphenated output path alias", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliases("", "ci/release.yml", "", "", "", "", "", "", "")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
t.Run("accepts the legacy output alias", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPath("", "", "ci/release.yml")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
t.Run("trims surrounding whitespace from aliases", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPath(" ci/release.yml ", " ", " ")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
t.Run("accepts matching aliases", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPath("ci/release.yml", "ci/release.yml", "ci/release.yml")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
t.Run("normalises equivalent path aliases", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPath("ci/release.yml", "./ci/release.yml", "ci/release.yml")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
}
func TestWorkflow_ResolveReleaseWorkflowOutputPath_Bad(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPath("ci/release.yml", "ops/release.yml", "")
assert.Error(t, err)
assert.Empty(t, path)
assert.Contains(t, err.Error(), "output aliases specify different locations")
}
func TestWorkflow_ResolveReleaseWorkflowOutputPathAliases_Good(t *testing.T) {
t.Run("accepts workflowOutputPath aliases", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliases("", "", "", "", "ci/release.yml", "", "", "", "")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
t.Run("accepts the hyphenated workflowOutputPath alias", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliases("", "", "", "", "", "", "ci/release.yml", "", "")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
t.Run("accepts the workflow_output alias", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliases("", "", "", "", "", "ci/release.yml", "", "", "")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
t.Run("accepts the workflow-output alias", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliases("", "", "", "", "", "", "ci/release.yml", "", "")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
t.Run("normalises matching workflow output aliases", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliases("ci/release.yml", "", "", "./ci/release.yml", "ci/release.yml", "", "", "", "")
require.NoError(t, err)
assert.Equal(t, "ci/release.yml", path)
})
}
func TestWorkflow_ResolveReleaseWorkflowOutputPathAliasesInProject_Good(t *testing.T) {
projectDir := t.TempDir()
absolutePath := ax.Join(projectDir, "ci", "release.yml")
absoluteDirectory := ax.Join(projectDir, "ops")
require.NoError(t, ax.MkdirAll(absoluteDirectory, 0o755))
t.Run("accepts the preferred output path", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliasesInProject(projectDir, "ci/release.yml", "", "", "", "", "", "", "", "")
require.NoError(t, err)
assert.Equal(t, absolutePath, path)
})
t.Run("accepts an absolute workflow output alias equivalent to the project path", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliasesInProject(projectDir, "", "", "", "", absolutePath, "", "", "", "")
require.NoError(t, err)
assert.Equal(t, absolutePath, path)
})
t.Run("accepts matching relative and absolute aliases", func(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliasesInProject(projectDir, "ci/release.yml", "", "", "", "", "", "", "", absolutePath)
require.NoError(t, err)
assert.Equal(t, absolutePath, path)
})
t.Run("treats an existing absolute directory as a workflow directory", func(t *testing.T) {
fs := io.NewMockMedium()
fs.Dirs[absoluteDirectory] = true
path, err := ResolveReleaseWorkflowOutputPathAliasesInProjectWithMedium(fs, projectDir, "", "", "", "", absoluteDirectory, "", "", "", "")
require.NoError(t, err)
assert.Equal(t, ax.Join(absoluteDirectory, DefaultReleaseWorkflowFileName), path)
})
}
func TestWorkflow_ResolveReleaseWorkflowOutputPathAliasesInProject_Bad(t *testing.T) {
projectDir := t.TempDir()
path, err := ResolveReleaseWorkflowOutputPathAliasesInProject(projectDir, "ci/release.yml", "", "", "", "", "", "", "", ax.Join(projectDir, "ops", "release.yml"))
assert.Error(t, err)
assert.Empty(t, path)
assert.Contains(t, err.Error(), "output aliases specify different locations")
}
func TestWorkflow_ResolveReleaseWorkflowOutputPathAliases_Bad(t *testing.T) {
path, err := ResolveReleaseWorkflowOutputPathAliases("ci/release.yml", "", "", "", "ops/release.yml", "", "", "", "")
assert.Error(t, err)
assert.Empty(t, path)
assert.Contains(t, err.Error(), "output aliases specify different locations")
}