internal/argocd/diagnose_test.go
Rama main · réplica de lectura del repositorio original.
package argocd
import (
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/sophotechlabs/spinoza/internal/api"
)
func issueTitled(issues []api.GitopsIssue, title string) (api.GitopsIssue, bool) {
for _, one := range issues {
if one.Title == title {
return one, true
}
}
return api.GitopsIssue{}, false
}
func issueAbout(issues []api.GitopsIssue, subject string) (api.GitopsIssue, bool) {
for _, one := range issues {
if one.Subject == subject {
return one, true
}
}
return api.GitopsIssue{}, false
}
func TestAHealthyApplicationHasNoIssues(t *testing.T) {
if got := Detail(detailed()); got.Issues != nil {
t.Fatalf("issues = %+v, want none", got.Issues)
}
}
func TestConditionsBecomeTypedIssues(t *testing.T) {
app := detailed()
_ = unstructured.SetNestedSlice(app.Object, []any{
map[string]any{"type": "ComparisonError", "message": "authentication required"},
map[string]any{"type": "OrphanedResourceWarning", "message": "Secret/db is orphaned"},
map[string]any{"type": "SomethingElse", "message": "who knows"},
}, "status", "conditions")
issues := Detail(app).Issues
comparison, found := issueAbout(issues, "ComparisonError")
if !found || comparison.Severity != api.SeverityMedium {
t.Fatalf("comparison issue = %+v, want it degraded", comparison)
}
if !strings.Contains(comparison.Detail, "repository credentials") {
t.Fatalf("detail = %q, want it to say what to do", comparison.Detail)
}
orphaned, found := issueAbout(issues, "OrphanedResourceWarning")
if !found || orphaned.Severity != api.SeverityLow {
t.Fatalf("orphan issue = %+v, want a warning", orphaned)
}
other, found := issueAbout(issues, "SomethingElse")
if !found || other.Severity != api.SeverityLow {
t.Fatalf("unclassified issue = %+v, want info", other)
}
if other.Detail != "" {
t.Fatalf("detail = %q, want nothing invented for a type we do not know", other.Detail)
}
}
func TestConditionsWithoutATypeAreSkipped(t *testing.T) {
app := detailed()
_ = unstructured.SetNestedSlice(app.Object, []any{
map[string]any{"message": "no type here"},
"not a map",
}, "status", "conditions")
if got := Detail(app); got.Issues != nil {
t.Fatalf("issues = %+v, want none", got.Issues)
}
}
func TestAFailedOperationBecomesAnIssueWithItsCause(t *testing.T) {
app := detailed()
_ = unstructured.SetNestedField(app.Object, "Failed", "status", "operationState", "phase")
_ = unstructured.SetNestedField(app.Object, "Operation terminated", "status", "operationState", "message")
issue, found := issueAbout(Detail(app).Issues, "operation")
if !found {
t.Fatal("a failed operation raised no issue")
}
if issue.Title != "The last operation failed" {
t.Fatalf("title = %q", issue.Title)
}
if !strings.HasPrefix(issue.Detail, "the operation was stopped") {
t.Fatalf("detail = %q, want the cause first", issue.Detail)
}
if !strings.HasSuffix(issue.Detail, "Operation terminated") {
t.Fatalf("detail = %q, want the raw message kept", issue.Detail)
}
}
func TestAFailureWithNoKnownCauseKeepsTheRawMessage(t *testing.T) {
app := detailed()
_ = unstructured.SetNestedField(app.Object, "Error", "status", "operationState", "phase")
_ = unstructured.SetNestedField(app.Object, "something odd happened", "status", "operationState", "message")
issue, _ := issueAbout(Detail(app).Issues, "operation")
if issue.Detail != "something odd happened" {
t.Fatalf("detail = %q, want the message as it came", issue.Detail)
}
}
func TestAFailureWithNoMessageAtAllStatesTheCauseAlone(t *testing.T) {
if got := detailOf(""); got != "" {
t.Fatalf("detail = %q, want nothing", got)
}
}
func TestARunningOperationIsNotAnIssue(t *testing.T) {
app := operating(detailed(), runningPhase)
if _, found := issueAbout(Detail(app).Issues, "operation"); found {
t.Fatal("an operation still running was reported as a problem")
}
}
func TestDriftWithAutoSyncOffSaysNothingWillReconcileIt(t *testing.T) {
app := automating(detailed(), map[string]any{"enabled": false})
_ = unstructured.SetNestedField(app.Object, outOfSync, "status", "sync", "status")
issue, found := issueAbout(Detail(app).Issues, "drift")
if !found {
t.Fatal("drift with auto-sync off raised no issue")
}
if issue.Title != "Nothing will reconcile this" {
t.Fatalf("title = %q", issue.Title)
}
}
func TestDriftThatKeepsComingBackIsCalledOut(t *testing.T) {
app := detailed()
_ = unstructured.SetNestedField(app.Object, outOfSync, "status", "sync", "status")
issue, found := issueAbout(Detail(app).Issues, "drift")
if !found {
t.Fatal("a synced-then-out-of-sync loop raised no issue")
}
if issue.Title != "Synced, and out of sync again" {
t.Fatalf("title = %q", issue.Title)
}
if !strings.Contains(issue.Detail, "mutating webhook") {
t.Fatalf("detail = %q, want the likely culprits named", issue.Detail)
}
}
func TestDriftIsNotCalledALoopWhileTheLastSyncFailed(t *testing.T) {
app := detailed()
_ = unstructured.SetNestedField(app.Object, outOfSync, "status", "sync", "status")
_ = unstructured.SetNestedField(app.Object, "Failed", "status", "operationState", "phase")
if _, found := issueAbout(Detail(app).Issues, "drift"); found {
t.Fatal("a failed sync was reported as a drift loop")
}
}
func TestASyncedApplicationRaisesNoDriftIssue(t *testing.T) {
if _, found := issueAbout(Detail(detailed()).Issues, "drift"); found {
t.Fatal("a synced application was reported as drifting")
}
}
func TestBrokenResourcesEachGetAnIssue(t *testing.T) {
app := detailed()
_ = unstructured.SetNestedSlice(app.Object, []any{
map[string]any{
"group": "apps", "version": "v1", "kind": "Deployment", "name": "podinfo", "namespace": "web",
"status": "Synced", "health": map[string]any{"status": "Degraded", "message": "0/3 ready"},
},
map[string]any{
"version": "v1", "kind": "Service", "name": "podinfo", "namespace": "web",
"status": "OutOfSync", "health": map[string]any{"status": "Missing"},
},
}, "status", "resources")
issues := Detail(app).Issues
broken, found := issueAbout(issues, "Deployment/podinfo")
if !found || broken.Severity != api.SeverityHigh {
t.Fatalf("degraded issue = %+v, want the same word the issues queue uses", broken)
}
if broken.Detail != "0/3 ready" {
t.Fatalf("detail = %q, want the health message", broken.Detail)
}
gone, found := issueAbout(issues, "Service/podinfo")
if !found || gone.Severity != api.SeverityMedium {
t.Fatalf("missing issue = %+v, want it ranked under a degraded one", gone)
}
}
func TestAResourceTheOperationAlreadyBlamedIsNotRepeated(t *testing.T) {
app := detailed()
_ = unstructured.SetNestedField(app.Object, "Failed", "status", "operationState", "phase")
_ = unstructured.SetNestedField(app.Object, `Deployment "podinfo" failed to apply`, "status", "operationState", "message")
_ = unstructured.SetNestedSlice(app.Object, []any{
map[string]any{
"group": "apps", "version": "v1", "kind": "Deployment", "name": "podinfo", "namespace": "web",
"health": map[string]any{"status": "Degraded"},
},
}, "status", "resources")
if _, found := issueAbout(Detail(app).Issues, "Deployment/podinfo"); found {
t.Fatal("the resource the operation already named appeared a second time")
}
}
func TestTheSameBrokenResourceIsCountedOnce(t *testing.T) {
app := detailed()
entry := map[string]any{
"group": "apps", "version": "v1", "kind": "Deployment", "name": "podinfo", "namespace": "web",
"health": map[string]any{"status": "Degraded"},
}
_ = unstructured.SetNestedSlice(app.Object, []any{entry, entry}, "status", "resources")
count := 0
for _, one := range Detail(app).Issues {
if one.Subject == "Deployment/podinfo" {
count++
}
}
if count != 1 {
t.Fatalf("the same broken deployment raised %d issues, want 1", count)
}
}
func TestHealthyResourcesRaiseNoIssue(t *testing.T) {
if _, found := issueAbout(Detail(detailed()).Issues, "Deployment/podinfo"); found {
t.Fatal("a healthy deployment raised an issue")
}
}
func TestAnApplicationBeingDeletedNamesTheFinalizersHoldingIt(t *testing.T) {
app := detailed()
now := metav1.Now()
app.SetDeletionTimestamp(&now)
app.SetFinalizers([]string{"resources-finalizer.argocd.argoproj.io"})
issue, found := issueTitled(Detail(app).Issues, "This application is being deleted")
if !found {
t.Fatal("an application being deleted raised no issue")
}
if !strings.Contains(issue.Detail, "resources-finalizer.argocd.argoproj.io") {
t.Fatalf("detail = %q, want the finalizer named", issue.Detail)
}
}
func TestAnApplicationBeingDeletedWithNoFinalizersSaysSo(t *testing.T) {
app := detailed()
now := metav1.Now()
app.SetDeletionTimestamp(&now)
app.SetFinalizers(nil)
issue, _ := issueTitled(Detail(app).Issues, "This application is being deleted")
if !strings.Contains(issue.Detail, "no finalizers left") {
t.Fatalf("detail = %q", issue.Detail)
}
}
func TestAFailedOperationIsAsFatalHereAsItIsInTheQueue(t *testing.T) {
app := detailed()
_ = unstructured.SetNestedField(app.Object, "Failed", "status", "operationState", "phase")
issue, _ := issueAbout(Detail(app).Issues, "operation")
if issue.Severity != api.SeverityHigh {
t.Fatalf("severity = %q, want %q so one failure reads the same on both surfaces", issue.Severity, api.SeverityHigh)
}
}
func TestEverySeverityComesFromTheSharedVocabulary(t *testing.T) {
known := map[string]bool{
api.SeverityHigh: true,
api.SeverityMedium: true,
api.SeverityLow: true,
}
app := detailed()
_ = unstructured.SetNestedField(app.Object, "Failed", "status", "operationState", "phase")
_ = unstructured.SetNestedField(app.Object, outOfSync, "status", "sync", "status")
_ = unstructured.SetNestedSlice(app.Object, []any{
map[string]any{"type": "ComparisonError", "message": "nope"},
map[string]any{"type": "OrphanedResourceWarning", "message": "stray"},
map[string]any{"type": "SomethingElse", "message": "who knows"},
}, "status", "conditions")
_ = unstructured.SetNestedSlice(app.Object, []any{
map[string]any{"kind": "Service", "name": "api", "health": map[string]any{"status": "Missing"}},
}, "status", "resources")
issues := Detail(app).Issues
if len(issues) < 4 {
t.Fatalf("issues = %+v, want the whole spread", issues)
}
for _, one := range issues {
if !known[one.Severity] {
t.Fatalf("severity = %q on %q, want one the queue also uses", one.Severity, one.Title)
}
}
}