package test import ( "RenewCMS/api/middleware" "net/http" "net/http/httptest" "testing" "time" ) func TestAccountExportDataEmptyCollectionsAreArrays(t *testing.T) { f := newExportFixture(t) for index := range f.events.events { f.events.events[index].Params = nil } rec := f.export(t, exportMartaID) files := openExportArchive(t, rec) for _, item := range exportList(t, exportJSON(t, files, "authored-content.json"), "articles") { authors, ok := item.(map[string]any)["authors"].([]any) if !ok || len(authors) != 0 { t.Errorf("uncredited article authors = %v, want empty list", item.(map[string]any)["authors"]) } } events := exportList(t, exportJSON(t, files, "account-activity.json"), "events") if len(events) == 0 { t.Fatal("expected activity events") } for _, item := range events { params, ok := item.(map[string]any)["params"].(map[string]any) if !ok || len(params) != 0 { t.Errorf("event params = %v, want empty object", item.(map[string]any)["params"]) } } } func successLimiterStatus(t *testing.T, handler http.Handler) *httptest.ResponseRecorder { t.Helper() rec := httptest.NewRecorder() handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil)) return rec } func newSuccessLimiterHandler(clock *exportClock, status *int) http.Handler { limited := middleware.NewUserSuccessLimiterAt(time.Minute, clock.now) return limited(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(*status) })) } func TestUserSuccessLimiterStatusBoundary(t *testing.T) { cases := map[string]struct { status int blocked bool }{ "399 keeps the slot": {399, true}, "400 releases the slot": {400, false}, "500 releases the slot": {500, false}, "200 keeps the slot": {200, true}, "302 keeps the slot": {302, true}, "429 releases the slot": {429, false}, "401 releases the slot": {401, false}, "204 keeps the slot too": {204, true}, } for name, tc := range cases { t.Run(name, func(t *testing.T) { clock := &exportClock{current: time.Date(2026, time.October, 29, 9, 0, 0, 0, time.UTC)} status := tc.status handler := newSuccessLimiterHandler(clock, &status) successLimiterStatus(t, handler) second := successLimiterStatus(t, handler) if tc.blocked && second.Code != http.StatusTooManyRequests { t.Errorf("second call = %d, want 429", second.Code) } if !tc.blocked && second.Code != tc.status { t.Errorf("second call = %d, want handler status %d", second.Code, tc.status) } }) } } func TestUserSuccessLimiterWindowEdge(t *testing.T) { clock := &exportClock{current: time.Date(2026, time.October, 29, 9, 0, 0, 0, time.UTC)} status := http.StatusOK handler := newSuccessLimiterHandler(clock, &status) successLimiterStatus(t, handler) clock.advance(time.Minute - time.Second) blocked := successLimiterStatus(t, handler) if blocked.Code != http.StatusTooManyRequests || blocked.Header().Get("Retry-After") != "1" { t.Errorf("just inside the window = %d retry-after %q, want 429 and 1", blocked.Code, blocked.Header().Get("Retry-After")) } clock.advance(time.Second) if edge := successLimiterStatus(t, handler); edge.Code != http.StatusOK { t.Errorf("exactly at the window = %d, want 200", edge.Code) } }