package main import ( "encoding/json" "strings" "testing" "time" "github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/conditions" "github.com/novox/mesh-controller/internal/link" ) // A provider that keeps failing a consumer is a problem `status` names (novox/hq ADR 0224), kept as // the condition store's first kind (to-be 45 §2). On 2026-10-05 the identity provider refused every // consumer for a day and status called the mesh well (04-ISSUES/179): this is that day, told to the // controller the way the provider now tells it. func TestAProviderFailingAConsumerBreaksAllWellUntilItRecovers(t *testing.T) { open := aMesh(t) ctx := t.Context() register(t, open, catalogue.Manifest{Module: "idp", Version: "1", Receives: map[string]string{"oidc-client": "/var/lib/mesh/idp/mesh.json"}}) if _, err := assign(ctx, open, "anchor", "idp"); err != nil { t.Fatal(err) } kept := standings{keeper: func() *conditions.Keeper { return conditionsFrom }} since := time.Now().Add(-23 * time.Hour) failing := link.Standing{Module: "idp", Failing: true, Provider: "oidc-client", ProviderNode: "anchor", Consumer: "mesh_laptop_dashboard", Node: "laptop", Class: "credentials-rejected", Error: `Keycloak token request failed: 401 {"error":"invalid_grant"}`, Since: since, Attempts: 31000} if _, err := kept.Stood(ctx, failing); err != nil { t.Fatal(err) } asked, err := theThreeQuestions(ctx, open) if err != nil { t.Fatal(err) } if asked.well() { t.Fatal("a mesh whose identity provider fails a consumer reads as well") } said := printed(t, func() error { return printStatus(asked) }) for _, want := range []string{"1 open condition(s)", "provider.idp.anchor.mesh_laptop_dashboard.failing", "idp on anchor keeps failing mesh_laptop_dashboard on laptop", "credentials-rejected", "31000 attempt(s)"} { if !strings.Contains(said, want) { t.Fatalf("status does not say %q:\n%s", want, said) } } if strings.Contains(said, "all doing what they were told") { t.Fatalf("status said all well beside a failing provider:\n%s", said) } if !strings.HasPrefix(said, "1 open condition(s)") { t.Fatalf("status does not lead with what is open:\n%s", said) } body, err := statusAsJSON(asked) if err != nil { t.Fatal(err) } var doc struct { Conditions []conditions.Condition `json:"conditions"` Failing []conditions.Condition `json:"failing"` } if err := json.Unmarshal(body, &doc); err != nil || len(doc.Failing) != 1 || len(doc.Conditions) != 1 || !strings.Contains(doc.Failing[0].Evidence[0].Said, "invalid_grant") { t.Fatalf("the document does not carry it: %v\n%s", err, body) } // Both machines' `node show` name it: where the provider runs, and where the consumer is. for _, node := range []string{"anchor", "laptop"} { shown := printed(t, func() error { return showNode(ctx, open.inventory, node) }) if !strings.Contains(shown, "open condition(s) about this machine") || !strings.Contains(shown, "mesh_laptop_dashboard") { t.Fatalf("node show %s does not name it:\n%s", node, shown) } } // Recovered: gone, and the mesh may be well again as far as this is concerned. failing.Failing = false if cleared, err := kept.Stood(ctx, failing); err != nil || !cleared { t.Fatalf("%v %v", cleared, err) } asked, err = theThreeQuestions(ctx, open) if err != nil { t.Fatal(err) } if len(asked.conditions) != 0 { t.Fatalf("a recovered consumer is still named: %+v", asked.conditions) } } // A provider no longer assigned where it ran has nothing running to fail anybody: its last word is // cleared on the next look, said as resolved by the assignment. func TestAnUnassignedProvidersLastWordIsCleared(t *testing.T) { open := aMesh(t) ctx := t.Context() kept := standings{keeper: func() *conditions.Keeper { return conditionsFrom }} if _, err := kept.Stood(ctx, link.Standing{Module: "gone", Failing: true, ProviderNode: "anchor", Consumer: "x", Since: time.Now()}); err != nil { t.Fatal(err) } all, err := conditionsFrom.Open(ctx) if err != nil || len(all) != 1 { t.Fatalf("%+v %v", all, err) } if err := unassignedProviders(ctx, open.inventory, conditionsFrom, all); err != nil { t.Fatal(err) } if all, _ := conditionsFrom.Open(ctx); len(all) != 0 { t.Fatalf("%+v", all) } } // **The store away holds a recovery** (ADR 0224 §3): a standing that cannot be kept is asked again. func TestAStandingTheStoreCannotKeepIsAskedAgain(t *testing.T) { kept := standings{keeper: func() *conditions.Keeper { return nil }} if _, err := kept.Stood(t.Context(), link.Standing{Module: "idp", ProviderNode: "anchor", Consumer: "x"}); err == nil || !strings.Contains(err.Error(), link.ErrTryAgain.Error()) { t.Fatalf("answered %v", err) } }