package handlers_test import ( "bytes" "context" "encoding/json" "errors" "net/http" "net/http/httptest" "path/filepath" "strings" "testing" "github.com/andyleap/hnh-map/internal/app" "github.com/andyleap/hnh-map/internal/app/apperr" "github.com/andyleap/hnh-map/internal/app/handlers" "github.com/andyleap/hnh-map/internal/app/services" "github.com/andyleap/hnh-map/internal/app/store" "go.etcd.io/bbolt" "golang.org/x/crypto/bcrypt" ) type testEnv struct { h *handlers.Handlers st *store.Store auth *services.AuthService } func newTestEnv(t *testing.T) *testEnv { t.Helper() dir := t.TempDir() db, err := bbolt.Open(filepath.Join(dir, "test.db"), 0600, nil) if err != nil { t.Fatal(err) } t.Cleanup(func() { db.Close() }) st := store.New(db) auth := services.NewAuthService(st) gridUpdates := &app.Topic[app.TileData]{} mergeUpdates := &app.Topic[app.Merge]{} mapSvc := services.NewMapService(services.MapServiceDeps{ Store: st, GridStorage: dir, GridUpdates: gridUpdates, MergeUpdates: mergeUpdates, GetChars: func() []app.Character { return nil }, }) admin := services.NewAdminService(st, mapSvc) client := services.NewClientService(services.ClientServiceDeps{ Store: st, MapSvc: mapSvc, WithChars: func(fn func(chars map[string]app.Character)) { fn(map[string]app.Character{}) }, }) export := services.NewExportService(st, mapSvc) h := handlers.New(auth, mapSvc, admin, client, export) return &testEnv{h: h, st: st, auth: auth} } func (env *testEnv) createUser(t *testing.T, username, password string, auths app.Auths) { t.Helper() hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost) u := app.User{Pass: hash, Auths: auths} raw, _ := json.Marshal(u) env.st.Update(context.Background(), func(tx *bbolt.Tx) error { return env.st.PutUser(tx, username, raw) }) } func (env *testEnv) loginSession(t *testing.T, username string) string { t.Helper() return env.auth.CreateSession(context.Background(), username, false) } func withSession(req *http.Request, sid string) *http.Request { req.AddCookie(&http.Cookie{Name: "session", Value: sid}) return req } func TestAPISetup_NoUsers(t *testing.T) { env := newTestEnv(t) req := httptest.NewRequest(http.MethodGet, "/map/api/setup", nil) rr := httptest.NewRecorder() env.h.APISetup(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr.Code) } var resp struct{ SetupRequired bool } json.NewDecoder(rr.Body).Decode(&resp) if !resp.SetupRequired { t.Fatal("expected setupRequired=true") } } func TestAPISetup_WithUsers(t *testing.T) { env := newTestEnv(t) env.createUser(t, "admin", "pass", app.Auths{app.AUTH_ADMIN}) req := httptest.NewRequest(http.MethodGet, "/map/api/setup", nil) rr := httptest.NewRecorder() env.h.APISetup(rr, req) var resp struct{ SetupRequired bool } json.NewDecoder(rr.Body).Decode(&resp) if resp.SetupRequired { t.Fatal("expected setupRequired=false with users") } } func TestAPISetup_WrongMethod(t *testing.T) { env := newTestEnv(t) req := httptest.NewRequest(http.MethodPost, "/map/api/setup", nil) rr := httptest.NewRecorder() env.h.APISetup(rr, req) if rr.Code != http.StatusMethodNotAllowed { t.Fatalf("expected 405, got %d", rr.Code) } } func TestAPILogin_Success(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "secret", app.Auths{app.AUTH_MAP}) body := `{"user":"alice","pass":"secret"}` req := httptest.NewRequest(http.MethodPost, "/map/api/login", strings.NewReader(body)) rr := httptest.NewRecorder() env.h.APILogin(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String()) } cookies := rr.Result().Cookies() found := false for _, c := range cookies { if c.Name == "session" && c.Value != "" { found = true } } if !found { t.Fatal("expected session cookie") } } func TestAPILogin_WrongPassword(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "secret", app.Auths{app.AUTH_MAP}) body := `{"user":"alice","pass":"wrong"}` req := httptest.NewRequest(http.MethodPost, "/map/api/login", strings.NewReader(body)) rr := httptest.NewRecorder() env.h.APILogin(rr, req) if rr.Code != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", rr.Code) } } func TestAPILogin_BadJSON(t *testing.T) { env := newTestEnv(t) req := httptest.NewRequest(http.MethodPost, "/map/api/login", strings.NewReader("{invalid")) rr := httptest.NewRecorder() env.h.APILogin(rr, req) if rr.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d", rr.Code) } } func TestAPILogin_MethodNotAllowed(t *testing.T) { env := newTestEnv(t) req := httptest.NewRequest(http.MethodGet, "/map/api/login", nil) rr := httptest.NewRecorder() env.h.APILogin(rr, req) if rr.Code != http.StatusMethodNotAllowed { t.Fatalf("expected 405, got %d", rr.Code) } } func TestAPIMe_Authenticated(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_MAP, app.AUTH_UPLOAD}) sid := env.loginSession(t, "alice") req := withSession(httptest.NewRequest(http.MethodGet, "/map/api/me", nil), sid) rr := httptest.NewRecorder() env.h.APIMe(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String()) } var resp struct { Username string `json:"username"` Auths []string `json:"auths"` } json.NewDecoder(rr.Body).Decode(&resp) if resp.Username != "alice" { t.Fatalf("expected alice, got %s", resp.Username) } } func TestAPIMe_Unauthenticated(t *testing.T) { env := newTestEnv(t) req := httptest.NewRequest(http.MethodGet, "/map/api/me", nil) rr := httptest.NewRecorder() env.h.APIMe(rr, req) if rr.Code != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", rr.Code) } } func TestAPILogout(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", nil) sid := env.loginSession(t, "alice") req := withSession(httptest.NewRequest(http.MethodPost, "/map/api/logout", nil), sid) rr := httptest.NewRecorder() env.h.APILogout(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr.Code) } req2 := withSession(httptest.NewRequest(http.MethodGet, "/map/api/me", nil), sid) rr2 := httptest.NewRecorder() env.h.APIMe(rr2, req2) if rr2.Code != http.StatusUnauthorized { t.Fatalf("expected 401 after logout, got %d", rr2.Code) } } func TestAPIMeTokens_Authenticated(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_UPLOAD}) sid := env.loginSession(t, "alice") req := withSession(httptest.NewRequest(http.MethodPost, "/map/api/me/tokens", nil), sid) rr := httptest.NewRecorder() env.h.APIMeTokens(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String()) } var resp struct{ Tokens []string } json.NewDecoder(rr.Body).Decode(&resp) if len(resp.Tokens) != 1 { t.Fatalf("expected 1 token, got %d", len(resp.Tokens)) } } func TestAPIMeTokens_Unauthenticated(t *testing.T) { env := newTestEnv(t) req := httptest.NewRequest(http.MethodPost, "/map/api/me/tokens", nil) rr := httptest.NewRecorder() env.h.APIMeTokens(rr, req) if rr.Code != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", rr.Code) } } func TestAPIMeTokens_NoUploadAuth(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_MAP}) sid := env.loginSession(t, "alice") req := withSession(httptest.NewRequest(http.MethodPost, "/map/api/me/tokens", nil), sid) rr := httptest.NewRecorder() env.h.APIMeTokens(rr, req) if rr.Code != http.StatusForbidden { t.Fatalf("expected 403, got %d", rr.Code) } } func TestAPIMePassword(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "old", app.Auths{app.AUTH_MAP}) sid := env.loginSession(t, "alice") body := `{"pass":"newpass"}` req := withSession(httptest.NewRequest(http.MethodPost, "/map/api/me/password", strings.NewReader(body)), sid) rr := httptest.NewRecorder() env.h.APIMePassword(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr.Code) } } func TestAPIMeUpdate_UpdatesEmail(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_MAP}) sid := env.loginSession(t, "alice") patchBody := `{"email":"test@example.com"}` req := withSession(httptest.NewRequest(http.MethodPatch, "/map/api/me", strings.NewReader(patchBody)), sid) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() env.h.APIMeUpdate(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String()) } req2 := withSession(httptest.NewRequest(http.MethodGet, "/map/api/me", nil), sid) rr2 := httptest.NewRecorder() env.h.APIMe(rr2, req2) if rr2.Code != http.StatusOK { t.Fatalf("expected 200 on GET /me, got %d", rr2.Code) } var meResp struct { Username string `json:"username"` Email string `json:"email"` } if err := json.NewDecoder(rr2.Body).Decode(&meResp); err != nil { t.Fatal(err) } if meResp.Email != "test@example.com" { t.Fatalf("expected email test@example.com, got %q", meResp.Email) } } func TestAPIRouter_Me_MethodNotAllowed(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_MAP}) sid := env.loginSession(t, "alice") req := withSession(httptest.NewRequest(http.MethodPost, "/map/api/me", nil), sid) rr := httptest.NewRecorder() env.h.APIRouter(rr, req) if rr.Code != http.StatusMethodNotAllowed { t.Fatalf("expected 405 for POST /me, got %d", rr.Code) } } func TestAdminUsers_RequiresAdmin(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_MAP}) sid := env.loginSession(t, "alice") req := withSession(httptest.NewRequest(http.MethodGet, "/map/api/admin/users", nil), sid) rr := httptest.NewRecorder() env.h.APIAdminUsers(rr, req) if rr.Code != http.StatusUnauthorized { t.Fatalf("expected 401 for non-admin, got %d", rr.Code) } } func TestAdminUsers_ListAndCreate(t *testing.T) { env := newTestEnv(t) env.createUser(t, "admin", "pass", app.Auths{app.AUTH_ADMIN}) sid := env.loginSession(t, "admin") req := withSession(httptest.NewRequest(http.MethodGet, "/map/api/admin/users", nil), sid) rr := httptest.NewRecorder() env.h.APIAdminUsers(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr.Code) } body := `{"user":"bob","pass":"secret","auths":["map","upload"]}` req2 := withSession(httptest.NewRequest(http.MethodPost, "/map/api/admin/users", strings.NewReader(body)), sid) rr2 := httptest.NewRecorder() env.h.APIAdminUsers(rr2, req2) if rr2.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", rr2.Code, rr2.Body.String()) } } func TestAdminSettings(t *testing.T) { env := newTestEnv(t) env.createUser(t, "admin", "pass", app.Auths{app.AUTH_ADMIN}) sid := env.loginSession(t, "admin") req := withSession(httptest.NewRequest(http.MethodGet, "/map/api/admin/settings", nil), sid) rr := httptest.NewRecorder() env.h.APIAdminSettingsGet(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr.Code) } body := `{"prefix":"pfx","title":"New Title","defaultHide":true}` req2 := withSession(httptest.NewRequest(http.MethodPost, "/map/api/admin/settings", strings.NewReader(body)), sid) rr2 := httptest.NewRecorder() env.h.APIAdminSettingsPost(rr2, req2) if rr2.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr2.Code) } req3 := withSession(httptest.NewRequest(http.MethodGet, "/map/api/admin/settings", nil), sid) rr3 := httptest.NewRecorder() env.h.APIAdminSettingsGet(rr3, req3) var resp struct { Prefix string `json:"prefix"` DefaultHide bool `json:"defaultHide"` Title string `json:"title"` } json.NewDecoder(rr3.Body).Decode(&resp) if resp.Prefix != "pfx" || !resp.DefaultHide || resp.Title != "New Title" { t.Fatalf("unexpected settings: %+v", resp) } } func TestAdminWipe(t *testing.T) { env := newTestEnv(t) env.createUser(t, "admin", "pass", app.Auths{app.AUTH_ADMIN}) sid := env.loginSession(t, "admin") req := withSession(httptest.NewRequest(http.MethodPost, "/map/api/admin/wipe", nil), sid) rr := httptest.NewRecorder() env.h.APIAdminWipe(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String()) } } func TestAdminMaps(t *testing.T) { env := newTestEnv(t) env.createUser(t, "admin", "pass", app.Auths{app.AUTH_ADMIN}) sid := env.loginSession(t, "admin") req := withSession(httptest.NewRequest(http.MethodGet, "/map/api/admin/maps", nil), sid) rr := httptest.NewRecorder() env.h.APIAdminMaps(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr.Code) } } func TestAPIRouter_NotFound(t *testing.T) { env := newTestEnv(t) req := httptest.NewRequest(http.MethodGet, "/map/api/nonexistent", nil) rr := httptest.NewRecorder() env.h.APIRouter(rr, req) if rr.Code != http.StatusNotFound { t.Fatalf("expected 404, got %d", rr.Code) } } func TestAPIRouter_Config(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_MAP}) sid := env.loginSession(t, "alice") req := withSession(httptest.NewRequest(http.MethodGet, "/map/api/config", nil), sid) rr := httptest.NewRecorder() env.h.APIRouter(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String()) } } func TestAPIGetChars_Unauthenticated(t *testing.T) { env := newTestEnv(t) req := httptest.NewRequest(http.MethodGet, "/map/api/v1/characters", nil) rr := httptest.NewRecorder() env.h.APIGetChars(rr, req) if rr.Code != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", rr.Code) } } func TestAPIGetMarkers_Unauthenticated(t *testing.T) { env := newTestEnv(t) req := httptest.NewRequest(http.MethodGet, "/map/api/v1/markers", nil) rr := httptest.NewRecorder() env.h.APIGetMarkers(rr, req) if rr.Code != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", rr.Code) } } func TestAPIGetMaps_Unauthenticated(t *testing.T) { env := newTestEnv(t) req := httptest.NewRequest(http.MethodGet, "/map/api/maps", nil) rr := httptest.NewRecorder() env.h.APIGetMaps(rr, req) if rr.Code != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", rr.Code) } } func TestAPIGetChars_NoMarkersAuth(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_MAP}) sid := env.loginSession(t, "alice") req := withSession(httptest.NewRequest(http.MethodGet, "/map/api/v1/characters", nil), sid) rr := httptest.NewRecorder() env.h.APIGetChars(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr.Code) } var chars []interface{} json.NewDecoder(rr.Body).Decode(&chars) if len(chars) != 0 { t.Fatal("expected empty array without markers auth") } } func TestAPIGetMarkers_NoMarkersAuth(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_MAP}) sid := env.loginSession(t, "alice") req := withSession(httptest.NewRequest(http.MethodGet, "/map/api/v1/markers", nil), sid) rr := httptest.NewRecorder() env.h.APIGetMarkers(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr.Code) } var markers []interface{} json.NewDecoder(rr.Body).Decode(&markers) if len(markers) != 0 { t.Fatal("expected empty array without markers auth") } } func TestRedirectLogout(t *testing.T) { env := newTestEnv(t) req := httptest.NewRequest(http.MethodGet, "/logout", nil) rr := httptest.NewRecorder() env.h.RedirectLogout(rr, req) if rr.Code != http.StatusFound { t.Fatalf("expected 302, got %d", rr.Code) } if loc := rr.Header().Get("Location"); loc != "/login" { t.Fatalf("expected redirect to /login, got %s", loc) } } func TestRedirectLogout_WrongPath(t *testing.T) { env := newTestEnv(t) req := httptest.NewRequest(http.MethodGet, "/other", nil) rr := httptest.NewRecorder() env.h.RedirectLogout(rr, req) if rr.Code != http.StatusNotFound { t.Fatalf("expected 404, got %d", rr.Code) } } func TestHandleServiceError(t *testing.T) { tests := []struct { name string err error status int }{ {"not found", apperr.ErrNotFound, http.StatusNotFound}, {"unauthorized", apperr.ErrUnauthorized, http.StatusUnauthorized}, {"forbidden", apperr.ErrForbidden, http.StatusForbidden}, {"bad request", apperr.ErrBadRequest, http.StatusBadRequest}, {"oauth only", apperr.ErrOAuthOnly, http.StatusUnauthorized}, {"provider unconfigured", apperr.ErrProviderUnconfigured, http.StatusServiceUnavailable}, {"state expired", apperr.ErrStateExpired, http.StatusBadRequest}, {"exchange failed", apperr.ErrExchangeFailed, http.StatusBadGateway}, {"unknown", errors.New("something else"), http.StatusInternalServerError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rr := httptest.NewRecorder() handlers.HandleServiceError(rr, tt.err) if rr.Code != tt.status { t.Fatalf("expected %d, got %d", tt.status, rr.Code) } }) } } func TestAdminUserByName(t *testing.T) { env := newTestEnv(t) env.createUser(t, "admin", "pass", app.Auths{app.AUTH_ADMIN}) env.createUser(t, "bob", "pass", app.Auths{app.AUTH_MAP, app.AUTH_UPLOAD}) sid := env.loginSession(t, "admin") req := withSession(httptest.NewRequest(http.MethodGet, "/map/api/admin/users/bob", nil), sid) rr := httptest.NewRecorder() env.h.APIAdminUserByName(rr, req, "bob") if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr.Code) } var resp struct { Username string `json:"username"` Auths []string `json:"auths"` } json.NewDecoder(rr.Body).Decode(&resp) if resp.Username != "bob" { t.Fatalf("expected bob, got %s", resp.Username) } } func TestAdminUserDelete(t *testing.T) { env := newTestEnv(t) env.createUser(t, "admin", "pass", app.Auths{app.AUTH_ADMIN}) env.createUser(t, "bob", "pass", app.Auths{app.AUTH_MAP}) sid := env.loginSession(t, "admin") req := withSession(httptest.NewRequest(http.MethodDelete, "/map/api/admin/users/bob", nil), sid) rr := httptest.NewRecorder() env.h.APIAdminUserDelete(rr, req, "bob") if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr.Code) } } func TestClientRouter_Locate(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_UPLOAD}) ctx := context.Background() tokens := env.auth.GenerateTokenForUser(ctx, "alice") if len(tokens) == 0 { t.Fatal("expected token") } gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 2, Y: 3}} raw, _ := json.Marshal(gd) env.st.Update(ctx, func(tx *bbolt.Tx) error { return env.st.PutGrid(tx, "g1", raw) }) req := httptest.NewRequest(http.MethodGet, "/client/"+tokens[0]+"/locate?gridID=g1", nil) rr := httptest.NewRecorder() env.h.ClientRouter(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String()) } if body := strings.TrimSpace(rr.Body.String()); body != "1;2;3" { t.Fatalf("expected body 1;2;3, got %q", body) } } func TestClientRouter_Locate_NotFound(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_UPLOAD}) tokens := env.auth.GenerateTokenForUser(context.Background(), "alice") if len(tokens) == 0 { t.Fatal("expected token") } req := httptest.NewRequest(http.MethodGet, "/client/"+tokens[0]+"/locate?gridID=ghost", nil) rr := httptest.NewRecorder() env.h.ClientRouter(rr, req) if rr.Code != http.StatusNotFound { t.Fatalf("expected 404, got %d", rr.Code) } } func TestClientRouter_CheckVersion(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_UPLOAD}) tokens := env.auth.GenerateTokenForUser(context.Background(), "alice") if len(tokens) == 0 { t.Fatal("expected token") } req := httptest.NewRequest(http.MethodGet, "/client/"+tokens[0]+"/checkVersion?version="+app.ClientVersion, nil) rr := httptest.NewRecorder() env.h.ClientRouter(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200 for matching version, got %d", rr.Code) } req2 := httptest.NewRequest(http.MethodGet, "/client/"+tokens[0]+"/checkVersion?version=other", nil) rr2 := httptest.NewRecorder() env.h.ClientRouter(rr2, req2) if rr2.Code != http.StatusBadRequest { t.Fatalf("expected 400 for wrong version, got %d", rr2.Code) } } func TestClientRouter_InvalidToken(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/client/badtoken/locate?gridID=g1", nil) rr := httptest.NewRecorder() env := newTestEnv(t) env.h.ClientRouter(rr, req) if rr.Code != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", rr.Code) } } func TestClientRouter_PositionUpdate_BodyTooLarge(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_UPLOAD}) tokens := env.auth.GenerateTokenForUser(context.Background(), "alice") if len(tokens) == 0 { t.Fatal("expected token") } // Body larger than maxClientBodySize (2MB) bigBody := bytes.Repeat([]byte("x"), 2*1024*1024+1) req := httptest.NewRequest(http.MethodPost, "/client/"+tokens[0]+"/positionUpdate", bytes.NewReader(bigBody)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() env.h.ClientRouter(rr, req) if rr.Code != http.StatusRequestEntityTooLarge { t.Fatalf("expected 413, got %d: %s", rr.Code, rr.Body.String()) } } func TestClientRouter_MarkerUpdate_BodyTooLarge(t *testing.T) { env := newTestEnv(t) env.createUser(t, "alice", "pass", app.Auths{app.AUTH_UPLOAD}) tokens := env.auth.GenerateTokenForUser(context.Background(), "alice") if len(tokens) == 0 { t.Fatal("expected token") } bigBody := bytes.Repeat([]byte("x"), 2*1024*1024+1) req := httptest.NewRequest(http.MethodPost, "/client/"+tokens[0]+"/markerUpdate", bytes.NewReader(bigBody)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() env.h.ClientRouter(rr, req) if rr.Code != http.StatusRequestEntityTooLarge { t.Fatalf("expected 413, got %d: %s", rr.Code, rr.Body.String()) } }