package handler import ( "bytes" "net/http" "net/http/httptest" "testing" ) func TestSavingsHandler_CreateCategory_InvalidBody(t *testing.T) { req := httptest.NewRequest("POST", "/savings/categories", bytes.NewBufferString("not json")) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.CreateCategory(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_CreateCategory_EmptyName(t *testing.T) { body := `{"name":""}` req := httptest.NewRequest("POST", "/savings/categories", bytes.NewBufferString(body)) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.CreateCategory(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_CreateTransaction_InvalidBody(t *testing.T) { req := httptest.NewRequest("POST", "/savings/transactions", bytes.NewBufferString("bad")) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.CreateTransaction(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_CreateTransaction_Validation(t *testing.T) { tests := []struct { name string body string }{ {"missing category_id", `{"amount": 100, "type": "deposit", "date": "2026-01-01"}`}, {"zero amount", `{"category_id": 1, "amount": 0, "type": "deposit", "date": "2026-01-01"}`}, {"negative amount", `{"category_id": 1, "amount": -10, "type": "deposit", "date": "2026-01-01"}`}, {"invalid type", `{"category_id": 1, "amount": 100, "type": "invalid", "date": "2026-01-01"}`}, {"missing date", `{"category_id": 1, "amount": 100, "type": "deposit", "date": ""}`}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest("POST", "/savings/transactions", bytes.NewBufferString(tt.body)) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.CreateTransaction(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } }) } } func TestSavingsHandler_UpdateCategory_InvalidBody(t *testing.T) { req := httptest.NewRequest("PUT", "/savings/categories/1", bytes.NewBufferString("bad")) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.UpdateCategory(rr, req) // No chi route params → will fail on ParseInt → 400 if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_UpdateCategory_InvalidID(t *testing.T) { req := httptest.NewRequest("PUT", "/savings/categories/abc", bytes.NewBufferString("{}")) req = withChiParam(req, "id", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.UpdateCategory(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_UpdateCategory_InvalidBodyAfterValidID(t *testing.T) { req := httptest.NewRequest("PUT", "/savings/categories/1", bytes.NewBufferString("bad json")) req = withChiParam(req, "id", "1") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.UpdateCategory(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_GetCategory_NoChiParam(t *testing.T) { req := httptest.NewRequest("GET", "/savings/categories/1", nil) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.GetCategory(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_GetCategory_InvalidID(t *testing.T) { req := httptest.NewRequest("GET", "/savings/categories/abc", nil) req = withChiParam(req, "id", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.GetCategory(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_DeleteCategory_NoChiParam(t *testing.T) { req := httptest.NewRequest("DELETE", "/savings/categories/1", nil) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.DeleteCategory(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_DeleteCategory_InvalidID(t *testing.T) { req := httptest.NewRequest("DELETE", "/savings/categories/abc", nil) req = withChiParam(req, "id", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.DeleteCategory(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_GetTransaction_NoChiParam(t *testing.T) { req := httptest.NewRequest("GET", "/savings/transactions/1", nil) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.GetTransaction(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_GetTransaction_InvalidID(t *testing.T) { req := httptest.NewRequest("GET", "/savings/transactions/abc", nil) req = withChiParam(req, "id", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.GetTransaction(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_UpdateTransaction_NoChiParam(t *testing.T) { req := httptest.NewRequest("PUT", "/savings/transactions/1", bytes.NewBufferString("{}")) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.UpdateTransaction(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_UpdateTransaction_InvalidID(t *testing.T) { req := httptest.NewRequest("PUT", "/savings/transactions/abc", bytes.NewBufferString("{}")) req = withChiParam(req, "id", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.UpdateTransaction(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_UpdateTransaction_InvalidBody(t *testing.T) { req := httptest.NewRequest("PUT", "/savings/transactions/1", bytes.NewBufferString("bad json")) req = withChiParam(req, "id", "1") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.UpdateTransaction(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_DeleteTransaction_NoChiParam(t *testing.T) { req := httptest.NewRequest("DELETE", "/savings/transactions/1", nil) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.DeleteTransaction(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_DeleteTransaction_InvalidID(t *testing.T) { req := httptest.NewRequest("DELETE", "/savings/transactions/abc", nil) req = withChiParam(req, "id", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.DeleteTransaction(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_ListMembers_NoChiParam(t *testing.T) { req := httptest.NewRequest("GET", "/savings/categories/1/members", nil) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.ListMembers(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_ListMembers_InvalidID(t *testing.T) { req := httptest.NewRequest("GET", "/savings/categories/abc/members", nil) req = withChiParam(req, "id", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.ListMembers(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_AddMember_NoChiParam(t *testing.T) { req := httptest.NewRequest("POST", "/savings/categories/1/members", bytes.NewBufferString("{}")) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.AddMember(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_AddMember_InvalidID(t *testing.T) { req := httptest.NewRequest("POST", "/savings/categories/abc/members", bytes.NewBufferString("{}")) req = withChiParam(req, "id", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.AddMember(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_RemoveMember_NoChiParam(t *testing.T) { req := httptest.NewRequest("DELETE", "/savings/categories/1/members/2", nil) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.RemoveMember(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_RemoveMember_InvalidCategoryID(t *testing.T) { req := httptest.NewRequest("DELETE", "/savings/categories/abc/members/2", nil) req = withChiParam(req, "id", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.RemoveMember(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_RemoveMember_InvalidUserID(t *testing.T) { req := httptest.NewRequest("DELETE", "/savings/categories/1/members/abc", nil) req = withChiParam(req, "id", "1") req = withChiParam(req, "userId", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.RemoveMember(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_ListRecurringPlans_NoChiParam(t *testing.T) { req := httptest.NewRequest("GET", "/savings/categories/1/plans", nil) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.ListRecurringPlans(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_ListRecurringPlans_InvalidID(t *testing.T) { req := httptest.NewRequest("GET", "/savings/categories/abc/plans", nil) req = withChiParam(req, "id", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.ListRecurringPlans(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_CreateRecurringPlan_InvalidBody(t *testing.T) { req := httptest.NewRequest("POST", "/savings/categories/1/plans", bytes.NewBufferString("bad")) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.CreateRecurringPlan(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_CreateRecurringPlan_NoChiParam(t *testing.T) { req := httptest.NewRequest("POST", "/savings/categories/1/plans", bytes.NewBufferString("{}")) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.CreateRecurringPlan(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_CreateRecurringPlan_InvalidID(t *testing.T) { req := httptest.NewRequest("POST", "/savings/categories/abc/plans", bytes.NewBufferString("{}")) req = withChiParam(req, "id", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.CreateRecurringPlan(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_DeleteRecurringPlan_NoChiParam(t *testing.T) { req := httptest.NewRequest("DELETE", "/savings/categories/1/plans/1", nil) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.DeleteRecurringPlan(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_DeleteRecurringPlan_InvalidID(t *testing.T) { req := httptest.NewRequest("DELETE", "/savings/categories/1/plans/abc", nil) req = withChiParam(req, "planId", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.DeleteRecurringPlan(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_UpdateRecurringPlan_NoChiParam(t *testing.T) { req := httptest.NewRequest("PUT", "/savings/categories/1/plans/1", bytes.NewBufferString("{}")) rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.UpdateRecurringPlan(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_UpdateRecurringPlan_InvalidID(t *testing.T) { req := httptest.NewRequest("PUT", "/savings/categories/1/plans/abc", bytes.NewBufferString("{}")) req = withChiParam(req, "planId", "abc") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.UpdateRecurringPlan(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestSavingsHandler_UpdateRecurringPlan_InvalidBody(t *testing.T) { req := httptest.NewRequest("PUT", "/savings/categories/1/plans/1", bytes.NewBufferString("bad json")) req = withChiParam(req, "planId", "1") rr := httptest.NewRecorder() h := &SavingsHandler{repo: nil} h.UpdateRecurringPlan(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } }