package health import ( "encoding/json" "strings" "testing" ) func roundTrip(t *testing.T, a Answer) Answer { t.Helper() b, err := json.Marshal(a) if err != nil { t.Fatal(err) } got, err := Read(b) if err != nil { t.Fatal(err) } return got } var password = Wait{Part: "the source games", Secret: "smb-password-games", What: "the password of the source games"} func TestEachConstructorSaysItsStateAndKeepsHealthyForAnOlderReader(t *testing.T) { for _, c := range []struct { a Answer state string healthy bool }{ {Healthy("all armed"), StateHealthy, true}, {Unhealthy("the share is unreachable"), StateUnhealthy, false}, {Waiting("the source games waits for its password", password), StateWaits, false}, } { b, _ := json.Marshal(c.a) var old struct { Healthy bool `json:"healthy"` Why string `json:"why"` } if err := json.Unmarshal(b, &old); err != nil || old.Healthy != c.healthy || old.Why == "" { t.Fatalf("an older reader of %s reads %+v (%v)", b, old, err) } if got := roundTrip(t, c.a); got.State != c.state || got.Healthy != c.healthy { t.Fatalf("%s read as %+v", b, got) } } } func TestAWaitIsReadWithWhatItNames(t *testing.T) { got := roundTrip(t, Waiting("waits", password, Wait{Part: "the source games", Setting: "smb-users", What: "the username of the source games"})) if got.State != StateWaits || len(got.Waits) != 2 || got.Waits[0].Secret != "smb-password-games" || got.Waits[1].Setting != "smb-users" { t.Fatalf("read as %+v", got) } } func TestAWaitThatCannotBeReadIsUnhealthyAndSaysWhy(t *testing.T) { for name, c := range map[string]struct { w []Wait says string }{ "no wait": {nil, "names nothing"}, "neither": {[]Wait{{Part: "p", What: "w"}}, "neither a secret nor a setting"}, "both": {[]Wait{{Part: "p", Secret: "a", Setting: "b", What: "w"}}, "both"}, "not a name": {[]Wait{{Part: "p", Secret: "Smb Password", What: "w"}}, "not a name"}, "no what": {[]Wait{{Part: "p", Secret: "a"}}, "does not say what"}, "no part": {[]Wait{{Secret: "a", What: "w"}}, "which part"}, "two lines": {[]Wait{{Part: "p", Secret: "a", What: "the password\nhunter2"}}, "more than one line"}, "an address": {[]Wait{{Part: "p", Secret: "a", What: "the password of 192.0.2.76"}}, "path or an address"}, "a url": {[]Wait{{Part: "p", Secret: "a", What: "the password of smb://host/share"}}, "path or an address"}, "a path": {[]Wait{{Part: "the source at /mnt/games", Secret: "a", What: "the password"}}, "path or an address"}, } { b, _ := json.Marshal(Answer{State: StateWaits, Why: "it waits", Waits: c.w}) got, err := Read(b) if err != nil { t.Fatalf("%s: %v", name, err) } if got.State != StateUnhealthy || got.Healthy || !strings.Contains(got.Why, c.says) { t.Fatalf("%s: read as %+v, want unhealthy saying %q", name, got, c.says) } } } func TestWaitsBesideAFaultAreSaidAndExcuseNothing(t *testing.T) { got := roundTrip(t, Unhealthy("the share is unreachable", password, Wait{Part: "p", What: "w"})) if got.State != StateUnhealthy || len(got.Waits) != 1 { t.Fatalf("read as %+v: the readable wait is kept, the other dropped, the state stays unhealthy", got) } if h := roundTrip(t, Answer{State: StateHealthy, Healthy: true, Why: "ok", Waits: []Wait{password}}); len(h.Waits) != 0 { t.Fatalf("a healthy answer kept its waits: %+v", h) } } func TestWaitingWithNothingIsUnhealthy(t *testing.T) { if a := Waiting("it waits"); a.State != StateUnhealthy { t.Fatalf("Waiting with no wait is %+v", a) } } func TestEveryFormOfPathOrAddressIsRefused(t *testing.T) { for _, words := range []string{ "the source (/mnt/games)", `the source "/mnt/games"`, "the folder ~/games", "the folder ./games", "2001:db8::1", "fe80::1%eth0", "2001:0db8:0000:0000:0000:0000:0000:0001", "nas.lan:445", "192.0.2.7:445", `\\nas\games`, `C:\Users\x`, "smb://nas/games", "198.51.100.4", } { w := Wait{Part: "the source games", Secret: "smb-password-games", What: "the password of " + words} if err := w.Check(); err == nil { t.Errorf("%q passed as words for the operator", words) } } for _, words := range []string{"the password of the source games", "the username of the source games-2", "the API key, issued by the vendor"} { w := Wait{Part: "the source games", Secret: "smb-password-games", What: words} if err := w.Check(); err != nil { t.Errorf("%q refused: %v", words, err) } } } func TestWaitingChecksItsWaits(t *testing.T) { if a := Waiting("it waits", Wait{Part: "p", What: "w"}); a.State != StateUnhealthy || !strings.Contains(a.Why, "neither") { t.Fatalf("Waiting with a malformed wait is %+v", a) } } // A raw answer is read by what it says: without state from healthy; an unknown state, or healthy with healthy false, as // unhealthy; not an answer at all as an error. func TestARawAnswerIsReadByWhatItSays(t *testing.T) { for _, c := range []struct { raw, state, says string fails bool }{ {raw: `{"healthy":true,"why":"ok"}`, state: StateHealthy}, {raw: `{"healthy":false,"why":"down"}`, state: StateUnhealthy}, {raw: `{"state":"fine","healthy":true}`, state: StateUnhealthy}, {raw: `{"state":"healthy","healthy":false,"why":"x"}`, state: StateUnhealthy, says: "contradict"}, {raw: `"yes"`, fails: true}, } { got, err := Read([]byte(c.raw)) if c.fails { if err == nil { t.Errorf("%s read as an answer", c.raw) } continue } if err != nil || got.State != c.state || (c.state == StateUnhealthy && got.Healthy) || !strings.Contains(got.Why, c.says) { t.Errorf("%s read as %+v, %v", c.raw, got, err) } } }