package query import ( "errors" "net/http" "net/url" "testing" "time" ) func TestParseList_defaults(t *testing.T) { params, err := ParseList(url.Values{}) if err != nil { t.Fatalf("ParseList() error = %v", err) } if params.Page != DefaultPage { t.Fatalf("Page = %d, want %d", params.Page, DefaultPage) } if params.PageSize != DefaultPageSize { t.Fatalf("PageSize = %d, want %d", params.PageSize, DefaultPageSize) } if params.Q != "" || params.Sort != "" { t.Fatalf("Q/Sort should be empty, got q=%q sort=%q", params.Q, params.Sort) } if params.From != nil || params.To != nil { t.Fatal("From/To should be nil") } if params.Offset() != 0 { t.Fatalf("Offset() = %d, want 0", params.Offset()) } if params.Limit() != DefaultPageSize { t.Fatalf("Limit() = %d, want %d", params.Limit(), DefaultPageSize) } } func TestParseList_valid(t *testing.T) { values := url.Values{ "page": {"3"}, "page_size": {"25"}, "q": {" hello "}, "sort": {"-created_at"}, "from": {"2026-01-01"}, "to": {"2026-01-31"}, } params, err := ParseList(values) if err != nil { t.Fatalf("ParseList() error = %v", err) } if params.Page != 3 || params.PageSize != 25 { t.Fatalf("page/page_size = %d/%d", params.Page, params.PageSize) } if params.Q != "hello" || params.Sort != "-created_at" { t.Fatalf("q/sort = %q/%q", params.Q, params.Sort) } if params.Offset() != 50 { t.Fatalf("Offset() = %d, want 50", params.Offset()) } if params.From == nil || params.To == nil { t.Fatal("expected From/To to be set") } if params.From.Format(dateLayout) != "2026-01-01" { t.Fatalf("From = %s", params.From.Format(time.RFC3339)) } if params.To.Format(dateLayout) != "2026-01-31" { t.Fatalf("To day = %s", params.To.Format(dateLayout)) } } func TestParseList_invalidPage(t *testing.T) { tests := []struct { name string value string }{ {"zero", "0"}, {"negative", "-1"}, {"non_numeric", "abc"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := ParseList(url.Values{"page": {tt.value}}) assertValidationError(t, err, "page") }) } } func TestParseList_invalidPageSize(t *testing.T) { tests := []struct { name string value string }{ {"zero", "0"}, {"negative", "-5"}, {"too_large", "201"}, {"non_numeric", "large"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := ParseList(url.Values{"page_size": {tt.value}}) assertValidationError(t, err, "page_size") }) } } func TestParseList_invalidDates(t *testing.T) { tests := []struct { name string values url.Values field string }{ { name: "invalid from format", values: url.Values{"from": {"01-01-2026"}}, field: "from", }, { name: "invalid to format", values: url.Values{"to": {"2026/01/31"}}, field: "to", }, { name: "from after to", values: url.Values{"from": {"2026-02-01"}, "to": {"2026-01-01"}}, field: "from", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := ParseList(tt.values) assertValidationError(t, err, tt.field) }) } } func TestParseList_multipleErrors(t *testing.T) { _, err := ParseList(url.Values{ "page": {"0"}, "page_size": {"500"}, "from": {"bad-date"}, }) var verr *ValidationError if !errors.As(err, &verr) { t.Fatalf("expected ValidationError, got %T", err) } if len(verr.Details) < 3 { t.Fatalf("Details len = %d, want >= 3", len(verr.Details)) } if verr.Code != "invalid_query_param" { t.Fatalf("Code = %q", verr.Code) } } func TestParseDate(t *testing.T) { got, err := ParseDate("2026-05-22") if err != nil { t.Fatalf("ParseDate() error = %v", err) } if got.Location() != time.UTC { t.Fatalf("location = %v, want UTC", got.Location()) } if got.Format(dateLayout) != "2026-05-22" { t.Fatalf("date = %s", got.Format(dateLayout)) } if _, err := ParseDate("22-05-2026"); err == nil { t.Fatal("expected error for invalid format") } } func TestMeta(t *testing.T) { params := ListParams{Page: 2, PageSize: 10} total := int64(42) meta := params.Meta(&total) if meta.Page != 2 || meta.PageSize != 10 { t.Fatalf("meta page/page_size = %d/%d", meta.Page, meta.PageSize) } if meta.Total == nil || *meta.Total != 42 { t.Fatalf("meta total = %v", meta.Total) } } func TestParseListRequest(t *testing.T) { req, err := http.NewRequest(http.MethodGet, "/items?page=2&page_size=10&q=test", nil) if err != nil { t.Fatal(err) } params, err := ParseListRequest(req) if err != nil { t.Fatalf("ParseListRequest() error = %v", err) } if params.Page != 2 || params.PageSize != 10 || params.Q != "test" { t.Fatalf("params = %+v", params) } } func assertValidationError(t *testing.T, err error, field string) { t.Helper() var verr *ValidationError if !errors.As(err, &verr) { t.Fatalf("expected ValidationError, got %T: %v", err, err) } for _, detail := range verr.Details { if detail.Field == field { return } } t.Fatalf("expected detail for field %q, got %+v", field, verr.Details) }