internal/argocd/detail_test.go
Rama main · réplica de lectura del repositorio original.
package argocd
import (
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/sophotechlabs/spinoza/internal/api"
)
func detailed() *unstructured.Unstructured {
return &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "argoproj.io/v1alpha1",
"kind": "Application",
"metadata": map[string]any{
"name": "podinfo",
"namespace": "argocd",
"creationTimestamp": "2026-08-01T10:00:00Z",
},
"spec": map[string]any{
"project": "default",
"source": map[string]any{
"repoURL": "https://example.test/apps",
"path": "podinfo",
"targetRevision": "main",
},
"destination": map[string]any{"server": "https://kubernetes.default.svc", "namespace": "web"},
"syncPolicy": map[string]any{
"automated": map[string]any{"prune": true, "selfHeal": true},
},
},
"status": map[string]any{
"sync": map[string]any{"status": "Synced", "revision": "abc1234"},
"health": map[string]any{"status": "Healthy"},
"resources": []any{
map[string]any{
"group": "apps",
"version": "v1",
"kind": "Deployment",
"name": "podinfo",
"namespace": "web",
"status": "Synced",
"health": map[string]any{"status": "Healthy"},
},
},
"history": []any{
map[string]any{
"id": int64(0),
"revision": "abc1234",
"deployStartedAt": "2026-08-02T10:00:00Z",
"deployedAt": "2026-08-02T10:00:05Z",
"initiatedBy": map[string]any{"automated": true},
"source": map[string]any{"path": "podinfo"},
},
},
"operationState": map[string]any{
"phase": "Succeeded",
"message": "successfully synced",
"startedAt": "2026-08-02T10:00:00Z",
"finishedAt": "2026-08-02T10:00:05Z",
"operation": map[string]any{
"initiatedBy": map[string]any{"username": "spinoza"},
"sync": map[string]any{"revision": "abc1234"},
},
},
},
}}
}
func TestDetailSplitsConfigFromDeploymentState(t *testing.T) {
got := Detail(detailed())
if got.Controller != api.ControllerArgo {
t.Fatalf("controller = %q, want argocd", got.Controller)
}
if got.Source.Repo != "https://example.test/apps" || got.Source.Path != "podinfo" {
t.Fatalf("source = %+v, want the repo and path from the spec", got.Source)
}
if got.Source.Target != "main" {
t.Fatalf("target = %q, want main", got.Source.Target)
}
if got.Source.Destination != "https://kubernetes.default.svc web" {
t.Fatalf("destination = %q", got.Source.Destination)
}
if got.State.Sync != "Synced" || got.State.Health != "Healthy" {
t.Fatalf("state = %+v, want the controller's own verdict", got.State)
}
if got.State.Revision != "abc1234" {
t.Fatalf("revision = %q, want abc1234", got.State.Revision)
}
if got.State.SyncedAt != "2026-08-02T10:00:05Z" {
t.Fatalf("syncedAt = %q, want the finish of the last operation", got.State.SyncedAt)
}
}
func TestDetailReadsTheAutomationPolicy(t *testing.T) {
got := Detail(detailed())
if got.Source.SyncMode != api.SyncModeAuto {
t.Fatalf("syncMode = %q, want auto", got.Source.SyncMode)
}
if got.Source.Policy != "prune, self-heal" {
t.Fatalf("policy = %q, want both flags named", got.Source.Policy)
}
}
func TestDetailCallsAPausedApplicationSuspended(t *testing.T) {
app := automating(detailed(), map[string]any{"enabled": false, "prune": true})
got := Detail(app)
if got.Source.SyncMode != api.SyncModeSuspended {
t.Fatalf("syncMode = %q, want suspended", got.Source.SyncMode)
}
if got.Source.Policy != "prune" {
t.Fatalf("policy = %q, want prune kept", got.Source.Policy)
}
}
func TestDetailCallsAnApplicationWithNoAutomationManual(t *testing.T) {
got := Detail(newApplication())
if got.Source.SyncMode != api.SyncModeManual {
t.Fatalf("syncMode = %q, want manual", got.Source.SyncMode)
}
if got.Source.Policy != "" {
t.Fatalf("policy = %q, want none", got.Source.Policy)
}
}
func TestDetailSaysWhenAutomationHasNeitherFlag(t *testing.T) {
got := Detail(automating(detailed(), map[string]any{}))
if got.Source.Policy != "neither prune nor self-heal" {
t.Fatalf("policy = %q", got.Source.Policy)
}
}
func TestDetailListsTheManagedResources(t *testing.T) {
got := Detail(detailed())
if len(got.Resources) != 1 {
t.Fatalf("resources = %+v, want the one the controller named", got.Resources)
}
one := got.Resources[0]
if one.Kind != "Deployment" || one.Name != "podinfo" || one.Namespace != "web" {
t.Fatalf("resource = %+v", one)
}
if one.Sync != "Synced" || one.Health != "Healthy" {
t.Fatalf("resource state = %+v", one)
}
}
func TestDetailSkipsResourceEntriesWithNoKindOrName(t *testing.T) {
app := detailed()
_ = unstructured.SetNestedSlice(app.Object, []any{
map[string]any{"kind": "Deployment"},
map[string]any{"name": "web"},
"not a map",
}, "status", "resources")
if got := Detail(app); len(got.Resources) != 0 {
t.Fatalf("resources = %+v, want none of the half-written entries", got.Resources)
}
}
func TestDetailReadsWhoDeployedEachRevision(t *testing.T) {
got := Detail(detailed())
if len(got.History) != 1 {
t.Fatalf("history = %+v, want one entry", got.History)
}
entry := got.History[0]
if entry.ID != 0 || entry.Revision != "abc1234" {
t.Fatalf("entry = %+v", entry)
}
if !entry.Automated {
t.Fatal("the entry was deployed by automation and does not say so")
}
if entry.Source != "podinfo" {
t.Fatalf("source = %q, want the path that entry recorded", entry.Source)
}
if entry.DeployedAt != "2026-08-02T10:00:05Z" {
t.Fatalf("deployedAt = %q", entry.DeployedAt)
}
}
func TestDetailSkipsHistoryEntriesWithNoID(t *testing.T) {
app := detailed()
_ = unstructured.SetNestedSlice(app.Object, []any{
map[string]any{"revision": "abc"},
"not a map",
}, "status", "history")
if got := Detail(app); len(got.History) != 0 {
t.Fatalf("history = %+v, want nothing without an id", got.History)
}
}
func TestDetailReportsTheLastOperation(t *testing.T) {
got := Detail(detailed())
if got.Operation == nil {
t.Fatal("no operation was reported")
}
if got.Operation.Phase != "Succeeded" || got.Operation.Running {
t.Fatalf("operation = %+v, want a finished one", got.Operation)
}
if got.Operation.InitiatedBy != "spinoza" {
t.Fatalf("initiatedBy = %q, want spinoza", got.Operation.InitiatedBy)
}
if got.Operation.Revision != "abc1234" {
t.Fatalf("revision = %q", got.Operation.Revision)
}
}
func TestDetailPinsAnOperationInFlight(t *testing.T) {
got := Detail(operating(detailed(), runningPhase))
if got.Operation == nil || !got.Operation.Running {
t.Fatalf("operation = %+v, want it marked as running", got.Operation)
}
}
func TestDetailTreatsTerminatingAsStillRunning(t *testing.T) {
got := Detail(operating(detailed(), terminatingPhase))
if got.Operation == nil || !got.Operation.Running {
t.Fatalf("operation = %+v, want it marked as running", got.Operation)
}
}
func TestDetailReportsNoOperationOnAnApplicationThatNeverSynced(t *testing.T) {
if got := Detail(newApplication()); got.Operation != nil {
t.Fatalf("operation = %+v, want nothing", got.Operation)
}
}
func TestDetailStatesTheCauseOfAFailedOperation(t *testing.T) {
app := detailed()
_ = unstructured.SetNestedField(app.Object, "Failed", "status", "operationState", "phase")
_ = unstructured.SetNestedField(app.Object,
`one or more objects failed to apply, reason: namespaces "web" not found`,
"status", "operationState", "message")
got := Detail(app)
want := "the destination namespace web does not exist; add CreateNamespace=true or create it in git"
if got.Operation.Cause != want {
t.Fatalf("cause = %q, want %q", got.Operation.Cause, want)
}
}
func TestDetailFallsBackToTheReconcileTimeWhenNothingSyncedYet(t *testing.T) {
app := newApplication()
_ = unstructured.SetNestedField(app.Object, "2026-08-03T09:00:00Z", "status", "reconciledAt")
if got := Detail(app); got.State.SyncedAt != "2026-08-03T09:00:00Z" {
t.Fatalf("syncedAt = %q, want the reconcile time", got.State.SyncedAt)
}
}
func TestDetailReadsAChartSourceAsThePath(t *testing.T) {
app := newApplication()
_ = unstructured.SetNestedMap(app.Object, map[string]any{
"repoURL": "https://charts.example.test",
"chart": "podinfo",
}, "spec", "source")
if got := Detail(app); got.Source.Path != "podinfo" {
t.Fatalf("path = %q, want the chart name", got.Source.Path)
}
}
func TestDetailCopesWithStatusEntriesThatCarryNoNestedMaps(t *testing.T) {
app := newApplication()
_ = unstructured.SetNestedSlice(app.Object, []any{
map[string]any{"kind": "Deployment", "name": "web"},
}, "status", "resources")
_ = unstructured.SetNestedSlice(app.Object, []any{
map[string]any{"id": int64(1), "revision": "abc"},
}, "status", "history")
_ = unstructured.SetNestedMap(app.Object, map[string]any{"phase": "Succeeded"}, "status", "operationState")
got := Detail(app)
if got.Resources[0].Health != "" || got.Resources[0].Message != "" {
t.Fatalf("resource = %+v, want no invented health", got.Resources[0])
}
if got.History[0].InitiatedBy != "" || got.History[0].Automated {
t.Fatalf("history = %+v, want no invented initiator", got.History[0])
}
if got.History[0].Source != "" {
t.Fatalf("source = %q, want nothing", got.History[0].Source)
}
if got.Operation.InitiatedBy != "" || got.Operation.Revision != "" {
t.Fatalf("operation = %+v, want no invented operation detail", got.Operation)
}
}
func TestDetailReadsNoRevisionFromAnOperationWithoutASync(t *testing.T) {
app := newApplication()
_ = unstructured.SetNestedMap(app.Object, map[string]any{
"phase": "Succeeded",
"operation": map[string]any{"initiatedBy": map[string]any{"username": "someone"}},
}, "status", "operationState")
got := Detail(app)
if got.Operation.Revision != "" {
t.Fatalf("revision = %q, want nothing", got.Operation.Revision)
}
if got.Operation.InitiatedBy != "someone" {
t.Fatalf("initiatedBy = %q", got.Operation.InitiatedBy)
}
}
func TestDetailIgnoresAnOperationStateWithNoPhase(t *testing.T) {
app := newApplication()
_ = unstructured.SetNestedMap(app.Object, map[string]any{"message": "nothing yet"}, "status", "operationState")
if got := Detail(app); got.Operation != nil {
t.Fatalf("operation = %+v, want nothing without a phase", got.Operation)
}
}
func TestTheHistorySourceLabelTakesEitherAPathOrAChart(t *testing.T) {
cases := []struct {
name string
entry map[string]any
want string
}{
{
name: "a path",
entry: map[string]any{"source": map[string]any{"path": "apps/web"}},
want: "apps/web",
},
{
name: "a chart when there is no path",
entry: map[string]any{"source": map[string]any{"chart": "podinfo"}},
want: "podinfo",
},
{
name: "a path wins over a chart beside it",
entry: map[string]any{"source": map[string]any{"path": "apps/web", "chart": "podinfo"}},
want: "apps/web",
},
{name: "a source that says neither", entry: map[string]any{"source": map[string]any{}}, want: ""},
{name: "an entry with no source", entry: map[string]any{}, want: ""},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := sourceLabelOf(tc.entry); got != tc.want {
t.Fatalf("sourceLabelOf(%v) = %q, want %q", tc.entry, got, tc.want)
}
})
}
}