Major changes: - Add internal/logging package with field constants, context propagation, sensitive data auto-redaction, and per-component log levels - Add worker timeout constants (TimeoutQuickOp, TimeoutHealthCheck, etc.) - Extend SDLC with callback handlers, generate endpoints, and executor - Add new cookbook trees for aeries and slackpath progression - Add skeleton templates for queue, realtime, and microservices - Add worker component template with async job processing - Refactor services and handlers to use new logging infrastructure - Split component.go into component_infra.go and component_listing.go Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
149 lines
3.9 KiB
Go
149 lines
3.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"github.com/orchard9/rdev/internal/domain"
|
|
)
|
|
|
|
// mockDiagnosticsGetter implements DiagnosticsGetter for testing.
|
|
type mockDiagnosticsGetter struct {
|
|
diagnostics *domain.ProjectDiagnostics
|
|
err error
|
|
}
|
|
|
|
func (m *mockDiagnosticsGetter) GetDiagnostics(_ context.Context, projectID string) (*domain.ProjectDiagnostics, error) {
|
|
if m.err != nil {
|
|
return nil, m.err
|
|
}
|
|
if m.diagnostics != nil {
|
|
return m.diagnostics, nil
|
|
}
|
|
// Return default healthy diagnostics
|
|
return &domain.ProjectDiagnostics{
|
|
ProjectID: projectID,
|
|
GeneratedAt: time.Now().UTC(),
|
|
Summary: domain.DiagnosticsSummaryHealthy,
|
|
Issues: []domain.DiagnosticIssue{},
|
|
}, nil
|
|
}
|
|
|
|
func TestDiagnosticsHandler_GetDiagnostics_Success(t *testing.T) {
|
|
getter := &mockDiagnosticsGetter{}
|
|
projects := newMockProjectRepo()
|
|
h := NewDiagnosticsHandler(getter, projects)
|
|
|
|
// Create router with chi to handle URL params
|
|
r := chi.NewRouter()
|
|
r.Use(testAdminAuth) // Add auth context for tests
|
|
h.Mount(r)
|
|
|
|
req := httptest.NewRequest("GET", "/projects/test-project/diagnostics/", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
r.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("GetDiagnostics() status = %d, want %d; body = %s", rec.Code, http.StatusOK, rec.Body.String())
|
|
}
|
|
|
|
var resp map[string]any
|
|
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
data, ok := resp["data"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("response missing data field")
|
|
}
|
|
|
|
if data["project_id"] != "test-project" {
|
|
t.Errorf("project_id = %q, want %q", data["project_id"], "test-project")
|
|
}
|
|
if data["summary"] != domain.DiagnosticsSummaryHealthy {
|
|
t.Errorf("summary = %q, want %q", data["summary"], domain.DiagnosticsSummaryHealthy)
|
|
}
|
|
}
|
|
|
|
func TestDiagnosticsHandler_GetDiagnostics_WithIssues(t *testing.T) {
|
|
getter := &mockDiagnosticsGetter{
|
|
diagnostics: &domain.ProjectDiagnostics{
|
|
ProjectID: "unhealthy-project",
|
|
GeneratedAt: time.Now().UTC(),
|
|
Summary: domain.DiagnosticsSummaryUnhealthy,
|
|
Issues: []domain.DiagnosticIssue{
|
|
{
|
|
Severity: domain.DiagnosticSeverityError,
|
|
Source: domain.DiagnosticSourceCI,
|
|
Message: "CI build #42 failed",
|
|
},
|
|
{
|
|
Severity: domain.DiagnosticSeverityWarning,
|
|
Source: domain.DiagnosticSourceRegistry,
|
|
Message: "Container registry slow",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
projects := newMockProjectRepo()
|
|
h := NewDiagnosticsHandler(getter, projects)
|
|
|
|
r := chi.NewRouter()
|
|
r.Use(testAdminAuth) // Add auth context for tests
|
|
h.Mount(r)
|
|
|
|
req := httptest.NewRequest("GET", "/projects/unhealthy-project/diagnostics/", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
r.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("GetDiagnostics() status = %d, want %d", rec.Code, http.StatusOK)
|
|
}
|
|
|
|
var resp map[string]any
|
|
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
data, ok := resp["data"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("response missing data field")
|
|
}
|
|
|
|
if data["summary"] != domain.DiagnosticsSummaryUnhealthy {
|
|
t.Errorf("summary = %q, want %q", data["summary"], domain.DiagnosticsSummaryUnhealthy)
|
|
}
|
|
|
|
issues, ok := data["issues"].([]any)
|
|
if !ok {
|
|
t.Fatalf("response missing issues field")
|
|
}
|
|
if len(issues) != 2 {
|
|
t.Errorf("issues count = %d, want %d", len(issues), 2)
|
|
}
|
|
}
|
|
|
|
func TestDiagnosticsHandler_GetDiagnostics_MissingProjectID(t *testing.T) {
|
|
getter := &mockDiagnosticsGetter{}
|
|
projects := newMockProjectRepo()
|
|
h := NewDiagnosticsHandler(getter, projects)
|
|
|
|
// Direct call without chi router to test missing projectId
|
|
req := httptest.NewRequest("GET", "/projects//diagnostics/", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
h.GetDiagnostics(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("GetDiagnostics() status = %d, want %d", rec.Code, http.StatusBadRequest)
|
|
}
|
|
}
|