package main import ( "encoding/json" "errors" "fmt" "strings" "testing" "time" "git.novox.be/novox/mesh-sdk/go/asks" ) // sdkCondition is a raised warning in mesh-sdk's layout, as a module says it on the operator channel's one subject. func sdkCondition(needs string) asks.Condition { verdict := asks.NothingToDo if needs != "" { verdict = asks.NeedsYou + needs } return asks.Condition{Key: "merge.d-7.stalled", Severity: asks.SeverityWarning, Subject: asks.Scope{Scope: "merge", ID: "d-7"}, State: asks.StateRaised, Summary: "the merge waits on a check", Headline: "A merge waits on a check", Needs: needs, Explanation: verdict + " It waits on a check."} } func conditionBody(t *testing.T, c asks.Condition) json.RawMessage { t.Helper() b, err := asks.EncodeCondition(c) if err != nil { t.Fatal(err) } return b } func TestARaisedConditionSaidOnTheOneSubjectIsToldAsTheControllersIs(t *testing.T) { // 1217/AC1 h, tg, dt, c, _ := newHolder(t) if err := h.TakeAsk("condition", "mesh-delivery-teller", conditionBody(t, sdkCondition("look at it."))); err != nil { t.Fatalf("not acknowledged: %v", err) // 1217/AC1: an error would offer the work again } open := h.Open() if len(open) != 1 || open[0].Key != "merge.d-7.stalled" || open[0].Subject != "merge d-7" || open[0].Origin != "condition" { t.Fatalf("open: %+v", open) } settle(h, c) if len(dt.sends) != 1 || len(tg.sends) != 0 { t.Fatalf("a warning goes to the desk when a session answers: desktop %d telegram %d", len(dt.sends), len(tg.sends)) } dt.fail = errors.New("the account is not logged in") c.pass(DesktopWarningEvery) k := sdkCondition("look at it.") k.Key, k.Subject.ID = "merge.d-8.stalled", "d-8" if err := h.TakeAsk("condition", "mesh-delivery-teller", conditionBody(t, k)); err != nil { t.Fatal(err) } settle(h, c) // the desktop fails: handed to Telegram h.Flush() if len(tg.sends) != 1 { t.Fatalf("no session: telegram %d", len(tg.sends)) } } func TestTheSameConditionTakenTwiceIsSaidOnce(t *testing.T) { // 1217/AC2 h, tg, dt, c, _ := newHolder(t) body := conditionBody(t, sdkCondition("look at it.")) for range 2 { if err := h.TakeAsk("condition", "mesh-delivery-teller", body); err != nil { t.Fatal(err) } } settle(h, c) if len(dt.sends)+len(tg.sends) != 1 { t.Fatalf("said %d times, want once", len(dt.sends)+len(tg.sends)) } if open := h.Open(); len(open) != 1 { t.Fatalf("open: %+v", open) } } func TestAClearedConditionIsClearedAsTheControllersIs(t *testing.T) { // 1217/AC3 h, _, _, c, _ := newHolder(t) if err := h.TakeAsk("condition", "mesh-delivery-teller", conditionBody(t, sdkCondition("look at it."))); err != nil { t.Fatal(err) } settle(h, c) cleared := asks.Condition{Key: "merge.d-7.stalled", Subject: asks.Scope{Scope: "merge", ID: "d-7"}, State: asks.StateCleared} if err := h.TakeAsk("condition", "mesh-delivery-teller", conditionBody(t, cleared)); err != nil { t.Fatal(err) } if open := h.Open(); len(open) != 0 { t.Fatalf("still open: %+v", open) } // The same as the controller's clearing of the same key. h2, _, _, c2, _ := newHolder(t) h2.Condition(EventRaised, cond("merge.d-7.stalled", Warning, "the merge waits on a check")) settle(h2, c2) h2.Condition(EventCleared, Condition{Key: "merge.d-7.stalled"}) if len(h2.Open()) != 0 { t.Fatal("the controller's clearing leaves it open") } } func TestAWarningThatNeedsNothingIsKeptQuiet(t *testing.T) { // 1217/AC4 h, tg, dt, c, _ := newHolder(t) if err := h.TakeAsk("condition", "mesh-delivery-teller", conditionBody(t, sdkCondition(""))); err != nil { t.Fatal(err) } settle(h, c) if len(dt.sends)+len(tg.sends) != 0 { t.Fatalf("a quiet warning was told: desktop %d telegram %d", len(dt.sends), len(tg.sends)) } if len(h.Open()) != 1 { t.Fatalf("it is still recorded: %+v", h.Open()) } } func TestABodyThatIsNotAConditionIsDroppedAndTheLogNamesThePublisher(t *testing.T) { // 1217/AC5 for name, body := range map[string]string{ "bad JSON": `{"v":1,`, "unknown version": `{"v":9,"key":"merge.d-7.stalled","subject":{"scope":"merge","id":"d-7"},"state":"cleared"}`, "no version": `{"key":"merge.d-7.stalled","subject":{"scope":"merge","id":"d-7"},"state":"cleared"}`, } { h, tg, dt, c, _ := newHolder(t) var logged []string h.Logf = func(f string, a ...any) { logged = append(logged, strings.TrimSpace(fmt.Sprintf(f, a...))) } if err := h.TakeAsk("condition", "mesh-delivery-teller", json.RawMessage(body)); err != nil { t.Fatalf("%s: the work is taken and dropped, got %v", name, err) } settle(h, c) if len(h.Open()) != 0 || len(dt.sends)+len(tg.sends) != 0 { t.Fatalf("%s: something was recorded or told", name) } if !strings.Contains(strings.Join(logged, "\n"), "mesh-delivery-teller") { t.Fatalf("%s: the log does not name the publisher: %q", name, logged) } } } func TestAModulesConditionStaysOpenWhenTheControllersConditionsAreRead(t *testing.T) { // 1217/AC7 h, tg, dt, c, _ := newHolder(t) body := conditionBody(t, sdkCondition("look at it.")) if err := h.TakeAsk("condition", "mesh-delivery-teller", body); err != nil { t.Fatal(err) } settle(h, c) c.pass(time.Second) res := h.Sync(nil, c.now()) // the controller lists nothing: the teller's condition is not its to end if len(res.Gone) != 0 || len(h.Open()) != 1 { t.Fatalf("the reading ended a module's condition: gone %v, open %+v", res.Gone, h.Open()) } if err := h.TakeAsk("condition", "mesh-delivery-teller", body); err != nil { // raised again: the same message t.Fatal(err) } settle(h, c) if len(dt.sends)+len(tg.sends) != 1 { t.Fatalf("said %d times, want once", len(dt.sends)+len(tg.sends)) } }