- Updated docker-compose.tools.yml to mount source code at /src and set working directory for backend tools, ensuring proper Go module caching. - Modified Dockerfile.tools to install the latest golangci-lint version compatible with Go 1.24 and adjusted working directory for build-time operations. - Enhanced Makefile to build backend tools before running tests and linting, ensuring dependencies are up-to-date and improving overall workflow efficiency. - Refactored test and handler files to include error handling for database operations, enhancing reliability and debugging capabilities.
720 lines
22 KiB
Go
720 lines
22 KiB
Go
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)
|
|
if err := env.st.Update(context.Background(), func(tx *bbolt.Tx) error {
|
|
return env.st.PutUser(tx, username, raw)
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
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)
|
|
if err := env.st.Update(ctx, func(tx *bbolt.Tx) error {
|
|
return env.st.PutGrid(tx, "g1", raw)
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
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())
|
|
}
|
|
}
|