diff --git a/handler.go b/handler.go index 491e019..02ab3fe 100644 --- a/handler.go +++ b/handler.go @@ -58,7 +58,7 @@ var tmpl = template.Must(template.New("").Funcs(template.FuncMap{ var validToken = regexp.MustCompile(`^[a-f0-9]{32}$`) func registerRoutes(mux *http.ServeMux, store *Store) { - mux.HandleFunc("GET /health", handleHealth) + mux.HandleFunc("GET /health", handleHealth(store)) mux.HandleFunc("GET /", handleIndex) mux.HandleFunc("POST /lists", handleCreateList(store)) mux.HandleFunc("GET /lists/{token}", handleShowList(store)) @@ -70,15 +70,20 @@ func registerRoutes(mux *http.ServeMux, store *Store) { mux.HandleFunc("POST /lists/{token}/delete", handleDeleteList(store)) } -func handleHealth(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(map[string]string{ //nolint:errcheck - "status": "ok", - "service": "bringit", - "uptime": time.Since(startTime).Round(time.Second).String(), - "timestamp": time.Now().UTC().Format(time.RFC3339), - }) +func handleHealth(store *Store) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + listCount, itemCount := store.Stats() + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]any{ //nolint:errcheck + "status": "ok", + "service": "bringit", + "uptime": time.Since(startTime).Round(time.Second).String(), + "timestamp": time.Now().UTC().Format(time.RFC3339), + "list_count": listCount, + "item_count": itemCount, + }) + } } func handleIndex(w http.ResponseWriter, r *http.Request) { diff --git a/handler_test.go b/handler_test.go index 4054cee..d68e199 100644 --- a/handler_test.go +++ b/handler_test.go @@ -29,19 +29,46 @@ func TestHealthEndpoint(t *testing.T) { if ct := w.Header().Get("Content-Type"); !strings.HasPrefix(ct, "application/json") { t.Fatalf("expected application/json content-type, got %s", ct) } - var body map[string]string + var body map[string]any if err := json.NewDecoder(w.Body).Decode(&body); err != nil { t.Fatalf("failed to decode JSON: %v", err) } if body["status"] != "ok" { - t.Fatalf("expected status=ok, got %s", body["status"]) + t.Fatalf("expected status=ok, got %v", body["status"]) } if body["service"] != "bringit" { - t.Fatalf("expected service=bringit, got %s", body["service"]) + t.Fatalf("expected service=bringit, got %v", body["service"]) } - if body["timestamp"] == "" { + if body["timestamp"] == nil || body["timestamp"] == "" { t.Fatal("expected non-empty timestamp") } + if body["list_count"] != float64(0) { + t.Fatalf("expected list_count=0, got %v", body["list_count"]) + } + if body["item_count"] != float64(0) { + t.Fatalf("expected item_count=0, got %v", body["item_count"]) + } +} + +func TestHealthEndpointWithData(t *testing.T) { + mux, store := setupTestServer() + l := store.CreateList("テスト", "") + store.AddItem(l.ShareToken, "テント", "太郎", true) + store.AddItem(l.ShareToken, "寝袋", "花子", false) + + req := httptest.NewRequest("GET", "/health", nil) + w := httptest.NewRecorder() + mux.ServeHTTP(w, req) + + var body map[string]any + json.NewDecoder(w.Body).Decode(&body) + + if body["list_count"] != float64(1) { + t.Fatalf("expected list_count=1, got %v", body["list_count"]) + } + if body["item_count"] != float64(2) { + t.Fatalf("expected item_count=2, got %v", body["item_count"]) + } } func TestIndexPage(t *testing.T) { diff --git a/store.go b/store.go index 8ce2921..981d50f 100644 --- a/store.go +++ b/store.go @@ -124,6 +124,16 @@ func (s *Store) DeleteList(token string) bool { return true } +func (s *Store) Stats() (listCount int, itemCount int) { + s.mu.RLock() + defer s.mu.RUnlock() + listCount = len(s.lists) + for _, l := range s.lists { + itemCount += len(l.Items) + } + return +} + func (s *Store) DeleteItem(token, itemID string) { s.mu.Lock() defer s.mu.Unlock() diff --git a/store_test.go b/store_test.go index bd7b1ff..71c89e6 100644 --- a/store_test.go +++ b/store_test.go @@ -156,6 +156,39 @@ func TestConcurrentAddItem(t *testing.T) { } } +func TestStoreStats(t *testing.T) { + s := NewStore() + + listCount, itemCount := s.Stats() + if listCount != 0 || itemCount != 0 { + t.Fatalf("expected 0/0, got %d/%d", listCount, itemCount) + } + + l1 := s.CreateList("リスト1", "") + s.AddItem(l1.ShareToken, "アイテム1", "", true) + s.AddItem(l1.ShareToken, "アイテム2", "", false) + + l2 := s.CreateList("リスト2", "") + s.AddItem(l2.ShareToken, "アイテム3", "", true) + + listCount, itemCount = s.Stats() + if listCount != 2 { + t.Fatalf("expected 2 lists, got %d", listCount) + } + if itemCount != 3 { + t.Fatalf("expected 3 items, got %d", itemCount) + } + + s.DeleteList(l1.ShareToken) + listCount, itemCount = s.Stats() + if listCount != 1 { + t.Fatalf("expected 1 list after deletion, got %d", listCount) + } + if itemCount != 1 { + t.Fatalf("expected 1 item after deletion, got %d", itemCount) + } +} + func TestGetEnv(t *testing.T) { // 環境変数が設定されている場合 os.Setenv("TEST_VAR", "hello")