329 lines
9.3 KiB
Go
329 lines
9.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"goyco/internal/database"
|
|
"goyco/internal/middleware"
|
|
"goyco/internal/repositories"
|
|
"goyco/internal/services"
|
|
"goyco/internal/testutils"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
func TestAPIHandlerGetAPIInfo(t *testing.T) {
|
|
mockPostRepo := testutils.NewPostRepositoryStub()
|
|
mockUserRepo := testutils.NewUserRepositoryStub()
|
|
handler := newAPIHandlerForTest(mockPostRepo, mockUserRepo)
|
|
recorder := httptest.NewRecorder()
|
|
request := httptest.NewRequest(http.MethodGet, "/api", nil)
|
|
|
|
handler.GetAPIInfo(recorder, request)
|
|
|
|
testutils.AssertHTTPStatus(t, recorder, http.StatusOK)
|
|
|
|
var resp APIInfo
|
|
if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
if !resp.Success || resp.Message == "" {
|
|
t.Fatalf("expected success response, got %+v", resp)
|
|
}
|
|
|
|
data, ok := resp.Data.(map[string]any)
|
|
if !ok || data["name"] != fmt.Sprintf("%s API", testutils.AppTestConfig.App.Title) {
|
|
t.Fatalf("unexpected data payload: %#v", resp.Data)
|
|
}
|
|
|
|
endpoints, ok := data["endpoints"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("expected endpoints map, got %#v", data["endpoints"])
|
|
}
|
|
|
|
authEndpoints := endpoints["authentication"].(map[string]any)
|
|
for _, route := range []string{
|
|
"POST /api/auth/resend-verification",
|
|
"POST /api/auth/account/confirm",
|
|
} {
|
|
if _, found := authEndpoints[route]; !found {
|
|
t.Fatalf("expected authentication catalogue to include %s", route)
|
|
}
|
|
}
|
|
|
|
systemEndpoints := endpoints["system"].(map[string]any)
|
|
if _, found := systemEndpoints["GET /metrics"]; !found {
|
|
t.Fatalf("expected system catalogue to include GET /metrics")
|
|
}
|
|
}
|
|
|
|
func TestAPIHandlerGetHealth(t *testing.T) {
|
|
mockPostRepo := testutils.NewPostRepositoryStub()
|
|
mockUserRepo := testutils.NewUserRepositoryStub()
|
|
handler := newAPIHandlerForTest(mockPostRepo, mockUserRepo)
|
|
recorder := httptest.NewRecorder()
|
|
request := httptest.NewRequest(http.MethodGet, "/health", nil)
|
|
|
|
handler.GetHealth(recorder, request)
|
|
|
|
testutils.AssertHTTPStatus(t, recorder, http.StatusOK)
|
|
|
|
var resp APIInfo
|
|
if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("decode error: %v", err)
|
|
}
|
|
|
|
if !resp.Success || resp.Message == "" {
|
|
t.Fatalf("expected success message, got %+v", resp)
|
|
}
|
|
|
|
data := resp.Data.(map[string]any)
|
|
if data["status"] != "healthy" {
|
|
t.Fatalf("expected health status, got %+v", data)
|
|
}
|
|
}
|
|
|
|
func TestAPIHandlerGetMetrics(t *testing.T) {
|
|
mockPostRepo := testutils.NewPostRepositoryStub()
|
|
mockPostRepo.CountFn = func() (int64, error) { return 10, nil }
|
|
mockPostRepo.GetTopPostsFn = func(limit int) ([]database.Post, error) {
|
|
return []database.Post{
|
|
{ID: 1, Score: 100},
|
|
{ID: 2, Score: 50},
|
|
{ID: 3, Score: 25},
|
|
}, nil
|
|
}
|
|
|
|
mockUserRepo := testutils.NewUserRepositoryStub()
|
|
mockUserRepo.CountFn = func() (int64, error) { return 5, nil }
|
|
|
|
handler := newAPIHandlerForTest(mockPostRepo, mockUserRepo)
|
|
recorder := httptest.NewRecorder()
|
|
request := httptest.NewRequest(http.MethodGet, "/metrics", nil)
|
|
|
|
handler.GetMetrics(recorder, request)
|
|
|
|
testutils.AssertHTTPStatus(t, recorder, http.StatusOK)
|
|
|
|
var resp APIInfo
|
|
if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("decode error: %v", err)
|
|
}
|
|
|
|
if !resp.Success || resp.Message == "" {
|
|
t.Fatalf("expected success response, got %+v", resp)
|
|
}
|
|
|
|
data, ok := resp.Data.(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("expected metrics data map, got %T", resp.Data)
|
|
}
|
|
|
|
if data["posts"] == nil {
|
|
t.Fatalf("expected metrics payload to include posts")
|
|
}
|
|
if data["users"] == nil {
|
|
t.Fatalf("expected metrics payload to include users")
|
|
}
|
|
if data["votes"] == nil {
|
|
t.Fatalf("expected metrics payload to include votes")
|
|
}
|
|
if data["system"] == nil {
|
|
t.Fatalf("expected metrics payload to include system")
|
|
}
|
|
|
|
posts, ok := data["posts"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("expected posts to be a map, got %T", data["posts"])
|
|
}
|
|
if posts["total_count"] != float64(10) {
|
|
t.Fatalf("expected posts total_count to be 10, got %v", posts["total_count"])
|
|
}
|
|
}
|
|
|
|
func newAPIHandlerForTest(postRepo repositories.PostRepository, userRepo repositories.UserRepository) *APIHandler {
|
|
voteRepo := testutils.NewMockVoteRepository()
|
|
voteService := services.NewVoteService(voteRepo, postRepo, nil)
|
|
return NewAPIHandler(testutils.AppTestConfig, postRepo, userRepo, voteService)
|
|
}
|
|
|
|
func TestAPIHandlerGetMetricsErrorHandling(t *testing.T) {
|
|
mockPostRepo := testutils.NewPostRepositoryStub()
|
|
mockPostRepo.CountFn = func() (int64, error) { return 0, errors.New("database error") }
|
|
|
|
mockUserRepo := testutils.NewUserRepositoryStub()
|
|
handler := newAPIHandlerForTest(mockPostRepo, mockUserRepo)
|
|
|
|
recorder := httptest.NewRecorder()
|
|
request := httptest.NewRequest(http.MethodGet, "/metrics", nil)
|
|
|
|
handler.GetMetrics(recorder, request)
|
|
|
|
testutils.AssertHTTPStatus(t, recorder, http.StatusInternalServerError)
|
|
|
|
var resp APIInfo
|
|
if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("decode error: %v", err)
|
|
}
|
|
|
|
if resp.Success {
|
|
t.Fatalf("expected error response, got %+v", resp)
|
|
}
|
|
}
|
|
|
|
func TestAPIHandlerGetMetricsWithDatabaseMonitoring(t *testing.T) {
|
|
mockPostRepo := testutils.NewPostRepositoryStub()
|
|
mockPostRepo.CountFn = func() (int64, error) { return 10, nil }
|
|
mockPostRepo.GetTopPostsFn = func(limit int) ([]database.Post, error) {
|
|
return []database.Post{
|
|
{ID: 1, Score: 100},
|
|
{ID: 2, Score: 50},
|
|
}, nil
|
|
}
|
|
|
|
mockUserRepo := testutils.NewUserRepositoryStub()
|
|
mockUserRepo.CountFn = func() (int64, error) { return 5, nil }
|
|
|
|
voteRepo := testutils.NewMockVoteRepository()
|
|
voteService := services.NewVoteService(voteRepo, mockPostRepo, nil)
|
|
|
|
handler := NewAPIHandler(testutils.AppTestConfig, mockPostRepo, mockUserRepo, voteService)
|
|
|
|
recorder := httptest.NewRecorder()
|
|
request := httptest.NewRequest(http.MethodGet, "/metrics", nil)
|
|
|
|
handler.GetMetrics(recorder, request)
|
|
|
|
testutils.AssertHTTPStatus(t, recorder, http.StatusOK)
|
|
|
|
var resp APIInfo
|
|
if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("decode error: %v", err)
|
|
}
|
|
|
|
if !resp.Success {
|
|
t.Fatalf("expected success response, got %+v", resp)
|
|
}
|
|
|
|
data, ok := resp.Data.(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("expected metrics data map, got %T", resp.Data)
|
|
}
|
|
|
|
expectedSections := []string{"posts", "users", "votes", "system"}
|
|
for _, section := range expectedSections {
|
|
if data[section] == nil {
|
|
t.Fatalf("expected metrics payload to include %s", section)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNewAPIHandlerWithMonitoring(t *testing.T) {
|
|
mockPostRepo := testutils.NewPostRepositoryStub()
|
|
mockUserRepo := testutils.NewUserRepositoryStub()
|
|
voteRepo := testutils.NewMockVoteRepository()
|
|
voteService := services.NewVoteService(voteRepo, mockPostRepo, nil)
|
|
monitor := middleware.NewInMemoryDBMonitor()
|
|
|
|
db := testutils.NewTestDB(t)
|
|
defer func() {
|
|
sqlDB, _ := db.DB()
|
|
sqlDB.Close()
|
|
}()
|
|
|
|
handler := NewAPIHandlerWithMonitoring(testutils.AppTestConfig, mockPostRepo, mockUserRepo, voteService, db, monitor)
|
|
|
|
if handler == nil {
|
|
t.Fatal("Expected handler to be created")
|
|
}
|
|
|
|
if handler.dbMonitor == nil {
|
|
t.Error("Expected dbMonitor to be set")
|
|
}
|
|
|
|
if handler.healthChecker == nil {
|
|
t.Error("Expected healthChecker to be set")
|
|
}
|
|
|
|
if handler.metricsCollector == nil {
|
|
t.Error("Expected metricsCollector to be set")
|
|
}
|
|
}
|
|
|
|
func TestNewAPIHandlerWithMonitoring_NilDB(t *testing.T) {
|
|
mockPostRepo := testutils.NewPostRepositoryStub()
|
|
mockUserRepo := testutils.NewUserRepositoryStub()
|
|
voteRepo := testutils.NewMockVoteRepository()
|
|
voteService := services.NewVoteService(voteRepo, mockPostRepo, nil)
|
|
|
|
handler := NewAPIHandlerWithMonitoring(testutils.AppTestConfig, mockPostRepo, mockUserRepo, voteService, nil, nil)
|
|
|
|
if handler == nil {
|
|
t.Fatal("Expected handler to be created")
|
|
}
|
|
|
|
if handler.dbMonitor != nil {
|
|
t.Error("Expected dbMonitor to be nil when db is nil")
|
|
}
|
|
|
|
if handler.healthChecker != nil {
|
|
t.Error("Expected healthChecker to be nil when db is nil")
|
|
}
|
|
|
|
if handler.metricsCollector != nil {
|
|
t.Error("Expected metricsCollector to be nil when db is nil")
|
|
}
|
|
}
|
|
|
|
func TestAPIHandlerMountRoutes(t *testing.T) {
|
|
mockPostRepo := testutils.NewPostRepositoryStub()
|
|
mockUserRepo := testutils.NewUserRepositoryStub()
|
|
handler := newAPIHandlerForTest(mockPostRepo, mockUserRepo)
|
|
|
|
router := chi.NewRouter()
|
|
config := RouteModuleConfig{}
|
|
|
|
router.Route("/api", func(r chi.Router) {
|
|
handler.MountRoutes(r, config)
|
|
})
|
|
|
|
t.Run("GET route works", func(t *testing.T) {
|
|
request := httptest.NewRequest(http.MethodGet, "/api", nil)
|
|
recorder := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(recorder, request)
|
|
|
|
testutils.AssertHTTPStatus(t, recorder, http.StatusOK)
|
|
|
|
var resp APIInfo
|
|
if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
if !resp.Success {
|
|
t.Fatalf("expected success response, got %+v", resp)
|
|
}
|
|
})
|
|
|
|
t.Run("only GET is mounted", func(t *testing.T) {
|
|
methods := []string{http.MethodPost, http.MethodPut, http.MethodDelete}
|
|
|
|
for _, method := range methods {
|
|
request := httptest.NewRequest(method, "/api", nil)
|
|
recorder := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(recorder, request)
|
|
|
|
if recorder.Code != http.StatusMethodNotAllowed && recorder.Code != http.StatusNotFound {
|
|
t.Errorf("expected method not allowed or not found for %s, got %d", method, recorder.Code)
|
|
}
|
|
}
|
|
})
|
|
}
|