rdev/internal/domain/build_test.go
jordan bc47e426b0 feat: Add CI pipeline proxy, DNS alias management, and worker executor system
- Add ListPipelines/GetPipeline to CIProvider port with Woodpecker adapter
- Add DNS alias endpoints: GET/POST/DELETE /projects/{id}/domains
- Implement worker executor daemon, build executor, and git operations
- Add build service, worker service, and build audit tracking
- Add worker registry with PostgreSQL adapter and migration
- Add multi-provider code agent interface (Claude Code + OpenCode)
- Add create-and-build combo endpoint
- Update landing-page cookbook to reflect all gaps closed
- Fix tech debt: unified validation, auth scopes, error wrapping, slog patterns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 21:05:28 -07:00

208 lines
4.9 KiB
Go

package domain
import (
"errors"
"testing"
)
func TestBuildSpec_Validate(t *testing.T) {
tests := []struct {
name string
spec BuildSpec
wantErr error
}{
{
name: "valid spec with prompt",
spec: BuildSpec{Prompt: "Build a landing page"},
wantErr: nil,
},
{
name: "valid spec with all fields",
spec: BuildSpec{Prompt: "Build it", Template: "nextjs", AutoCommit: true, AutoPush: true},
wantErr: nil,
},
{
name: "empty prompt",
spec: BuildSpec{},
wantErr: ErrPromptRequired,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.spec.Validate()
if tt.wantErr != nil {
if !errors.Is(err, tt.wantErr) {
t.Errorf("Validate() error = %v, want %v", err, tt.wantErr)
}
} else if err != nil {
t.Errorf("Validate() unexpected error = %v", err)
}
})
}
}
func TestBuildStatus_IsValid(t *testing.T) {
tests := []struct {
status BuildStatus
want bool
}{
{BuildStatusPending, true},
{BuildStatusRunning, true},
{BuildStatusCompleted, true},
{BuildStatusFailed, true},
{BuildStatusCancelled, true},
{"unknown", false},
{"", false},
}
for _, tt := range tests {
t.Run(string(tt.status), func(t *testing.T) {
if got := tt.status.IsValid(); got != tt.want {
t.Errorf("IsValid() = %v, want %v", got, tt.want)
}
})
}
}
func TestBuildResult_ToWorkResult(t *testing.T) {
t.Run("success with all fields", func(t *testing.T) {
result := &BuildResult{
Success: true,
Output: "Build completed",
CommitSHA: "abc123",
FilesChanged: []string{"main.go", "go.mod"},
DurationMs: 1500,
Artifacts: map[string]string{"deploy_url": "https://example.com"},
}
wr := result.ToWorkResult()
if wr.Output != "Build completed" {
t.Errorf("Output = %q, want %q", wr.Output, "Build completed")
}
if wr.Artifacts["commit_sha"] != "abc123" {
t.Errorf("commit_sha = %q, want %q", wr.Artifacts["commit_sha"], "abc123")
}
if wr.Artifacts["duration_ms"] != "1500" {
t.Errorf("duration_ms = %q, want %q", wr.Artifacts["duration_ms"], "1500")
}
if wr.Artifacts["files_changed_count"] != "2" {
t.Errorf("files_changed_count = %q, want %q", wr.Artifacts["files_changed_count"], "2")
}
if wr.Artifacts["deploy_url"] != "https://example.com" {
t.Errorf("deploy_url = %q, want %q", wr.Artifacts["deploy_url"], "https://example.com")
}
})
t.Run("failure uses error as output", func(t *testing.T) {
result := &BuildResult{
Success: false,
Output: "partial output",
Error: "build failed: missing deps",
}
wr := result.ToWorkResult()
if wr.Output != "build failed: missing deps" {
t.Errorf("Output = %q, want error message", wr.Output)
}
})
t.Run("nil artifacts map is safe", func(t *testing.T) {
result := &BuildResult{
Success: true,
Output: "done",
}
wr := result.ToWorkResult()
if wr.Output != "done" {
t.Errorf("Output = %q, want %q", wr.Output, "done")
}
if len(wr.Artifacts) != 0 {
t.Errorf("Artifacts = %v, want empty", wr.Artifacts)
}
})
t.Run("zero duration not included", func(t *testing.T) {
result := &BuildResult{
Success: true,
DurationMs: 0,
}
wr := result.ToWorkResult()
if _, ok := wr.Artifacts["duration_ms"]; ok {
t.Error("duration_ms should not be in artifacts when zero")
}
})
t.Run("nil receiver returns empty result", func(t *testing.T) {
var result *BuildResult
wr := result.ToWorkResult()
if wr.Output != "" {
t.Errorf("Output = %q, want empty", wr.Output)
}
if wr.Artifacts != nil {
t.Errorf("Artifacts = %v, want nil", wr.Artifacts)
}
})
t.Run("promoted fields overwrite existing artifacts", func(t *testing.T) {
result := &BuildResult{
Success: true,
CommitSHA: "new-sha",
Artifacts: map[string]string{
"commit_sha": "old-sha",
"custom_key": "kept",
},
}
wr := result.ToWorkResult()
if wr.Artifacts["commit_sha"] != "new-sha" {
t.Errorf("commit_sha = %q, want %q (promoted field should overwrite)", wr.Artifacts["commit_sha"], "new-sha")
}
if wr.Artifacts["custom_key"] != "kept" {
t.Errorf("custom_key = %q, want %q", wr.Artifacts["custom_key"], "kept")
}
})
t.Run("failed with empty error keeps output", func(t *testing.T) {
result := &BuildResult{
Success: false,
Output: "partial output before crash",
Error: "",
}
wr := result.ToWorkResult()
if wr.Output != "partial output before crash" {
t.Errorf("Output = %q, want original output when error is empty", wr.Output)
}
})
}
func TestBuildStatus_IsTerminal(t *testing.T) {
tests := []struct {
status BuildStatus
want bool
}{
{BuildStatusPending, false},
{BuildStatusRunning, false},
{BuildStatusCompleted, true},
{BuildStatusFailed, true},
{BuildStatusCancelled, true},
}
for _, tt := range tests {
t.Run(string(tt.status), func(t *testing.T) {
if got := tt.status.IsTerminal(); got != tt.want {
t.Errorf("IsTerminal() = %v, want %v", got, tt.want)
}
})
}
}