package api import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/jacek/pamietnik/backend/internal/db" "github.com/jacek/pamietnik/backend/internal/domain" ) // fakeQueryTrackpointStore implements db.TrackpointStorer for query handler tests. type fakeQueryTrackpointStore struct { days []domain.DaySummary points []domain.Trackpoint err error } func (f *fakeQueryTrackpointStore) UpsertBatch(_ context.Context, _ string, _ []domain.Trackpoint) ([]string, []db.RejectedItem, error) { return nil, nil, nil } func (f *fakeQueryTrackpointStore) ListByDate(_ context.Context, _, _ string) ([]domain.Trackpoint, error) { return f.points, f.err } func (f *fakeQueryTrackpointStore) ListDays(_ context.Context, _, _, _ string) ([]domain.DaySummary, error) { return f.days, f.err } // fakeStopStore implements db.StopStorer. type fakeStopStore struct { stops []domain.Stop err error } func (f *fakeStopStore) ListByDate(_ context.Context, _, _ string) ([]domain.Stop, error) { return f.stops, f.err } // fakeSuggestionStore implements db.SuggestionStorer. type fakeSuggestionStore struct { suggestions []domain.Suggestion err error } func (f *fakeSuggestionStore) ListByDate(_ context.Context, _, _ string) ([]domain.Suggestion, error) { return f.suggestions, f.err } // --- HandleListDays --- func TestHandleListDays_MissingFromParam(t *testing.T) { handler := HandleListDays(&fakeQueryTrackpointStore{}) req := httptest.NewRequest(http.MethodGet, "/v1/days?to=2024-06-30", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusBadRequest { t.Errorf("expected 400 when 'from' missing, got %d", rec.Code) } } func TestHandleListDays_MissingToParam(t *testing.T) { handler := HandleListDays(&fakeQueryTrackpointStore{}) req := httptest.NewRequest(http.MethodGet, "/v1/days?from=2024-06-01", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusBadRequest { t.Errorf("expected 400 when 'to' missing, got %d", rec.Code) } } func TestHandleListDays_BothParamsMissing(t *testing.T) { handler := HandleListDays(&fakeQueryTrackpointStore{}) req := httptest.NewRequest(http.MethodGet, "/v1/days", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusBadRequest { t.Errorf("expected 400 when both params missing, got %d", rec.Code) } } func TestHandleListDays_EmptyResultIsArray(t *testing.T) { handler := HandleListDays(&fakeQueryTrackpointStore{days: nil}) req := httptest.NewRequest(http.MethodGet, "/v1/days?from=2024-06-01&to=2024-06-30", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } // Must be a JSON array, not null body := strings.TrimSpace(rec.Body.String()) if !strings.HasPrefix(body, "[") { t.Errorf("expected JSON array, got: %s", body) } } func TestHandleListDays_ReturnsDays(t *testing.T) { ts := time.Now() store := &fakeQueryTrackpointStore{ days: []domain.DaySummary{ {Date: "2024-06-01", Count: 42, FirstTS: &ts, LastTS: &ts}, }, } handler := HandleListDays(store) req := httptest.NewRequest(http.MethodGet, "/v1/days?from=2024-06-01&to=2024-06-30", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } var days []domain.DaySummary json.NewDecoder(rec.Body).Decode(&days) if len(days) != 1 || days[0].Date != "2024-06-01" || days[0].Count != 42 { t.Errorf("unexpected response: %+v", days) } } // --- HandleListTrackpoints --- func TestHandleListTrackpoints_MissingDateParam(t *testing.T) { handler := HandleListTrackpoints(&fakeQueryTrackpointStore{}) req := httptest.NewRequest(http.MethodGet, "/v1/trackpoints", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusBadRequest { t.Errorf("expected 400 when 'date' missing, got %d", rec.Code) } } func TestHandleListTrackpoints_EmptyResultIsArray(t *testing.T) { handler := HandleListTrackpoints(&fakeQueryTrackpointStore{points: nil}) req := httptest.NewRequest(http.MethodGet, "/v1/trackpoints?date=2024-06-01", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } body := strings.TrimSpace(rec.Body.String()) if !strings.HasPrefix(body, "[") { t.Errorf("expected JSON array, got: %s", body) } } func TestHandleListTrackpoints_ReturnsPoints(t *testing.T) { store := &fakeQueryTrackpointStore{ points: []domain.Trackpoint{ {EventID: "e1", DeviceID: "d1", Lat: 52.5, Lon: 13.4, Source: "gps"}, {EventID: "e2", DeviceID: "d1", Lat: 52.6, Lon: 13.5, Source: "gps"}, }, } handler := HandleListTrackpoints(store) req := httptest.NewRequest(http.MethodGet, "/v1/trackpoints?date=2024-06-01", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } var pts []domain.Trackpoint json.NewDecoder(rec.Body).Decode(&pts) if len(pts) != 2 { t.Errorf("expected 2 trackpoints, got %d", len(pts)) } } // --- HandleListStops --- func TestHandleListStops_MissingDateParam(t *testing.T) { handler := HandleListStops(&fakeStopStore{}) req := httptest.NewRequest(http.MethodGet, "/v1/stops", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusBadRequest { t.Errorf("expected 400 when 'date' missing, got %d", rec.Code) } } func TestHandleListStops_EmptyResultIsArray(t *testing.T) { handler := HandleListStops(&fakeStopStore{stops: nil}) req := httptest.NewRequest(http.MethodGet, "/v1/stops?date=2024-06-01", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } body := strings.TrimSpace(rec.Body.String()) if !strings.HasPrefix(body, "[") { t.Errorf("expected JSON array, got: %s", body) } } func TestHandleListStops_ReturnsStops(t *testing.T) { now := time.Now() store := &fakeStopStore{ stops: []domain.Stop{ {StopID: "stop-1", DeviceID: "d1", StartTS: now, EndTS: now, CenterLat: 52.5, CenterLon: 13.4, DurationS: 600}, }, } handler := HandleListStops(store) req := httptest.NewRequest(http.MethodGet, "/v1/stops?date=2024-06-01", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } var stops []domain.Stop json.NewDecoder(rec.Body).Decode(&stops) if len(stops) != 1 || stops[0].StopID != "stop-1" { t.Errorf("unexpected response: %+v", stops) } } // --- HandleListSuggestions --- func TestHandleListSuggestions_MissingDateParam(t *testing.T) { handler := HandleListSuggestions(&fakeSuggestionStore{}) req := httptest.NewRequest(http.MethodGet, "/v1/suggestions", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusBadRequest { t.Errorf("expected 400 when 'date' missing, got %d", rec.Code) } } func TestHandleListSuggestions_EmptyResultIsArray(t *testing.T) { handler := HandleListSuggestions(&fakeSuggestionStore{suggestions: nil}) req := httptest.NewRequest(http.MethodGet, "/v1/suggestions?date=2024-06-01", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } body := strings.TrimSpace(rec.Body.String()) if !strings.HasPrefix(body, "[") { t.Errorf("expected JSON array, got: %s", body) } } func TestHandleListSuggestions_ReturnsSuggestions(t *testing.T) { now := time.Now() store := &fakeSuggestionStore{ suggestions: []domain.Suggestion{ {SuggestionID: "sug-1", StopID: "stop-1", Type: "highlight", Title: "Nice spot", CreatedAt: now}, }, } handler := HandleListSuggestions(store) req := httptest.NewRequest(http.MethodGet, "/v1/suggestions?date=2024-06-01", nil) req = authContext(req) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } var suggestions []domain.Suggestion json.NewDecoder(rec.Body).Decode(&suggestions) if len(suggestions) != 1 || suggestions[0].SuggestionID != "sug-1" { t.Errorf("unexpected response: %+v", suggestions) } }