//go:build integration package main import ( "context" "strings" "testing" "time" "github.com/novox/mesh-controller/internal/broker" "github.com/novox/mesh-controller/internal/link" "github.com/novox/mesh-controller/internal/testbus" ) func servingDeadLetters(t *testing.T) *broker.JetStream { t.Helper() js, err := broker.Dial(testbus.URL(t)) if err != nil { t.Fatal(err) } t.Cleanup(js.Close) if err := broker.AssertMeshStreams(js); err != nil { t.Fatal(err) } if err := js.EnsureControllerBuckets(); err != nil { t.Fatal(err) } before := servingBus.Load() servingBus.Store(js) t.Cleanup(func() { servingBus.Store(before) }) return js } func TestDeadLettersListsDropsAndRecordsWhy(t *testing.T) { js := servingDeadLetters(t) if _, err := js.Context().Publish("mesh.mod.gitea.event.pull.merged", []byte(`{"n":1}`)); err != nil { t.Fatal(err) } d, err := link.KeepDeadLetter(js.Context(), []byte(`{"stream":"EVENTS","consumer":"media_sonarr","stream_seq":1,"deliveries":5}`)) if err != nil { t.Fatal(err) } answer, err := askDeadLetters(t, map[string]any{}) if err != nil { t.Fatal(err) } listed := answer.(map[string]any) if listed["held"] != 1 || len(listed["dead_letters"].([]link.DeadLetter)) != 1 { t.Fatalf("listed %v", listed) } // Refused before anything is done: an act without why, an argument the shape passes over, both acts. for _, args := range []map[string]any{ {"drop": "1"}, {"drop": "1", "why": "x", "limit": "3"}, {"drop": "1", "deliver": "1", "why": "x"}, {"why": "x"}, {"id": "nought"}, } { if _, err := askDeadLetters(t, args); err == nil { t.Errorf("%v was done", args) } } if _, total, _ := link.DeadLetters(js.Context(), "", 0); total != 1 { t.Fatalf("a refused call changed what is kept: %d left", total) } done, err := askDeadLetters(t, map[string]any{"drop": "1", "why": "the media server took the download in by hand"}) if err != nil { t.Fatal(err) } if said := done.(map[string]any); said["recorded"] == nil || !strings.Contains(said["done"].(string), "dropped") { t.Fatalf("answered %v", said) } acts, err := link.HandActs(context.Background(), js.Conn(), time.Now().Add(-time.Minute)) if err != nil || len(acts) != 1 || acts[0].Verb != "dead-letters drop" || acts[0].Cause != causeDeadLetter || !strings.Contains(acts[0].Condition, "media_sonarr") { t.Fatalf("recorded %+v (%v)", acts, err) } if !personsDecision(acts[0]) { t.Error("dropping a dead letter is counted as a repair, so S15 would want a healer for it") } if _, err := askDeadLetters(t, map[string]any{"id": "1"}); err == nil { t.Errorf("dead letter %d is still answered after it was dropped", d.ID) } }