101 lines
3.2 KiB
Go
101 lines
3.2 KiB
Go
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)
|
|
}
|
|
}
|