package asks import ( "encoding/json" "errors" "strings" "testing" ) func TestConditionSubject(t *testing.T) { // 1202/AC1 s, err := ConditionSubject("mesh-delivery-teller") if err != nil || s != "mesh.seat.operator-channel.accept.condition.mesh-delivery-teller" { t.Fatalf("subject = %q, %v", s, err) } // The router splits an accept subject in six parts and reads the verb at 4 and the caller at 5. parts := strings.Split(s, ".") if len(parts) != 6 || parts[4] != "condition" || parts[5] != "mesh-delivery-teller" { t.Errorf("subject %q does not keep the router's shape: %v", s, parts) } for _, bad := range []string{"", "a.b", "a*", "a>", "a b", "a\tb", "a\nb"} { if _, err := ConditionSubject(bad); err == nil { t.Errorf("publisher %q accepted", bad) } } } func raisedCondition() Condition { return Condition{ Key: "delivery.stuck.mesh-mail", Severity: "warning", Subject: Scope{Scope: "module", ID: "mesh-mail", Machine: "ace"}, State: "raised", Summary: "plan 12 stopped", Headline: "Delivery of mesh-mail is stuck", Needs: "approve the merge.", Explanation: "Needs you: approve the merge. The plan waits.", Resolved: "The delivery of mesh-mail moved on.", Actions: []Action{{Label: "Approve", Verb: "mesh-controller.assign", Machine: "ace", Arguments: map[string]string{"why": ""}, Level: Approve}}, } } func clearedCondition() Condition { return Condition{Key: "delivery.stuck.mesh-mail", Subject: Scope{Scope: "module", ID: "mesh-mail"}, State: "cleared"} } func TestConditionRoundTrip(t *testing.T) { // 1202/AC2 for _, c := range []Condition{raisedCondition(), clearedCondition()} { data, err := EncodeCondition(c) if err != nil { t.Fatalf("%s: %v", c.State, err) } var probe map[string]any if err := json.Unmarshal(data, &probe); err != nil || probe["v"] != float64(1) { t.Fatalf("%s: %s does not carry v:1", c.State, data) } c.V = ConditionVersion got, err := DecodeCondition(data) if err != nil || !SameCondition(got, c) { t.Errorf("%s round trip: %+v %v", c.State, got, err) } } data, _ := EncodeCondition(raisedCondition()) var probe map[string]any _ = json.Unmarshal(data, &probe) for _, name := range []string{"key", "severity", "subject", "summary", "headline", "needs", "explanation", "resolved", "actions", "state"} { if _, ok := probe[name]; !ok { t.Errorf("the JSON has no %q: %s", name, data) } } subject := probe["subject"].(map[string]any) for _, name := range []string{"scope", "id", "machine"} { if _, ok := subject[name]; !ok { t.Errorf("the subject has no %q: %s", name, data) } } action := probe["actions"].([]any)[0].(map[string]any) for _, name := range []string{"label", "verb", "machine", "arguments", "level"} { if _, ok := action[name]; !ok { t.Errorf("the action has no %q: %s", name, data) } } for _, in := range []string{`{"key":"k"}`, `{"v":2,"key":"k"}`} { if _, err := DecodeCondition([]byte(in)); !errors.Is(err, ErrUnknownConditionVersion) { t.Errorf("decode of %s = %v, want ErrUnknownConditionVersion", in, err) } } } func TestConditionVerdict(t *testing.T) { // 1202/AC3 c := raisedCondition() c.Needs, c.Explanation = "", "The plan waits." _, err := EncodeCondition(c) if err == nil || !strings.Contains(err.Error(), "Nothing for you to do.") { t.Errorf("empty needs without its verdict: %v", err) } c.Explanation = "Nothing for you to do. The plan waits." if _, err := EncodeCondition(c); err != nil { t.Errorf("empty needs with its verdict refused: %v", err) } c = raisedCondition() c.Explanation = "Nothing for you to do. The plan waits." _, err = EncodeCondition(c) if err == nil || !strings.Contains(err.Error(), "Needs you: approve the merge.") { t.Errorf("needs set with the wrong verdict: %v", err) } c.Explanation = "Needs you: something else. The plan waits." if _, err = EncodeCondition(c); err == nil { t.Error("needs set with other needs in the verdict accepted") } } func TestConditionRefused(t *testing.T) { // 1202/AC4 mutate := func(f func(*Condition)) error { c := raisedCondition() f(&c) _, err := EncodeCondition(c) return err } for name, f := range map[string]func(*Condition){ "severity": func(c *Condition) { c.Severity = "critical" }, "state": func(c *Condition) { c.State = "open" }, "key": func(c *Condition) { c.Key = "" }, "headline": func(c *Condition) { c.Headline = "" }, "long headline": func(c *Condition) { c.Headline = strings.Repeat("h", HeadlineLength+1) }, } { if err := mutate(f); err == nil { t.Errorf("a condition with a bad %s accepted", name) } } if err := mutate(func(c *Condition) { c.Headline = strings.Repeat("h", HeadlineLength) }); err != nil { t.Errorf("a headline of the greatest length refused: %v", err) } if _, err := EncodeCondition(clearedCondition()); err != nil { t.Errorf("a cleared condition with only key, subject and state refused: %v", err) } c := clearedCondition() c.Key = "" if _, err := EncodeCondition(c); err == nil { t.Error("a cleared condition without its key accepted") } c = clearedCondition() c.State = "gone" if _, err := EncodeCondition(c); err == nil { t.Error("a condition with an unknown state accepted") } }