Files
goyco/internal/handlers/post_handler_test.go

712 lines
24 KiB
Go

package handlers
import (
"bytes"
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgconn"
"gorm.io/gorm"
"goyco/internal/database"
"goyco/internal/middleware"
"goyco/internal/repositories"
"goyco/internal/services"
"goyco/internal/testutils"
)
func decodeHandlerResponse(t *testing.T, rr *httptest.ResponseRecorder) map[string]any {
t.Helper()
var payload map[string]any
if err := json.Unmarshal(rr.Body.Bytes(), &payload); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
return payload
}
func TestPostHandlerGetPostsWithVoteService(t *testing.T) {
repo := testutils.NewPostRepositoryStub()
repo.GetAllFn = func(limit, offset int) ([]database.Post, error) {
return []database.Post{
{ID: 1, Title: "Test Post 1"},
{ID: 2, Title: "Test Post 2"},
}, nil
}
voteRepo := testutils.NewMockVoteRepository()
voteService := services.NewVoteService(voteRepo, repo, nil)
handler := NewPostHandler(repo, nil, voteService)
request := httptest.NewRequest(http.MethodGet, "/api/posts", nil)
request = testutils.WithUserContext(request, middleware.UserIDKey, uint(1))
recorder := httptest.NewRecorder()
handler.GetPosts(recorder, request)
testutils.AssertHTTPStatus(t, recorder, http.StatusOK)
payload := decodeHandlerResponse(t, recorder)
if !payload["success"].(bool) {
t.Fatalf("expected success response, got %v", payload)
}
}
func TestPostHandlerCreatePostWithTitleFetcher(t *testing.T) {
repo := testutils.NewPostRepositoryStub()
var storedPost *database.Post
repo.CreateFn = func(post *database.Post) error {
storedPost = post
return nil
}
titleFetcher := &testutils.MockTitleFetcher{}
titleFetcher.SetTitle("Fetched Title")
handler := NewPostHandler(repo, titleFetcher, nil)
request := httptest.NewRequest(http.MethodPost, "/api/posts", bytes.NewBufferString(`{"url":"https://example.com","content":"Test content"}`))
request = testutils.WithUserContext(request, middleware.UserIDKey, uint(1))
request.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
handler.CreatePost(recorder, request)
testutils.AssertHTTPStatus(t, recorder, http.StatusCreated)
if storedPost == nil {
t.Fatal("expected post to be created")
}
if storedPost.Title != "Fetched Title" {
t.Errorf("expected title 'Fetched Title', got %s", storedPost.Title)
}
}
func TestPostHandlerCreatePostTitleFetcherError(t *testing.T) {
repo := testutils.NewPostRepositoryStub()
titleFetcher := &testutils.MockTitleFetcher{}
titleFetcher.SetError(services.ErrUnsupportedScheme)
handler := NewPostHandler(repo, titleFetcher, nil)
request := httptest.NewRequest(http.MethodPost, "/api/posts", bytes.NewBufferString(`{"url":"ftp://example.com"}`))
request = testutils.WithUserContext(request, middleware.UserIDKey, uint(1))
request.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
handler.CreatePost(recorder, request)
testutils.AssertHTTPStatus(t, recorder, http.StatusBadRequest)
payload := decodeHandlerResponse(t, recorder)
if payload["success"].(bool) {
t.Fatalf("expected error response, got %v", payload)
}
}
func TestPostHandlerSearchPosts(t *testing.T) {
repo := testutils.NewPostRepositoryStub()
repo.SearchFn = func(query string, limit, offset int) ([]database.Post, error) {
return []database.Post{
{ID: 1, Title: "Search Result 1"},
{ID: 2, Title: "Search Result 2"},
}, nil
}
handler := NewPostHandler(repo, nil, nil)
request := httptest.NewRequest(http.MethodGet, "/api/posts/search?q=test", nil)
recorder := httptest.NewRecorder()
handler.SearchPosts(recorder, request)
testutils.AssertHTTPStatus(t, recorder, http.StatusOK)
payload := decodeHandlerResponse(t, recorder)
if !payload["success"].(bool) {
t.Fatalf("expected success response, got %v", payload)
}
}
func TestPostHandlerFetchTitleFromURL(t *testing.T) {
titleFetcher := &testutils.MockTitleFetcher{}
titleFetcher.SetTitle("Test Title")
handler := NewPostHandler(nil, titleFetcher, nil)
request := httptest.NewRequest(http.MethodGet, "/api/posts/title?url=https://example.com", nil)
recorder := httptest.NewRecorder()
handler.FetchTitleFromURL(recorder, request)
testutils.AssertHTTPStatus(t, recorder, http.StatusOK)
payload := decodeHandlerResponse(t, recorder)
if !payload["success"].(bool) {
t.Fatalf("expected success response, got %v", payload)
}
}
func TestPostHandlerFetchTitleFromURLNoFetcher(t *testing.T) {
handler := NewPostHandler(nil, nil, nil)
request := httptest.NewRequest(http.MethodGet, "/api/posts/title?url=https://example.com", nil)
recorder := httptest.NewRecorder()
handler.FetchTitleFromURL(recorder, request)
testutils.AssertHTTPStatus(t, recorder, http.StatusNotImplemented)
}
func TestPostHandlerUpdatePostUnauthorized(t *testing.T) {
repo := testutils.NewPostRepositoryStub()
repo.GetByIDFn = func(id uint) (*database.Post, error) {
return &database.Post{ID: id, AuthorID: func() *uint { u := uint(2); return &u }()}, nil
}
handler := NewPostHandler(repo, nil, nil)
request := httptest.NewRequest(http.MethodPut, "/api/posts/1", bytes.NewBufferString(`{"title":"Updated Title","content":"Updated content"}`))
request = testutils.WithUserContext(request, middleware.UserIDKey, uint(1))
request = testutils.WithURLParams(request, map[string]string{"id": "1"})
request.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
handler.UpdatePost(recorder, request)
testutils.AssertHTTPStatus(t, recorder, http.StatusForbidden)
}
func TestPostHandlerDeletePostUnauthorized(t *testing.T) {
repo := testutils.NewPostRepositoryStub()
repo.GetByIDFn = func(id uint) (*database.Post, error) {
return &database.Post{ID: id, AuthorID: func() *uint { u := uint(2); return &u }()}, nil
}
voteRepo := testutils.NewMockVoteRepository()
voteService := services.NewVoteService(voteRepo, repo, nil)
handler := NewPostHandler(repo, nil, voteService)
request := httptest.NewRequest(http.MethodDelete, "/api/posts/1", nil)
request = testutils.WithUserContext(request, middleware.UserIDKey, uint(1))
request = testutils.WithURLParams(request, map[string]string{"id": "1"})
recorder := httptest.NewRecorder()
handler.DeletePost(recorder, request)
testutils.AssertHTTPStatus(t, recorder, http.StatusForbidden)
}
func TestPostHandlerGetPosts(t *testing.T) {
var receivedLimit, receivedOffset int
repo := testutils.NewPostRepositoryStub()
repo.GetAllFn = func(limit, offset int) ([]database.Post, error) {
receivedLimit = limit
receivedOffset = offset
return []database.Post{{ID: 1}}, nil
}
handler := NewPostHandler(repo, nil, nil)
request := httptest.NewRequest(http.MethodGet, "/api/posts?limit=5&offset=2", nil)
recorder := httptest.NewRecorder()
handler.GetPosts(recorder, request)
if receivedLimit != 5 || receivedOffset != 2 {
t.Fatalf("expected limit=5 offset=2, got %d %d", receivedLimit, receivedOffset)
}
testutils.AssertHTTPStatus(t, recorder, http.StatusOK)
payload := decodeHandlerResponse(t, recorder)
if !payload["success"].(bool) {
t.Fatalf("expected success response, got %v", payload)
}
}
func TestPostHandlerGetPostErrors(t *testing.T) {
repo := testutils.NewPostRepositoryStub()
handler := NewPostHandler(repo, nil, nil)
request := httptest.NewRequest(http.MethodGet, "/api/posts", nil)
recorder := httptest.NewRecorder()
handler.GetPost(recorder, request)
if recorder.Result().StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400 for missing id, got %d", recorder.Result().StatusCode)
}
request = httptest.NewRequest(http.MethodGet, "/api/posts/abc", nil)
request = testutils.WithURLParams(request, map[string]string{"id": "abc"})
recorder = httptest.NewRecorder()
handler.GetPost(recorder, request)
if recorder.Result().StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400 for invalid id, got %d", recorder.Result().StatusCode)
}
repo.GetByIDFn = func(uint) (*database.Post, error) {
return nil, gorm.ErrRecordNotFound
}
request = httptest.NewRequest(http.MethodGet, "/api/posts/1", nil)
request = testutils.WithURLParams(request, map[string]string{"id": "1"})
recorder = httptest.NewRecorder()
handler.GetPost(recorder, request)
testutils.AssertHTTPStatus(t, recorder, http.StatusNotFound)
}
func TestPostHandlerCreatePostSuccess(t *testing.T) {
var storedPost *database.Post
repo := testutils.NewPostRepositoryStub()
repo.CreateFn = func(post *database.Post) error {
storedPost = &database.Post{
Title: post.Title,
URL: post.URL,
Content: post.Content,
AuthorID: post.AuthorID,
}
storedPost.ID = 1
return nil
}
fetcher := &testutils.TitleFetcherStub{FetchTitleFn: func(ctx context.Context, rawURL string) (string, error) {
return "Fetched Title", nil
}}
handler := NewPostHandler(repo, fetcher, nil)
body := bytes.NewBufferString(`{"title":" ","url":"https://example.com","content":"Go"}`)
request := httptest.NewRequest(http.MethodPost, "/api/posts", body)
ctx := context.WithValue(request.Context(), middleware.UserIDKey, uint(42))
request = request.WithContext(ctx)
recorder := httptest.NewRecorder()
handler.CreatePost(recorder, request)
testutils.AssertHTTPStatus(t, recorder, http.StatusCreated)
if storedPost == nil || storedPost.Title != "Fetched Title" || storedPost.AuthorID == nil || *storedPost.AuthorID != 42 {
t.Fatalf("unexpected stored post: %#v", storedPost)
}
}
func TestPostHandlerCreatePostValidation(t *testing.T) {
handler := NewPostHandler(testutils.NewPostRepositoryStub(), &testutils.TitleFetcherStub{}, nil)
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodPost, "/api/posts", bytes.NewBufferString(`{"title":"","url":"","content":""}`))
request = request.WithContext(context.WithValue(request.Context(), middleware.UserIDKey, uint(1)))
handler.CreatePost(recorder, request)
if recorder.Result().StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400 for missing url, got %d", recorder.Result().StatusCode)
}
recorder = httptest.NewRecorder()
request = httptest.NewRequest(http.MethodPost, "/api/posts", bytes.NewBufferString(`invalid json`))
handler.CreatePost(recorder, request)
if recorder.Result().StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400 for invalid JSON, got %d", recorder.Result().StatusCode)
}
recorder = httptest.NewRecorder()
request = httptest.NewRequest(http.MethodPost, "/api/posts", bytes.NewBufferString(`{"title":"ok","url":"https://example.com"}`))
handler.CreatePost(recorder, request)
testutils.AssertHTTPStatus(t, recorder, http.StatusUnauthorized)
}
func TestPostHandlerCreatePostTitleFetcherErrors(t *testing.T) {
tests := []struct {
name string
err error
wantStatus int
wantMsg string
}{
{name: "Unsupported", err: services.ErrUnsupportedScheme, wantStatus: http.StatusBadRequest, wantMsg: "Only HTTP and HTTPS URLs are supported"},
{name: "TitleMissing", err: services.ErrTitleNotFound, wantStatus: http.StatusBadRequest, wantMsg: "Title could not be extracted"},
{name: "Generic", err: errors.New("timeout"), wantStatus: http.StatusBadGateway, wantMsg: "Failed to fetch title"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
repo := testutils.NewPostRepositoryStub()
fetcher := &testutils.TitleFetcherStub{FetchTitleFn: func(ctx context.Context, rawURL string) (string, error) {
return "", tc.err
}}
handler := NewPostHandler(repo, fetcher, nil)
body := bytes.NewBufferString(`{"title":" ","url":"https://example.com"}`)
request := httptest.NewRequest(http.MethodPost, "/api/posts", body)
request = request.WithContext(context.WithValue(request.Context(), middleware.UserIDKey, uint(1)))
recorder := httptest.NewRecorder()
handler.CreatePost(recorder, request)
testutils.AssertHTTPStatus(t, recorder, tc.wantStatus)
if !strings.Contains(recorder.Body.String(), tc.wantMsg) {
t.Fatalf("expected message to contain %q, got %q", tc.wantMsg, recorder.Body.String())
}
})
}
}
func TestPostHandlerFetchTitleFromURLErrors(t *testing.T) {
handler := NewPostHandler(testutils.NewPostRepositoryStub(), nil, nil)
request := httptest.NewRequest(http.MethodGet, "/api/posts/title?url=https://example.com", nil)
recorder := httptest.NewRecorder()
handler.FetchTitleFromURL(recorder, request)
if recorder.Result().StatusCode != http.StatusNotImplemented {
t.Fatalf("expected 501 when fetcher unavailable, got %d", recorder.Result().StatusCode)
}
handler = NewPostHandler(testutils.NewPostRepositoryStub(), &testutils.TitleFetcherStub{}, nil)
request = httptest.NewRequest(http.MethodGet, "/api/posts/title", nil)
recorder = httptest.NewRecorder()
handler.FetchTitleFromURL(recorder, request)
if recorder.Result().StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400 for missing url query, got %d", recorder.Result().StatusCode)
}
handler = NewPostHandler(testutils.NewPostRepositoryStub(), &testutils.TitleFetcherStub{FetchTitleFn: func(ctx context.Context, rawURL string) (string, error) {
return "", errors.New("failed")
}}, nil)
request = httptest.NewRequest(http.MethodGet, "/api/posts/title?url=https://example.com", nil)
recorder = httptest.NewRecorder()
handler.FetchTitleFromURL(recorder, request)
testutils.AssertHTTPStatus(t, recorder, http.StatusBadGateway)
}
func TestTranslatePostCreateError(t *testing.T) {
conflictErr := &pgconn.PgError{Code: "23505"}
msg, status := translatePostCreateError(conflictErr)
if status != http.StatusConflict || !strings.Contains(msg, "already been submitted") {
t.Fatalf("unexpected conflict translation: status=%d msg=%q", status, msg)
}
fkErr := &pgconn.PgError{Code: "23503"}
msg, status = translatePostCreateError(fkErr)
if status != http.StatusUnauthorized || !strings.Contains(msg, "Author account not found") {
t.Fatalf("unexpected foreign key translation: status=%d msg=%q", status, msg)
}
msg, status = translatePostCreateError(errors.New("other"))
if status != 0 || msg != "" {
t.Fatalf("expected passthrough for unrelated errors, got status=%d msg=%q", status, msg)
}
}
func TestPostHandlerUpdatePost(t *testing.T) {
tests := []struct {
name string
postID string
requestBody string
userID uint
mockSetup func(*testutils.PostRepositoryStub)
expectedStatus int
expectedError string
}{
{
name: "valid post update",
postID: "1",
requestBody: `{"title": "Updated Title", "content": "Updated content"}`,
userID: 1,
mockSetup: func(repo *testutils.PostRepositoryStub) {
repo.GetByIDFn = func(id uint) (*database.Post, error) {
authorID := uint(1)
return &database.Post{ID: id, Title: "Old Title", AuthorID: &authorID}, nil
}
repo.UpdateFn = func(post *database.Post) error { return nil }
},
expectedStatus: http.StatusOK,
},
{
name: "missing user context",
postID: "1",
requestBody: `{"title": "Updated Title", "content": "Updated content"}`,
userID: 0,
mockSetup: func(repo *testutils.PostRepositoryStub) {},
expectedStatus: http.StatusUnauthorized,
expectedError: "Authentication required",
},
{
name: "post not found",
postID: "999",
requestBody: `{"title": "Updated Title", "content": "Updated content"}`,
userID: 1,
mockSetup: func(repo *testutils.PostRepositoryStub) {
repo.GetByIDFn = func(id uint) (*database.Post, error) {
return nil, gorm.ErrRecordNotFound
}
},
expectedStatus: http.StatusNotFound,
expectedError: "Post not found",
},
{
name: "not author",
postID: "1",
requestBody: `{"title": "Updated Title", "content": "Updated content"}`,
userID: 2,
mockSetup: func(repo *testutils.PostRepositoryStub) {
repo.GetByIDFn = func(id uint) (*database.Post, error) {
authorID := uint(1)
return &database.Post{ID: id, Title: "Old Title", AuthorID: &authorID}, nil
}
},
expectedStatus: http.StatusForbidden,
expectedError: "You can only edit your own posts",
},
{
name: "empty title",
postID: "1",
requestBody: `{"title": "", "content": "Updated content"}`,
userID: 1,
mockSetup: func(repo *testutils.PostRepositoryStub) {
authorID := uint(1)
repo.GetByIDFn = func(id uint) (*database.Post, error) {
return &database.Post{ID: id, Title: "Old Title", AuthorID: &authorID}, nil
}
},
expectedStatus: http.StatusBadRequest,
expectedError: "Title is required",
},
{
name: "short title",
postID: "1",
requestBody: `{"title": "ab", "content": "Updated content"}`,
userID: 1,
mockSetup: func(repo *testutils.PostRepositoryStub) {
authorID := uint(1)
repo.GetByIDFn = func(id uint) (*database.Post, error) {
return &database.Post{ID: id, Title: "Old Title", AuthorID: &authorID}, nil
}
},
expectedStatus: http.StatusBadRequest,
expectedError: "Title must be at least 3 characters",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := testutils.NewPostRepositoryStub()
if tt.mockSetup != nil {
tt.mockSetup(repo)
}
handler := NewPostHandler(repo, &testutils.TitleFetcherStub{}, nil)
request := httptest.NewRequest(http.MethodPut, "/api/posts/"+tt.postID, bytes.NewBufferString(tt.requestBody))
if tt.userID > 0 {
ctx := context.WithValue(request.Context(), middleware.UserIDKey, tt.userID)
request = request.WithContext(ctx)
}
ctx := chi.NewRouteContext()
ctx.URLParams.Add("id", tt.postID)
request = request.WithContext(context.WithValue(request.Context(), chi.RouteCtxKey, ctx))
recorder := httptest.NewRecorder()
handler.UpdatePost(recorder, request)
testutils.AssertHTTPStatus(t, recorder, tt.expectedStatus)
if tt.expectedError != "" {
if !strings.Contains(recorder.Body.String(), tt.expectedError) {
t.Fatalf("expected error to contain %q, got %q", tt.expectedError, recorder.Body.String())
}
}
})
}
}
func TestPostHandlerDeletePost(t *testing.T) {
tests := []struct {
name string
postID string
userID uint
mockSetup func(*testutils.PostRepositoryStub)
expectedStatus int
expectedError string
}{
{
name: "valid post deletion",
postID: "1",
userID: 1,
mockSetup: func(repo *testutils.PostRepositoryStub) {
repo.GetByIDFn = func(id uint) (*database.Post, error) {
authorID := uint(1)
return &database.Post{ID: id, Title: "Test Post", AuthorID: &authorID}, nil
}
repo.DeleteFn = func(id uint) error { return nil }
},
expectedStatus: http.StatusOK,
},
{
name: "missing user context",
postID: "1",
userID: 0,
mockSetup: func(repo *testutils.PostRepositoryStub) {},
expectedStatus: http.StatusUnauthorized,
expectedError: "Authentication required",
},
{
name: "post not found",
postID: "999",
userID: 1,
mockSetup: func(repo *testutils.PostRepositoryStub) {
repo.GetByIDFn = func(id uint) (*database.Post, error) {
return nil, gorm.ErrRecordNotFound
}
},
expectedStatus: http.StatusNotFound,
expectedError: "Post not found",
},
{
name: "not author",
postID: "1",
userID: 2,
mockSetup: func(repo *testutils.PostRepositoryStub) {
repo.GetByIDFn = func(id uint) (*database.Post, error) {
authorID := uint(1)
return &database.Post{ID: id, Title: "Test Post", AuthorID: &authorID}, nil
}
},
expectedStatus: http.StatusForbidden,
expectedError: "You can only delete your own posts",
},
{
name: "delete error",
postID: "1",
userID: 1,
mockSetup: func(repo *testutils.PostRepositoryStub) {
repo.GetByIDFn = func(id uint) (*database.Post, error) {
authorID := uint(1)
return &database.Post{ID: id, Title: "Test Post", AuthorID: &authorID}, nil
}
repo.DeleteFn = func(id uint) error { return errors.New("database error") }
},
expectedStatus: http.StatusInternalServerError,
expectedError: "Failed to delete post",
},
{
name: "delete votes error",
postID: "1",
userID: 1,
mockSetup: func(repo *testutils.PostRepositoryStub) {
repo.GetByIDFn = func(id uint) (*database.Post, error) {
authorID := uint(1)
return &database.Post{ID: id, Title: "Test Post", AuthorID: &authorID}, nil
}
},
expectedStatus: http.StatusInternalServerError,
expectedError: "Failed to delete post votes",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := testutils.NewPostRepositoryStub()
if tt.mockSetup != nil {
tt.mockSetup(repo)
}
var voteService *services.VoteService
if tt.name == "delete votes error" {
voteRepo := &errorVoteRepository{}
voteService = services.NewVoteService(voteRepo, repo, nil)
} else {
voteRepo := testutils.NewMockVoteRepository()
voteService = services.NewVoteService(voteRepo, repo, nil)
}
handler := NewPostHandler(repo, &testutils.TitleFetcherStub{}, voteService)
request := httptest.NewRequest(http.MethodDelete, "/api/posts/"+tt.postID, nil)
if tt.userID > 0 {
ctx := context.WithValue(request.Context(), middleware.UserIDKey, tt.userID)
request = request.WithContext(ctx)
}
ctx := chi.NewRouteContext()
ctx.URLParams.Add("id", tt.postID)
request = request.WithContext(context.WithValue(request.Context(), chi.RouteCtxKey, ctx))
recorder := httptest.NewRecorder()
handler.DeletePost(recorder, request)
testutils.AssertHTTPStatus(t, recorder, tt.expectedStatus)
if tt.expectedError != "" {
if !strings.Contains(recorder.Body.String(), tt.expectedError) {
t.Fatalf("expected error to contain %q, got %q", tt.expectedError, recorder.Body.String())
}
}
})
}
}
type errorVoteRepository struct{}
func (e *errorVoteRepository) Create(*database.Vote) error { return nil }
func (e *errorVoteRepository) CreateOrUpdate(*database.Vote) error { return nil }
func (e *errorVoteRepository) GetByID(uint) (*database.Vote, error) {
return nil, gorm.ErrRecordNotFound
}
func (e *errorVoteRepository) GetByUserAndPost(uint, uint) (*database.Vote, error) {
return nil, gorm.ErrRecordNotFound
}
func (e *errorVoteRepository) GetByVoteHash(string) (*database.Vote, error) {
return nil, gorm.ErrRecordNotFound
}
func (e *errorVoteRepository) GetByPostID(uint) ([]database.Vote, error) {
return nil, errors.New("database error")
}
func (e *errorVoteRepository) GetByUserID(uint) ([]database.Vote, error) { return nil, nil }
func (e *errorVoteRepository) Update(*database.Vote) error { return nil }
func (e *errorVoteRepository) Delete(uint) error { return nil }
func (e *errorVoteRepository) Count() (int64, error) { return 0, nil }
func (e *errorVoteRepository) CountByPostID(uint) (int64, error) { return 0, nil }
func (e *errorVoteRepository) CountByUserID(uint) (int64, error) { return 0, nil }
func (e *errorVoteRepository) WithTx(*gorm.DB) repositories.VoteRepository { return e }
func TestPostHandler_EdgeCases(t *testing.T) {
postRepo := testutils.NewPostRepositoryStub()
titleFetcher := &testutils.TitleFetcherStub{}
handler := NewPostHandler(postRepo, titleFetcher, nil)
t.Run("GetPosts with zero limit", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/posts?limit=0", nil)
w := httptest.NewRecorder()
handler.GetPosts(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200 for zero limit, got %d", w.Code)
}
})
t.Run("GetPosts with negative limit", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/posts?limit=-1", nil)
w := httptest.NewRecorder()
handler.GetPosts(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200 for negative limit, got %d", w.Code)
}
})
t.Run("GetPosts with negative offset", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/posts?offset=-1", nil)
w := httptest.NewRecorder()
handler.GetPosts(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200 for negative offset, got %d", w.Code)
}
})
}