package view import ( "bufio" "context" "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/delivery" ) func served(t *testing.T) (string, *Model) { t.Helper() m := New() fill(m, base(t)...) fill(m, haveE(t, "a", "w", have(t, "b9", delivery.PhaseRunning, delivery.HealthOK, ""), t0)) dir, err := os.MkdirTemp(os.Getenv("TMPDIR"), "s") if err != nil { t.Fatal(err) } t.Cleanup(func() { os.RemoveAll(dir) }) path := filepath.Join(dir, "run", "cli.sock") l, err := ListenSocket(path) if err != nil { t.Fatal(err) } ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { ServeSocket(ctx, l, m); close(done) }() t.Cleanup(func() { cancel(); l.Close(); <-done }) return path, m } func TestSocketAnswersRows(t *testing.T) { // 1134/AC6 path, m := served(t) got, err := Ask(path) if err != nil { t.Fatal(err) } want := m.Snapshot() b1, _ := json.Marshal(got) b2, _ := json.Marshal(want) if len(want.Rows) == 0 || string(b1) != string(b2) { t.Fatalf("socket answered %s, want %s", b1, b2) } st, err := os.Stat(path) if err != nil || st.Mode().Perm() != 0o666 { t.Fatalf("socket mode = %v, %v; want 0666", st.Mode().Perm(), err) } dst, _ := os.Stat(filepath.Dir(path)) if dst.Mode().Perm() != 0o755 { t.Fatalf("directory mode = %v, want 0755", dst.Mode().Perm()) } } func TestSocketRefusesAnyOtherAsk(t *testing.T) { // 1134/AC6 path, m := served(t) before, _ := json.Marshal(m.Snapshot()) for _, ask := range []string{`{"ask":"start","module":"x"}`, `{"ask":"retry"}`, `{"ask":""}`, `not json`, `{}`} { c, err := dial(path) if err != nil { t.Fatal(err) } c.Write([]byte(ask + "\n")) line, err := bufio.NewReader(c).ReadString('\n') c.Close() if err != nil || !strings.Contains(line, `"error"`) || strings.Contains(line, `"rows"`) { t.Errorf("ask %q answered %q, %v; want a refusal", ask, line, err) } } after, _ := json.Marshal(m.Snapshot()) if string(before) != string(after) { t.Fatal("a refused ask changed the rows") } } func TestTableShowsEveryRow(t *testing.T) { // 1134/AC6 _, m := served(t) var out strings.Builder s := m.Snapshot() Table(&out, s) for _, r := range s.Rows { if !strings.Contains(out.String(), r.Module) { t.Fatalf("table lacks %s:\n%s", r.Module, out.String()) } } for _, w := range []string{"NODE", "MODULE", "ASSIGNED", "HAVE", "PHASE", "HEALTH", "SINCE", "WAITS", "no have yet", "left behind"} { if !strings.Contains(out.String(), w) { t.Errorf("table lacks %q:\n%s", w, out.String()) } } } func TestWebIsTheSameRowsAndReadOnly(t *testing.T) { // 1134/AC6 _, m := served(t) h := Web(m) rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil)) if rec.Code != 200 || !strings.Contains(rec.Body.String(), "no have yet") || !strings.Contains(rec.Header().Get("Content-Type"), "text/html") { t.Fatalf("web = %d %q", rec.Code, rec.Body.String()) } rec = httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/", strings.NewReader("x"))) if rec.Code != http.StatusMethodNotAllowed { t.Fatalf("POST = %d, want 405", rec.Code) } } func watchmanSnapshot(t *testing.T) *Model { t.Helper() m := New() fill(m, presenceE(t, "a", false, t0), stuckE(t, "node-engine_have", "a.x", "a report from a", t0, false), Entry{Bucket: delivery.PresenceBucket, Key: "c", Value: []byte(`{"v":9}`), At: t0}) return m } func TestTableAndWebShowTheWatchmansLists(t *testing.T) { // 1168/AC4 m := watchmanSnapshot(t) var out strings.Builder Table(&out, m.Snapshot()) rec := httptest.NewRecorder() Web(m).ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil)) for name, text := range map[string]string{"table": out.String(), "web": rec.Body.String()} { for _, w := range []string{"OFFLINE", "STUCK", "UNREADABLE", "node-engine_have", "a.x", "a report from a", "offline", "c"} { if !strings.Contains(text, w) { t.Errorf("%s lacks %q:\n%s", name, w, text) } } } } func TestSocketCarriesTheWatchmansLists(t *testing.T) { // 1168/AC4 m := watchmanSnapshot(t) dir, _ := os.MkdirTemp(os.Getenv("TMPDIR"), "w") t.Cleanup(func() { os.RemoveAll(dir) }) path := filepath.Join(dir, "cli.sock") l, err := ListenSocket(path) if err != nil { t.Fatal(err) } ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) go ServeSocket(ctx, l, m) got, err := Ask(path) if err != nil || len(got.Offline) != 1 || len(got.Stuck) != 1 || len(got.Unreadable) != 1 { t.Fatalf("socket = %+v, %v", got, err) } }