rdev/internal/handlers/infrastructure_domains_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

247 lines
7.4 KiB
Go

package handlers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-chi/chi/v5"
"github.com/orchard9/rdev/internal/domain"
)
func TestInfrastructureHandler_ListDomains(t *testing.T) {
t.Run("returns matching A records", func(t *testing.T) {
_, _, dns, _, router := setupInfraHandler()
// Add records — one matching the project, one unrelated
dns.records["landing.threesix.ai"] = &domain.DNSRecord{
ID: "rec-1", Type: "A", Name: "landing.threesix.ai",
Content: "208.122.204.172", TTL: 1,
}
dns.records["other.threesix.ai"] = &domain.DNSRecord{
ID: "rec-2", Type: "A", Name: "other.threesix.ai",
Content: "208.122.204.172", TTL: 1,
}
req := httptest.NewRequest("GET", "/projects/landing/domains", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusOK, rec.Body.String())
}
var resp map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("unmarshal: %v", err)
}
data := resp["data"].(map[string]any)
total := int(data["total"].(float64))
if total != 1 {
t.Errorf("total = %d, want 1 (only landing.threesix.ai)", total)
}
})
t.Run("returns CNAME aliases", func(t *testing.T) {
_, _, dns, _, router := setupInfraHandler()
dns.records["landing.threesix.ai"] = &domain.DNSRecord{
ID: "rec-1", Type: "A", Name: "landing.threesix.ai",
Content: "208.122.204.172", TTL: 1,
}
dns.records["www.threesix.ai"] = &domain.DNSRecord{
ID: "rec-2", Type: "CNAME", Name: "www.threesix.ai",
Content: "landing.threesix.ai", TTL: 1,
}
req := httptest.NewRequest("GET", "/projects/landing/domains", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
var resp map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("unmarshal: %v", err)
}
data := resp["data"].(map[string]any)
total := int(data["total"].(float64))
if total != 2 {
t.Errorf("total = %d, want 2 (A + CNAME)", total)
}
})
t.Run("DNS not configured", func(t *testing.T) {
h := NewInfrastructureHandler(nil, nil, nil, nil, nil, InfrastructureConfig{
DefaultGitOwner: "threesix",
DefaultDomain: "threesix.ai",
ClusterIP: "208.122.204.172",
})
r := chi.NewRouter()
h.Mount(r)
req := httptest.NewRequest("GET", "/projects/myapp/domains", nil)
rec := httptest.NewRecorder()
r.ServeHTTP(rec, req)
if rec.Code != http.StatusInternalServerError {
t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
}
})
}
func TestInfrastructureHandler_AddDomainAlias(t *testing.T) {
t.Run("add A record alias", func(t *testing.T) {
_, _, dns, _, router := setupInfraHandler()
body, _ := json.Marshal(DomainAliasRequest{Domain: "www.threesix.ai"})
req := httptest.NewRequest("POST", "/projects/landing/domains", bytes.NewReader(body))
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusCreated {
t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusCreated, rec.Body.String())
}
if len(dns.records) != 1 {
t.Errorf("DNS records = %d, want 1", len(dns.records))
}
})
t.Run("add CNAME alias", func(t *testing.T) {
_, _, dns, _, router := setupInfraHandler()
body, _ := json.Marshal(DomainAliasRequest{
Domain: "www.threesix.ai",
Type: "CNAME",
})
req := httptest.NewRequest("POST", "/projects/landing/domains", bytes.NewReader(body))
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusCreated {
t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusCreated, rec.Body.String())
}
// CNAME should target landing.threesix.ai
for _, r := range dns.records {
if r.Type != "CNAME" {
t.Errorf("type = %s, want CNAME", r.Type)
}
if r.Content != "landing.threesix.ai" {
t.Errorf("content = %s, want landing.threesix.ai", r.Content)
}
}
})
t.Run("invalid type", func(t *testing.T) {
_, _, _, _, router := setupInfraHandler()
body, _ := json.Marshal(DomainAliasRequest{Domain: "www.threesix.ai", Type: "MX"})
req := httptest.NewRequest("POST", "/projects/landing/domains", bytes.NewReader(body))
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
}
})
t.Run("missing domain", func(t *testing.T) {
_, _, _, _, router := setupInfraHandler()
body, _ := json.Marshal(DomainAliasRequest{})
req := httptest.NewRequest("POST", "/projects/landing/domains", bytes.NewReader(body))
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
}
})
t.Run("DNS not configured", func(t *testing.T) {
h := NewInfrastructureHandler(nil, nil, nil, nil, nil, InfrastructureConfig{
DefaultGitOwner: "threesix",
DefaultDomain: "threesix.ai",
ClusterIP: "208.122.204.172",
})
r := chi.NewRouter()
h.Mount(r)
body, _ := json.Marshal(DomainAliasRequest{Domain: "www.threesix.ai"})
req := httptest.NewRequest("POST", "/projects/landing/domains", bytes.NewReader(body))
rec := httptest.NewRecorder()
r.ServeHTTP(rec, req)
if rec.Code != http.StatusInternalServerError {
t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
}
})
}
func TestInfrastructureHandler_RemoveDomainAlias(t *testing.T) {
t.Run("removes alias", func(t *testing.T) {
_, _, dns, _, router := setupInfraHandler()
dns.records["www"] = &domain.DNSRecord{
ID: "rec-www", Type: "A", Name: "www",
Content: "208.122.204.172",
}
req := httptest.NewRequest("DELETE", "/projects/landing/domains/www.threesix.ai", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusOK, rec.Body.String())
}
})
t.Run("prevents removing primary domain", func(t *testing.T) {
_, _, _, _, router := setupInfraHandler()
req := httptest.NewRequest("DELETE", "/projects/landing/domains/landing.threesix.ai", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
}
})
t.Run("not found", func(t *testing.T) {
_, _, dns, _, router := setupInfraHandler()
dns.err = nil // No records stored
req := httptest.NewRequest("DELETE", "/projects/landing/domains/nonexistent.threesix.ai", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Errorf("status = %d, want %d; body: %s", rec.Code, http.StatusNotFound, rec.Body.String())
}
})
}
func TestIsProjectDomain(t *testing.T) {
tests := []struct {
name, projectID, baseDomain string
want bool
}{
{"landing.threesix.ai", "landing", "threesix.ai", true},
{"other.threesix.ai", "landing", "threesix.ai", false},
{"landing.example.com", "landing", "threesix.ai", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isProjectDomain(tt.name, tt.projectID, tt.baseDomain)
if got != tt.want {
t.Errorf("isProjectDomain(%q, %q, %q) = %v, want %v",
tt.name, tt.projectID, tt.baseDomain, got, tt.want)
}
})
}
}