Ctrl K
ComunidadGuías…

pkg/packages/gitops_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package packages

import (
	"reflect"
	"testing"
)

func TestParseArgoApplication_SingleSourceHelm(t *testing.T) {
	obj := map[string]any{
		"metadata": map[string]any{
			"namespace": "argocd",
			"name":      "cert-manager",
		},
		"spec": map[string]any{
			"destination": map[string]any{
				"namespace": "cert-manager",
				"name":      "in-cluster",
			},
			"source": map[string]any{
				"chart":          "cert-manager",
				"targetRevision": "1.14.0",
			},
		},
		"status": map[string]any{
			"health": map[string]any{"status": "Healthy"},
		},
	}
	d, ok := ParseArgoApplication(obj)
	if !ok {
		t.Fatal("expected ok")
	}
	want := Declaration{
		Source:          "argocd",
		Namespace:       "argocd",
		Name:            "cert-manager",
		TargetNamespace: "cert-manager",
		TargetName:      "cert-manager",
		Chart:           "cert-manager",
		ChartVersion:    "1.14.0",
		Status:          "healthy",
	}
	if !reflect.DeepEqual(d, want) {
		t.Errorf("got %+v\nwant %+v", d, want)
	}
}

// First Helm source wins; we don't model multiple charts per app.
func TestParseArgoApplication_MultiSourceFirstHelmWins(t *testing.T) {
	obj := map[string]any{
		"metadata": map[string]any{"name": "stack", "namespace": "argocd"},
		"spec": map[string]any{
			"destination": map[string]any{"namespace": "monitoring"},
			"sources": []any{
				map[string]any{"repoURL": "git@example.com/values.git", "path": "."},
				map[string]any{"chart": "prometheus", "targetRevision": "25.0.0"},
				map[string]any{"chart": "grafana", "targetRevision": "7.0.0"},
			},
		},
	}
	d, ok := ParseArgoApplication(obj)
	if !ok {
		t.Fatal("expected ok")
	}
	if d.Chart != "prometheus" || d.ChartVersion != "25.0.0" {
		t.Errorf("first Helm source should win, got chart=%q version=%q", d.Chart, d.ChartVersion)
	}
}

func TestParseArgoApplication_MissingNameRejected(t *testing.T) {
	if _, ok := ParseArgoApplication(map[string]any{"metadata": map[string]any{}}); ok {
		t.Error("expected !ok for missing metadata.name")
	}
	if _, ok := ParseArgoApplication(map[string]any{}); ok {
		t.Error("expected !ok for missing metadata")
	}
}

func TestMapArgoHealth(t *testing.T) {
	cases := map[string]Health{
		"Healthy":     HealthHealthy,
		"healthy":     HealthHealthy,
		"Progressing": HealthDegraded,
		"Degraded":    HealthUnhealthy,
		// Suspended = intentional pause, not a degradation — neutral (sky).
		"Suspended": HealthNeutral,
		// Missing means "should exist but doesn't" — surface as a
		// real signal (degraded), not as the same bucket as Unknown.
		"Missing": HealthDegraded,
		"Unknown": HealthUnknown,
		"":        HealthUnknown,
		"Bogus":   HealthUnknown,
	}
	for in, want := range cases {
		if got := mapArgoHealth(in); got != want {
			t.Errorf("mapArgoHealth(%q) = %q, want %q", in, got, want)
		}
	}
}

// v2 layout: spec.chart.spec.{chart,version}.
func TestParseFluxHelmRelease_V2Layout(t *testing.T) {
	obj := map[string]any{
		"metadata": map[string]any{"name": "redis", "namespace": "data"},
		"spec": map[string]any{
			"chart": map[string]any{
				"spec": map[string]any{"chart": "redis", "version": "18.0.0"},
			},
			"targetNamespace": "redis",
			"releaseName":     "redis-prod",
		},
		"status": map[string]any{
			"conditions": []any{
				map[string]any{"type": "Ready", "status": "True"},
			},
		},
	}
	d, ok := ParseFluxHelmRelease(obj)
	if !ok {
		t.Fatal("expected ok")
	}
	if d.Chart != "redis" || d.ChartVersion != "18.0.0" {
		t.Errorf("v2 chart shape, got chart=%q version=%q", d.Chart, d.ChartVersion)
	}
	if d.TargetNamespace != "redis" || d.TargetName != "redis-prod" {
		t.Errorf("target wrong: ns=%q name=%q", d.TargetNamespace, d.TargetName)
	}
	if d.Status != "healthy" {
		t.Errorf("Ready=True should map healthy, got %q", d.Status)
	}
}

// v2beta2 layout: spec.chart.{chart,version}.
func TestParseFluxHelmRelease_V2Beta2Layout(t *testing.T) {
	obj := map[string]any{
		"metadata": map[string]any{"name": "nginx", "namespace": "kube-system"},
		"spec": map[string]any{
			"chart": map[string]any{"chart": "nginx", "version": "1.5.0"},
		},
	}
	d, ok := ParseFluxHelmRelease(obj)
	if !ok {
		t.Fatal("expected ok")
	}
	if d.Chart != "nginx" || d.ChartVersion != "1.5.0" {
		t.Errorf("v2beta2 chart shape, got chart=%q version=%q", d.Chart, d.ChartVersion)
	}
	// targetNamespace falls back to the HR's own namespace when not set.
	if d.TargetNamespace != "kube-system" {
		t.Errorf("targetNamespace fallback, got %q", d.TargetNamespace)
	}
	// releaseName falls back to the HR's name.
	if d.TargetName != "nginx" {
		t.Errorf("releaseName fallback, got %q", d.TargetName)
	}
}

func TestParseFluxKustomization(t *testing.T) {
	obj := map[string]any{
		"metadata": map[string]any{"name": "platform", "namespace": "flux-system"},
		"spec": map[string]any{
			"targetNamespace": "platform",
		},
		"status": map[string]any{
			"conditions": []any{
				map[string]any{"type": "Ready", "status": "False", "reason": "ReconciliationFailed"},
			},
		},
	}
	d, ok := ParseFluxKustomization(obj)
	if !ok {
		t.Fatal("expected ok")
	}
	if d.Chart != "" {
		t.Errorf("Kustomization should leave Chart empty, got %q", d.Chart)
	}
	if d.TargetNamespace != "platform" || d.TargetName != "platform" {
		t.Errorf("target wrong: ns=%q name=%q", d.TargetNamespace, d.TargetName)
	}
	if d.Status != "unhealthy" {
		t.Errorf("Ready=False with hard reason should map unhealthy, got %q", d.Status)
	}
}

// Stalled=True overrides Ready=True — Flux uses Stalled to signal a
// terminal failure that needs operator intervention even if the previous
// reconciliation succeeded.
func TestFluxConditionStatus_StalledOverridesReady(t *testing.T) {
	obj := map[string]any{
		"status": map[string]any{
			"conditions": []any{
				map[string]any{"type": "Ready", "status": "True"},
				map[string]any{"type": "Stalled", "status": "True", "reason": "InstallFailed"},
			},
		},
	}
	if got := fluxConditionStatus(obj); got != "unhealthy" {
		t.Errorf("Stalled=True should override Ready=True, got %q", got)
	}
}

func TestFluxConditionStatus_TransientReasonDegraded(t *testing.T) {
	obj := map[string]any{
		"status": map[string]any{
			"conditions": []any{
				map[string]any{"type": "Ready", "status": "False", "reason": "Progressing"},
			},
		},
	}
	if got := fluxConditionStatus(obj); got != "degraded" {
		t.Errorf("Ready=False with transient reason should map degraded, got %q", got)
	}
}

func TestFluxConditionStatus_NoConditions(t *testing.T) {
	if got := fluxConditionStatus(map[string]any{}); got != "unknown" {
		t.Errorf("missing status → unknown, got %q", got)
	}
	if got := fluxConditionStatus(map[string]any{"status": map[string]any{}}); got != "unknown" {
		t.Errorf("empty status → unknown, got %q", got)
	}
}

// TestIsTransientConditionReason pins the shared transient-reason set that gates
// both the GitOps health badge (degraded vs unhealthy) and the Issues CRD noise
// floor (suppress vs emit). The membership of these reasons — and the deliberate
// exclusion of "Unknown" — is load-bearing; a silent edit would flip suppression
// behavior on both paths with no other failing test.
func TestIsTransientConditionReason(t *testing.T) {
	transient := []string{
		"Progressing", "DependencyNotReady", "ReconciliationInProgress", "ChartNotReady", "ArtifactFailed",
		"Reconciling", "Creating", "Issuing", "Pending", "InProgress", "Initializing", "Waiting",
	}
	for _, r := range transient {
		if !IsTransientConditionReason(r) {
			t.Errorf("IsTransientConditionReason(%q) = false, want true (in-progress reason)", r)
		}
	}
	// "Unknown" means the controller hasn't reported a verdict (ambiguous), not
	// in-progress — it must stay loud. Empty and hard-failure reasons too.
	for _, r := range []string{"Unknown", "", "BuildFailed", "InstallFailed"} {
		if IsTransientConditionReason(r) {
			t.Errorf("IsTransientConditionReason(%q) = true, want false", r)
		}
	}
}

// A False Ready whose reason is Unknown stays unhealthy — the loud-on-ambiguous
// invariant, complementing the transient-reason degraded path above.
func TestFluxConditionStatus_UnknownReasonStaysUnhealthy(t *testing.T) {
	obj := map[string]any{
		"status": map[string]any{
			"conditions": []any{
				map[string]any{"type": "Ready", "status": "False", "reason": "Unknown"},
			},
		},
	}
	if got := fluxConditionStatus(obj); got != "unhealthy" {
		t.Errorf("Ready=False reason=Unknown should stay unhealthy, got %q", got)
	}
}