Refactor Docker and Makefile configurations for improved build processes

- 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.
This commit is contained in:
2026-03-04 13:59:00 +03:00
parent fc42d86ca0
commit 761fbaed55
20 changed files with 352 additions and 165 deletions

View File

@@ -1,7 +1,10 @@
# Backend tools image: Go + golangci-lint for test, fmt, lint. # Backend tools image: Go + golangci-lint for test, fmt, lint.
# Source is mounted at /hnh-map at run time via docker-compose.tools.yml. # Source is mounted at /src at run time; this WORKDIR is only for build-time go mod download.
FROM golang:1.24-alpine FROM golang:1.24-alpine
RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.61.0 # v1.64+ required for Go 1.24 (export data format); see https://github.com/golangci/golangci-lint/issues/5225
RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.0
WORKDIR /hnh-map WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download

View File

@@ -11,17 +11,23 @@ build:
test: test-backend test-frontend test: test-backend test-frontend
test-backend: test-backend:
$(TOOLS_COMPOSE) run --rm backend-tools go test ./... $(TOOLS_COMPOSE) build backend-tools
$(TOOLS_COMPOSE) run --rm backend-tools sh -c "go mod download && go test ./..."
test-frontend: test-frontend:
$(TOOLS_COMPOSE) build frontend-tools
$(TOOLS_COMPOSE) run --rm frontend-tools sh -c "npm ci && npm test" $(TOOLS_COMPOSE) run --rm frontend-tools sh -c "npm ci && npm test"
lint: lint:
$(TOOLS_COMPOSE) run --rm backend-tools golangci-lint run $(TOOLS_COMPOSE) build backend-tools
$(TOOLS_COMPOSE) build frontend-tools
$(TOOLS_COMPOSE) run --rm backend-tools sh -c "go mod download && golangci-lint run"
$(TOOLS_COMPOSE) run --rm frontend-tools sh -c "npm ci && npm run lint" $(TOOLS_COMPOSE) run --rm frontend-tools sh -c "npm ci && npm run lint"
fmt: fmt:
$(TOOLS_COMPOSE) run --rm backend-tools go fmt ./... $(TOOLS_COMPOSE) build backend-tools
$(TOOLS_COMPOSE) build frontend-tools
$(TOOLS_COMPOSE) run --rm backend-tools sh -c "go mod download && go fmt ./..."
$(TOOLS_COMPOSE) run --rm frontend-tools sh -c "npm ci && npm run format" $(TOOLS_COMPOSE) run --rm frontend-tools sh -c "npm ci && npm run format"
generate-frontend: generate-frontend:

View File

@@ -1,13 +1,18 @@
# One-off tools: test, lint, fmt. Use with: docker compose -f docker-compose.tools.yml run --rm <service> <cmd> # One-off tools: test, lint, fmt. Use with: docker compose -f docker-compose.tools.yml run --rm <service> <cmd>
# Source is mounted so commands run against current code. # Source is mounted so commands run against current code.
# Backend: mount at /src so the image's /go module cache (from build) is not overwritten.
services: services:
backend-tools: backend-tools:
build: build:
context: . context: .
dockerfile: Dockerfile.tools dockerfile: Dockerfile.tools
working_dir: /src
environment:
GOPATH: /go
GOMODCACHE: /go/pkg/mod
volumes: volumes:
- .:/hnh-map - .:/src
# Default command; override when running (e.g. go test ./..., golangci-lint run). # Default command; override when running (e.g. go test ./..., golangci-lint run).
command: ["go", "test", "./..."] command: ["go", "test", "./..."]

View File

@@ -1,11 +1,14 @@
// @ts-check // @ts-check
import withNuxt from './.nuxt/eslint.config.mjs' import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt({ export default withNuxt(
rules: { { ignores: ['eslint.config.mjs'] },
'@typescript-eslint/no-explicit-any': 'warn', {
'@typescript-eslint/no-unused-vars': 'warn', rules: {
'@typescript-eslint/consistent-type-imports': ['warn', { prefer: 'type-imports' }], '@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/consistent-type-imports': ['warn', { prefer: 'type-imports' }],
'@typescript-eslint/no-require-imports': 'off',
},
}, },
}) )

View File

@@ -66,7 +66,7 @@ func (h *Handlers) clientLocate(rw http.ResponseWriter, req *http.Request) {
} }
rw.Header().Set("Content-Type", "text/plain") rw.Header().Set("Content-Type", "text/plain")
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
rw.Write([]byte(result)) _, _ = rw.Write([]byte(result))
} }
func (h *Handlers) clientGridUpdate(rw http.ResponseWriter, req *http.Request) { func (h *Handlers) clientGridUpdate(rw http.ResponseWriter, req *http.Request) {
@@ -85,7 +85,7 @@ func (h *Handlers) clientGridUpdate(rw http.ResponseWriter, req *http.Request) {
} }
rw.Header().Set("Content-Type", "application/json") rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
json.NewEncoder(rw).Encode(result.Response) _ = json.NewEncoder(rw).Encode(result.Response)
} }
func (h *Handlers) clientGridUpload(rw http.ResponseWriter, req *http.Request) { func (h *Handlers) clientGridUpload(rw http.ResponseWriter, req *http.Request) {

View File

@@ -64,9 +64,11 @@ func (env *testEnv) createUser(t *testing.T, username, password string, auths ap
hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost) hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
u := app.User{Pass: hash, Auths: auths} u := app.User{Pass: hash, Auths: auths}
raw, _ := json.Marshal(u) raw, _ := json.Marshal(u)
env.st.Update(context.Background(), func(tx *bbolt.Tx) error { if err := env.st.Update(context.Background(), func(tx *bbolt.Tx) error {
return env.st.PutUser(tx, username, raw) return env.st.PutUser(tx, username, raw)
}) }); err != nil {
t.Fatal(err)
}
} }
func (env *testEnv) loginSession(t *testing.T, username string) string { func (env *testEnv) loginSession(t *testing.T, username string) string {
@@ -90,7 +92,7 @@ func TestAPISetup_NoUsers(t *testing.T) {
} }
var resp struct{ SetupRequired bool } var resp struct{ SetupRequired bool }
json.NewDecoder(rr.Body).Decode(&resp) _ = json.NewDecoder(rr.Body).Decode(&resp)
if !resp.SetupRequired { if !resp.SetupRequired {
t.Fatal("expected setupRequired=true") t.Fatal("expected setupRequired=true")
} }
@@ -105,7 +107,7 @@ func TestAPISetup_WithUsers(t *testing.T) {
env.h.APISetup(rr, req) env.h.APISetup(rr, req)
var resp struct{ SetupRequired bool } var resp struct{ SetupRequired bool }
json.NewDecoder(rr.Body).Decode(&resp) _ = json.NewDecoder(rr.Body).Decode(&resp)
if resp.SetupRequired { if resp.SetupRequired {
t.Fatal("expected setupRequired=false with users") t.Fatal("expected setupRequired=false with users")
} }
@@ -196,7 +198,7 @@ func TestAPIMe_Authenticated(t *testing.T) {
Username string `json:"username"` Username string `json:"username"`
Auths []string `json:"auths"` Auths []string `json:"auths"`
} }
json.NewDecoder(rr.Body).Decode(&resp) _ = json.NewDecoder(rr.Body).Decode(&resp)
if resp.Username != "alice" { if resp.Username != "alice" {
t.Fatalf("expected alice, got %s", resp.Username) t.Fatalf("expected alice, got %s", resp.Username)
} }
@@ -245,7 +247,7 @@ func TestAPIMeTokens_Authenticated(t *testing.T) {
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String()) t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
} }
var resp struct{ Tokens []string } var resp struct{ Tokens []string }
json.NewDecoder(rr.Body).Decode(&resp) _ = json.NewDecoder(rr.Body).Decode(&resp)
if len(resp.Tokens) != 1 { if len(resp.Tokens) != 1 {
t.Fatalf("expected 1 token, got %d", len(resp.Tokens)) t.Fatalf("expected 1 token, got %d", len(resp.Tokens))
} }
@@ -396,7 +398,7 @@ func TestAdminSettings(t *testing.T) {
DefaultHide bool `json:"defaultHide"` DefaultHide bool `json:"defaultHide"`
Title string `json:"title"` Title string `json:"title"`
} }
json.NewDecoder(rr3.Body).Decode(&resp) _ = json.NewDecoder(rr3.Body).Decode(&resp)
if resp.Prefix != "pfx" || !resp.DefaultHide || resp.Title != "New Title" { if resp.Prefix != "pfx" || !resp.DefaultHide || resp.Title != "New Title" {
t.Fatalf("unexpected settings: %+v", resp) t.Fatalf("unexpected settings: %+v", resp)
} }
@@ -493,7 +495,7 @@ func TestAPIGetChars_NoMarkersAuth(t *testing.T) {
t.Fatalf("expected 200, got %d", rr.Code) t.Fatalf("expected 200, got %d", rr.Code)
} }
var chars []interface{} var chars []interface{}
json.NewDecoder(rr.Body).Decode(&chars) _ = json.NewDecoder(rr.Body).Decode(&chars)
if len(chars) != 0 { if len(chars) != 0 {
t.Fatal("expected empty array without markers auth") t.Fatal("expected empty array without markers auth")
} }
@@ -511,7 +513,7 @@ func TestAPIGetMarkers_NoMarkersAuth(t *testing.T) {
t.Fatalf("expected 200, got %d", rr.Code) t.Fatalf("expected 200, got %d", rr.Code)
} }
var markers []interface{} var markers []interface{}
json.NewDecoder(rr.Body).Decode(&markers) _ = json.NewDecoder(rr.Body).Decode(&markers)
if len(markers) != 0 { if len(markers) != 0 {
t.Fatal("expected empty array without markers auth") t.Fatal("expected empty array without markers auth")
} }
@@ -583,7 +585,7 @@ func TestAdminUserByName(t *testing.T) {
Username string `json:"username"` Username string `json:"username"`
Auths []string `json:"auths"` Auths []string `json:"auths"`
} }
json.NewDecoder(rr.Body).Decode(&resp) _ = json.NewDecoder(rr.Body).Decode(&resp)
if resp.Username != "bob" { if resp.Username != "bob" {
t.Fatalf("expected bob, got %s", resp.Username) t.Fatalf("expected bob, got %s", resp.Username)
} }
@@ -613,9 +615,11 @@ func TestClientRouter_Locate(t *testing.T) {
} }
gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 2, Y: 3}} gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 2, Y: 3}}
raw, _ := json.Marshal(gd) raw, _ := json.Marshal(gd)
env.st.Update(ctx, func(tx *bbolt.Tx) error { if err := env.st.Update(ctx, func(tx *bbolt.Tx) error {
return env.st.PutGrid(tx, "g1", raw) return env.st.PutGrid(tx, "g1", raw)
}) }); err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodGet, "/client/"+tokens[0]+"/locate?gridID=g1", nil) req := httptest.NewRequest(http.MethodGet, "/client/"+tokens[0]+"/locate?gridID=g1", nil)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()

View File

@@ -55,7 +55,7 @@ func (h *Handlers) WatchGridUpdates(rw http.ResponseWriter, req *http.Request) {
tileCache := []services.TileCache{} tileCache := []services.TileCache{}
raw, _ := json.Marshal(tileCache) raw, _ := json.Marshal(tileCache)
fmt.Fprint(rw, "data: ") fmt.Fprint(rw, "data: ")
rw.Write(raw) _, _ = rw.Write(raw)
fmt.Fprint(rw, "\n\n") fmt.Fprint(rw, "\n\n")
flusher.Flush() flusher.Flush()
@@ -93,13 +93,13 @@ func (h *Handlers) WatchGridUpdates(rw http.ResponseWriter, req *http.Request) {
} }
fmt.Fprint(rw, "event: merge\n") fmt.Fprint(rw, "event: merge\n")
fmt.Fprint(rw, "data: ") fmt.Fprint(rw, "data: ")
rw.Write(raw) _, _ = rw.Write(raw)
fmt.Fprint(rw, "\n\n") fmt.Fprint(rw, "\n\n")
flusher.Flush() flusher.Flush()
case <-ticker.C: case <-ticker.C:
raw, _ := json.Marshal(tileCache) raw, _ := json.Marshal(tileCache)
fmt.Fprint(rw, "data: ") fmt.Fprint(rw, "data: ")
rw.Write(raw) _, _ = rw.Write(raw)
fmt.Fprint(rw, "\n\n") fmt.Fprint(rw, "\n\n")
tileCache = tileCache[:0] tileCache = tileCache[:0]
flusher.Flush() flusher.Flush()
@@ -152,7 +152,7 @@ func (h *Handlers) GridTile(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Content-Type", "image/png") rw.Header().Set("Content-Type", "image/png")
rw.Header().Set("Cache-Control", "private, max-age=3600") rw.Header().Set("Cache-Control", "private, max-age=3600")
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
rw.Write(transparentPNG) _, _ = rw.Write(transparentPNG)
return return
} }

View File

@@ -172,7 +172,9 @@ var migrations = []func(tx *bbolt.Tx) error{
if err != nil { if err != nil {
return err return err
} }
users.Put(k, raw) if err := users.Put(k, raw); err != nil {
return err
}
} }
return nil return nil
}) })

View File

@@ -26,7 +26,7 @@ func TestRunMigrations_FreshDB(t *testing.T) {
t.Fatalf("migrations failed on fresh DB: %v", err) t.Fatalf("migrations failed on fresh DB: %v", err)
} }
db.View(func(tx *bbolt.Tx) error { if err := db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket(store.BucketConfig) b := tx.Bucket(store.BucketConfig)
if b == nil { if b == nil {
t.Fatal("expected config bucket after migrations") t.Fatal("expected config bucket after migrations")
@@ -40,13 +40,15 @@ func TestRunMigrations_FreshDB(t *testing.T) {
t.Fatalf("expected default title, got %s", title) t.Fatalf("expected default title, got %s", title)
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
if tx, _ := db.Begin(false); tx != nil { if tx, _ := db.Begin(false); tx != nil {
if tx.Bucket(store.BucketOAuthStates) == nil { if tx.Bucket(store.BucketOAuthStates) == nil {
t.Fatal("expected oauth_states bucket after migrations") t.Fatal("expected oauth_states bucket after migrations")
} }
tx.Rollback() _ = tx.Rollback()
} }
} }
@@ -61,7 +63,7 @@ func TestRunMigrations_Idempotent(t *testing.T) {
t.Fatalf("second run failed: %v", err) t.Fatalf("second run failed: %v", err)
} }
db.View(func(tx *bbolt.Tx) error { if err := db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket(store.BucketConfig) b := tx.Bucket(store.BucketConfig)
if b == nil { if b == nil {
t.Fatal("expected config bucket") t.Fatal("expected config bucket")
@@ -71,7 +73,9 @@ func TestRunMigrations_Idempotent(t *testing.T) {
t.Fatal("expected version key") t.Fatal("expected version key")
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
func TestRunMigrations_SetsVersion(t *testing.T) { func TestRunMigrations_SetsVersion(t *testing.T) {
@@ -81,11 +85,13 @@ func TestRunMigrations_SetsVersion(t *testing.T) {
} }
var version string var version string
db.View(func(tx *bbolt.Tx) error { if err := db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket(store.BucketConfig) b := tx.Bucket(store.BucketConfig)
version = string(b.Get([]byte("version"))) version = string(b.Get([]byte("version")))
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
if version == "" || version == "0" { if version == "" || version == "0" {
t.Fatalf("expected non-zero version, got %q", version) t.Fatalf("expected non-zero version, got %q", version)

View File

@@ -101,7 +101,9 @@ func (s *AdminService) DeleteUser(ctx context.Context, username string) error {
return err return err
} }
for _, tok := range u.Tokens { for _, tok := range u.Tokens {
s.st.DeleteToken(tx, tok) if err := s.st.DeleteToken(tx, tok); err != nil {
return err
}
} }
} }
return s.st.DeleteUser(tx, username) return s.st.DeleteUser(tx, username)
@@ -129,17 +131,25 @@ func (s *AdminService) GetSettings(ctx context.Context) (prefix string, defaultH
func (s *AdminService) UpdateSettings(ctx context.Context, prefix *string, defaultHide *bool, title *string) error { func (s *AdminService) UpdateSettings(ctx context.Context, prefix *string, defaultHide *bool, title *string) error {
return s.st.Update(ctx, func(tx *bbolt.Tx) error { return s.st.Update(ctx, func(tx *bbolt.Tx) error {
if prefix != nil { if prefix != nil {
s.st.PutConfig(tx, "prefix", []byte(*prefix)) if err := s.st.PutConfig(tx, "prefix", []byte(*prefix)); err != nil {
return err
}
} }
if defaultHide != nil { if defaultHide != nil {
if *defaultHide { if *defaultHide {
s.st.PutConfig(tx, "defaultHide", []byte("1")) if err := s.st.PutConfig(tx, "defaultHide", []byte("1")); err != nil {
return err
}
} else { } else {
s.st.DeleteConfig(tx, "defaultHide") if err := s.st.DeleteConfig(tx, "defaultHide"); err != nil {
return err
}
} }
} }
if title != nil { if title != nil {
s.st.PutConfig(tx, "title", []byte(*title)) if err := s.st.PutConfig(tx, "title", []byte(*title)); err != nil {
return err
}
} }
return nil return nil
}) })
@@ -264,7 +274,9 @@ func (s *AdminService) WipeTile(ctx context.Context, mapid, x, y int) error {
return err return err
} }
for _, id := range ids { for _, id := range ids {
grids.Delete(id) if err := grids.Delete(id); err != nil {
return err
}
} }
return nil return nil
}); err != nil { }); err != nil {
@@ -310,7 +322,9 @@ func (s *AdminService) SetCoords(ctx context.Context, mapid, fx, fy, tx2, ty int
g.Coord.X += diff.X g.Coord.X += diff.X
g.Coord.Y += diff.Y g.Coord.Y += diff.Y
raw, _ := json.Marshal(g) raw, _ := json.Marshal(g)
grids.Put(k, raw) if err := grids.Put(k, raw); err != nil {
return err
}
} }
return nil return nil
}); err != nil { }); err != nil {
@@ -367,7 +381,9 @@ func (s *AdminService) HideMarker(ctx context.Context, markerID string) error {
} }
m.Hidden = true m.Hidden = true
raw, _ = json.Marshal(m) raw, _ = json.Marshal(m)
grid.Put(key, raw) if err := grid.Put(key, raw); err != nil {
return err
}
return nil return nil
}) })
} }

View File

@@ -234,7 +234,7 @@ func TestToggleMapHidden(t *testing.T) {
admin, _ := newTestAdmin(t) admin, _ := newTestAdmin(t)
ctx := context.Background() ctx := context.Background()
admin.UpdateMap(ctx, 1, "world", false, false) _ = admin.UpdateMap(ctx, 1, "world", false, false)
mi, err := admin.ToggleMapHidden(ctx, 1) mi, err := admin.ToggleMapHidden(ctx, 1)
if err != nil { if err != nil {
@@ -257,19 +257,27 @@ func TestWipe(t *testing.T) {
admin, st := newTestAdmin(t) admin, st := newTestAdmin(t)
ctx := context.Background() ctx := context.Background()
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
st.PutGrid(tx, "g1", []byte("data")) if err := st.PutGrid(tx, "g1", []byte("data")); err != nil {
st.PutMap(tx, 1, []byte("data")) return err
st.PutTile(tx, 1, 0, "0_0", []byte("data")) }
st.CreateMarkersBuckets(tx) if err := st.PutMap(tx, 1, []byte("data")); err != nil {
return nil return err
}) }
if err := st.PutTile(tx, 1, 0, "0_0", []byte("data")); err != nil {
return err
}
_, _, err := st.CreateMarkersBuckets(tx)
return err
}); err != nil {
t.Fatal(err)
}
if err := admin.Wipe(ctx); err != nil { if err := admin.Wipe(ctx); err != nil {
t.Fatal(err) t.Fatal(err)
} }
st.View(ctx, func(tx *bbolt.Tx) error { if err := st.View(ctx, func(tx *bbolt.Tx) error {
if st.GetGrid(tx, "g1") != nil { if st.GetGrid(tx, "g1") != nil {
t.Fatal("expected grids wiped") t.Fatal("expected grids wiped")
} }
@@ -283,7 +291,9 @@ func TestWipe(t *testing.T) {
t.Fatal("expected markers wiped") t.Fatal("expected markers wiped")
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
func TestGetMap_NotFound(t *testing.T) { func TestGetMap_NotFound(t *testing.T) {

View File

@@ -53,7 +53,7 @@ func (s *AuthService) GetSession(ctx context.Context, req *http.Request) *app.Se
return nil return nil
} }
var sess *app.Session var sess *app.Session
s.st.View(ctx, func(tx *bbolt.Tx) error { if err := s.st.View(ctx, func(tx *bbolt.Tx) error {
raw := s.st.GetSession(tx, c.Value) raw := s.st.GetSession(tx, c.Value)
if raw == nil { if raw == nil {
return nil return nil
@@ -77,7 +77,9 @@ func (s *AuthService) GetSession(ctx context.Context, req *http.Request) *app.Se
} }
sess.Auths = u.Auths sess.Auths = u.Auths
return nil return nil
}) }); err != nil {
return nil
}
return sess return sess
} }
@@ -165,7 +167,7 @@ func (s *AuthService) GetUserByUsername(ctx context.Context, username string) *a
// SetupRequired returns true if no users exist (first run). // SetupRequired returns true if no users exist (first run).
func (s *AuthService) SetupRequired(ctx context.Context) bool { func (s *AuthService) SetupRequired(ctx context.Context) bool {
var required bool var required bool
s.st.View(ctx, func(tx *bbolt.Tx) error { _ = s.st.View(ctx, func(tx *bbolt.Tx) error {
if s.st.UserCount(tx) == 0 { if s.st.UserCount(tx) == 0 {
required = true required = true
} }
@@ -181,7 +183,7 @@ func (s *AuthService) BootstrapAdmin(ctx context.Context, username, pass, bootst
} }
var created bool var created bool
var u *app.User var u *app.User
s.st.Update(ctx, func(tx *bbolt.Tx) error { if err := s.st.Update(ctx, func(tx *bbolt.Tx) error {
if s.st.GetUser(tx, "admin") != nil { if s.st.GetUser(tx, "admin") != nil {
return nil return nil
} }
@@ -200,7 +202,9 @@ func (s *AuthService) BootstrapAdmin(ctx context.Context, username, pass, bootst
created = true created = true
u = &user u = &user
return nil return nil
}) }); err != nil {
return nil
}
if created { if created {
return u return u
} }
@@ -239,7 +243,7 @@ func (s *AuthService) GenerateTokenForUser(ctx context.Context, username string)
} }
token := hex.EncodeToString(tokenRaw) token := hex.EncodeToString(tokenRaw)
var tokens []string var tokens []string
s.st.Update(ctx, func(tx *bbolt.Tx) error { _ = s.st.Update(ctx, func(tx *bbolt.Tx) error {
uRaw := s.st.GetUser(tx, username) uRaw := s.st.GetUser(tx, username)
u := app.User{} u := app.User{}
if uRaw != nil { if uRaw != nil {
@@ -250,7 +254,9 @@ func (s *AuthService) GenerateTokenForUser(ctx context.Context, username string)
u.Tokens = append(u.Tokens, token) u.Tokens = append(u.Tokens, token)
tokens = u.Tokens tokens = u.Tokens
buf, _ := json.Marshal(u) buf, _ := json.Marshal(u)
s.st.PutUser(tx, username, buf) if err := s.st.PutUser(tx, username, buf); err != nil {
return err
}
return s.st.PutToken(tx, token, username) return s.st.PutToken(tx, token, username)
}) })
return tokens return tokens
@@ -522,7 +528,9 @@ func (s *AuthService) findOrCreateOAuthUser(ctx context.Context, provider, sub,
user.Email = email user.Email = email
} }
raw, _ = json.Marshal(user) raw, _ = json.Marshal(user)
s.st.PutUser(tx, username, raw) if err := s.st.PutUser(tx, username, raw); err != nil {
return err
}
} }
return nil return nil
} }

View File

@@ -41,9 +41,11 @@ func createUser(t *testing.T, st *store.Store, username, password string, auths
} }
u := app.User{Pass: hash, Auths: auths} u := app.User{Pass: hash, Auths: auths}
raw, _ := json.Marshal(u) raw, _ := json.Marshal(u)
st.Update(context.Background(), func(tx *bbolt.Tx) error { if err := st.Update(context.Background(), func(tx *bbolt.Tx) error {
return st.PutUser(tx, username, raw) return st.PutUser(tx, username, raw)
}) }); err != nil {
t.Fatal(err)
}
} }
func TestSetupRequired_EmptyDB(t *testing.T) { func TestSetupRequired_EmptyDB(t *testing.T) {
@@ -246,9 +248,11 @@ func TestGetUserTokensAndPrefix(t *testing.T) {
ctx := context.Background() ctx := context.Background()
createUser(t, st, "alice", "pass", app.Auths{app.AUTH_UPLOAD}) createUser(t, st, "alice", "pass", app.Auths{app.AUTH_UPLOAD})
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
return st.PutConfig(tx, "prefix", []byte("myprefix")) return st.PutConfig(tx, "prefix", []byte("myprefix"))
}) }); err != nil {
t.Fatal(err)
}
auth.GenerateTokenForUser(ctx, "alice") auth.GenerateTokenForUser(ctx, "alice")
tokens, prefix := auth.GetUserTokensAndPrefix(ctx, "alice") tokens, prefix := auth.GetUserTokensAndPrefix(ctx, "alice")
@@ -288,9 +292,11 @@ func TestValidateClientToken_NoUploadPerm(t *testing.T) {
ctx := context.Background() ctx := context.Background()
createUser(t, st, "alice", "pass", app.Auths{app.AUTH_MAP}) createUser(t, st, "alice", "pass", app.Auths{app.AUTH_MAP})
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
return st.PutToken(tx, "tok123", "alice") return st.PutToken(tx, "tok123", "alice")
}) }); err != nil {
t.Fatal(err)
}
_, err := auth.ValidateClientToken(ctx, "tok123") _, err := auth.ValidateClientToken(ctx, "tok123")
if err == nil { if err == nil {

View File

@@ -141,7 +141,9 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate)
if err != nil { if err != nil {
return err return err
} }
grids.Put([]byte(grid), raw) if err := grids.Put([]byte(grid), raw); err != nil {
return err
}
greq.GridRequests = append(greq.GridRequests, grid) greq.GridRequests = append(greq.GridRequests, grid)
} }
} }
@@ -192,7 +194,9 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate)
if err != nil { if err != nil {
return err return err
} }
grids.Put([]byte(grid), raw) if err := grids.Put([]byte(grid), raw); err != nil {
return err
}
greq.GridRequests = append(greq.GridRequests, grid) greq.GridRequests = append(greq.GridRequests, grid)
} }
} }
@@ -207,7 +211,7 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate)
} }
} }
if len(maps) > 1 { if len(maps) > 1 {
grids.ForEach(func(k, v []byte) error { if err := grids.ForEach(func(k, v []byte) error {
gd := app.GridData{} gd := app.GridData{}
if err := json.Unmarshal(v, &gd); err != nil { if err := json.Unmarshal(v, &gd); err != nil {
return err return err
@@ -244,16 +248,22 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate)
File: td.File, File: td.File,
}) })
} }
grids.Put(k, raw) if err := grids.Put(k, raw); err != nil {
return err
}
} }
return nil return nil
}) }); err != nil {
return err
}
} }
for mergeid, merge := range maps { for mergeid, merge := range maps {
if mapid == mergeid { if mapid == mergeid {
continue continue
} }
mapB.Delete([]byte(strconv.Itoa(mergeid))) if err := mapB.Delete([]byte(strconv.Itoa(mergeid))); err != nil {
return err
}
slog.Info("reporting merge", "from", mergeid, "to", mapid) slog.Info("reporting merge", "from", mergeid, "to", mapid)
s.mapSvc.ReportMerge(mergeid, mapid, app.Coord{X: offset.X - merge.X, Y: offset.Y - merge.Y}) s.mapSvc.ReportMerge(mergeid, mapid, app.Coord{X: offset.X - merge.X, Y: offset.Y - merge.Y})
} }
@@ -271,10 +281,10 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate)
func (s *ClientService) ProcessGridUpload(ctx context.Context, id string, extraData string, fileReader io.Reader) error { func (s *ClientService) ProcessGridUpload(ctx context.Context, id string, extraData string, fileReader io.Reader) error {
if extraData != "" { if extraData != "" {
ed := ExtraData{} ed := ExtraData{}
json.Unmarshal([]byte(extraData), &ed) _ = json.Unmarshal([]byte(extraData), &ed)
if ed.Season == 3 { if ed.Season == 3 {
needTile := false needTile := false
s.st.Update(ctx, func(tx *bbolt.Tx) error { if err := s.st.Update(ctx, func(tx *bbolt.Tx) error {
raw := s.st.GetGrid(tx, id) raw := s.st.GetGrid(tx, id)
if raw == nil { if raw == nil {
return fmt.Errorf("unknown grid id: %s", id) return fmt.Errorf("unknown grid id: %s", id)
@@ -301,7 +311,9 @@ func (s *ClientService) ProcessGridUpload(ctx context.Context, id string, extraD
} }
raw, _ = json.Marshal(cur) raw, _ = json.Marshal(cur)
return s.st.PutGrid(tx, id, raw) return s.st.PutGrid(tx, id, raw)
}) }); err != nil {
return err
}
if !needTile { if !needTile {
slog.Debug("ignoring tile upload: winter") slog.Debug("ignoring tile upload: winter")
return nil return nil
@@ -316,7 +328,7 @@ func (s *ClientService) ProcessGridUpload(ctx context.Context, id string, extraD
cur := app.GridData{} cur := app.GridData{}
mapid := 0 mapid := 0
s.st.Update(ctx, func(tx *bbolt.Tx) error { if err := s.st.Update(ctx, func(tx *bbolt.Tx) error {
raw := s.st.GetGrid(tx, id) raw := s.st.GetGrid(tx, id)
if raw == nil { if raw == nil {
return fmt.Errorf("unknown grid id: %s", id) return fmt.Errorf("unknown grid id: %s", id)
@@ -331,7 +343,9 @@ func (s *ClientService) ProcessGridUpload(ctx context.Context, id string, extraD
} }
raw, _ = json.Marshal(cur) raw, _ = json.Marshal(cur)
return s.st.PutGrid(tx, id, raw) return s.st.PutGrid(tx, id, raw)
}) }); err != nil {
return err
}
if updateTile { if updateTile {
gridDir := fmt.Sprintf("%s/grids", s.mapSvc.GridStorage()) gridDir := fmt.Sprintf("%s/grids", s.mapSvc.GridStorage())
@@ -374,7 +388,7 @@ func (s *ClientService) UpdatePositions(ctx context.Context, data []byte) error
} }
gridDataByID := make(map[string]app.GridData) gridDataByID := make(map[string]app.GridData)
s.st.View(ctx, func(tx *bbolt.Tx) error { if err := s.st.View(ctx, func(tx *bbolt.Tx) error {
for _, craw := range craws { for _, craw := range craws {
raw := s.st.GetGrid(tx, craw.GridID) raw := s.st.GetGrid(tx, craw.GridID)
if raw != nil { if raw != nil {
@@ -385,7 +399,9 @@ func (s *ClientService) UpdatePositions(ctx context.Context, data []byte) error
} }
} }
return nil return nil
}) }); err != nil {
return err
}
username, _ := ctx.Value(app.ClientUsernameKey).(string) username, _ := ctx.Value(app.ClientUsernameKey).(string)
@@ -478,8 +494,12 @@ func (s *ClientService) UploadMarkers(ctx context.Context, data []byte) error {
Image: img, Image: img,
} }
raw, _ := json.Marshal(m) raw, _ := json.Marshal(m)
grid.Put(key, raw) if err := grid.Put(key, raw); err != nil {
idB.Put(idKey, key) return err
}
if err := idB.Put(idKey, key); err != nil {
return err
}
} }
return nil return nil
}) })

View File

@@ -58,9 +58,11 @@ func TestClientLocate_Found(t *testing.T) {
ctx := context.Background() ctx := context.Background()
gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 2, Y: 3}} gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 2, Y: 3}}
raw, _ := json.Marshal(gd) raw, _ := json.Marshal(gd)
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
return st.PutGrid(tx, "g1", raw) return st.PutGrid(tx, "g1", raw)
}) }); err != nil {
t.Fatal(err)
}
result, err := client.Locate(ctx, "g1") result, err := client.Locate(ctx, "g1")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@@ -111,7 +111,7 @@ func (s *ExportService) Export(ctx context.Context, w io.Writer) error {
if markersb != nil { if markersb != nil {
markersgrid := markersb.Bucket(store.BucketMarkersGrid) markersgrid := markersb.Bucket(store.BucketMarkersGrid)
if markersgrid != nil { if markersgrid != nil {
markersgrid.ForEach(func(k, v []byte) error { if err := markersgrid.ForEach(func(k, v []byte) error {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
@@ -125,7 +125,9 @@ func (s *ExportService) Export(ctx context.Context, w io.Writer) error {
maps[gridMap[marker.GridID]].Markers[marker.GridID] = append(maps[gridMap[marker.GridID]].Markers[marker.GridID], marker) maps[gridMap[marker.GridID]].Markers[marker.GridID] = append(maps[gridMap[marker.GridID]].Markers[marker.GridID], marker)
} }
return nil return nil
}) }); err != nil {
return err
}
} }
} }
return nil return nil
@@ -218,7 +220,11 @@ func (s *ExportService) Merge(ctx context.Context, zr *zip.Reader) error {
f.Close() f.Close()
return err return err
} }
io.Copy(f, r) if _, err := io.Copy(f, r); err != nil {
r.Close()
f.Close()
return err
}
r.Close() r.Close()
f.Close() f.Close()
newTiles[strings.TrimSuffix(filepath.Base(fhdr.Name), ".png")] = struct{}{} newTiles[strings.TrimSuffix(filepath.Base(fhdr.Name), ".png")] = struct{}{}
@@ -290,8 +296,12 @@ func (s *ExportService) processMergeJSON(
Image: img, Image: img,
} }
raw, _ := json.Marshal(m) raw, _ := json.Marshal(m)
mgrid.Put(key, raw) if err := mgrid.Put(key, raw); err != nil {
idB.Put(idKey, key) return err
}
if err := idB.Put(idKey, key); err != nil {
return err
}
} }
} }
@@ -333,7 +343,9 @@ func (s *ExportService) processMergeJSON(
if err != nil { if err != nil {
return err return err
} }
grids.Put([]byte(grid), raw) if err := grids.Put([]byte(grid), raw); err != nil {
return err
}
} }
return nil return nil
} }
@@ -372,11 +384,13 @@ func (s *ExportService) processMergeJSON(
if err != nil { if err != nil {
return err return err
} }
grids.Put([]byte(grid), raw) if err := grids.Put([]byte(grid), raw); err != nil {
return err
}
} }
if len(existingMaps) > 1 { if len(existingMaps) > 1 {
grids.ForEach(func(k, v []byte) error { if err := grids.ForEach(func(k, v []byte) error {
gd := app.GridData{} gd := app.GridData{}
if err := json.Unmarshal(v, &gd); err != nil { if err := json.Unmarshal(v, &gd); err != nil {
return err return err
@@ -413,16 +427,22 @@ func (s *ExportService) processMergeJSON(
File: td.File, File: td.File,
}) })
} }
grids.Put(k, raw) if err := grids.Put(k, raw); err != nil {
return err
}
} }
return nil return nil
}) }); err != nil {
return err
}
} }
for mergeid, merge := range existingMaps { for mergeid, merge := range existingMaps {
if mapid == mergeid { if mapid == mergeid {
continue continue
} }
mapB.Delete([]byte(strconv.Itoa(mergeid))) if err := mapB.Delete([]byte(strconv.Itoa(mergeid))); err != nil {
return err
}
slog.Info("reporting merge", "from", mergeid, "to", mapid) slog.Info("reporting merge", "from", mergeid, "to", mapid)
s.mapSvc.ReportMerge(mergeid, mapid, app.Coord{X: offset.X - merge.X, Y: offset.Y - merge.Y}) s.mapSvc.ReportMerge(mergeid, mapid, app.Coord{X: offset.X - merge.X, Y: offset.Y - merge.Y})
} }

View File

@@ -47,12 +47,14 @@ func TestExport_WithGrid(t *testing.T) {
gdRaw, _ := json.Marshal(gd) gdRaw, _ := json.Marshal(gd)
mi := app.MapInfo{ID: 1, Name: "test", Hidden: false} mi := app.MapInfo{ID: 1, Name: "test", Hidden: false}
miRaw, _ := json.Marshal(mi) miRaw, _ := json.Marshal(mi)
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
if err := st.PutGrid(tx, "g1", gdRaw); err != nil { if err := st.PutGrid(tx, "g1", gdRaw); err != nil {
return err return err
} }
return st.PutMap(tx, 1, miRaw) return st.PutMap(tx, 1, miRaw)
}) }); err != nil {
t.Fatal(err)
}
var buf bytes.Buffer var buf bytes.Buffer
err := export.Export(ctx, &buf) err := export.Export(ctx, &buf)
if err != nil { if err != nil {

View File

@@ -218,7 +218,7 @@ func (s *MapService) getSubTiles(ctx context.Context, mapid int, c app.Coord, z
// SaveTile persists a tile and broadcasts the update. // SaveTile persists a tile and broadcasts the update.
func (s *MapService) SaveTile(ctx context.Context, mapid int, c app.Coord, z int, f string, t int64) { func (s *MapService) SaveTile(ctx context.Context, mapid int, c app.Coord, z int, f string, t int64) {
s.st.Update(ctx, func(tx *bbolt.Tx) error { _ = s.st.Update(ctx, func(tx *bbolt.Tx) error {
td := &app.TileData{ td := &app.TileData{
MapID: mapid, MapID: mapid,
Coord: c, Coord: c,
@@ -293,7 +293,7 @@ func (s *MapService) RebuildZooms(ctx context.Context) error {
if b == nil { if b == nil {
return nil return nil
} }
b.ForEach(func(k, v []byte) error { if err := b.ForEach(func(k, v []byte) error {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
@@ -306,8 +306,12 @@ func (s *MapService) RebuildZooms(ctx context.Context) error {
needProcess[zoomproc{grid.Coord.Parent(), grid.Map}] = struct{}{} needProcess[zoomproc{grid.Coord.Parent(), grid.Map}] = struct{}{}
saveGrid[zoomproc{grid.Coord, grid.Map}] = grid.ID saveGrid[zoomproc{grid.Coord, grid.Map}] = grid.ID
return nil return nil
}) }); err != nil {
tx.DeleteBucket(store.BucketTiles) return err
}
if err := tx.DeleteBucket(store.BucketTiles); err != nil {
return err
}
return nil return nil
}); err != nil { }); err != nil {
slog.Error("RebuildZooms: failed to update store", "error", err) slog.Error("RebuildZooms: failed to update store", "error", err)
@@ -364,7 +368,7 @@ func (s *MapService) WatchMerges() chan *app.Merge {
// GetAllTileCache returns all tiles for the initial SSE cache dump. // GetAllTileCache returns all tiles for the initial SSE cache dump.
func (s *MapService) GetAllTileCache(ctx context.Context) []TileCache { func (s *MapService) GetAllTileCache(ctx context.Context) []TileCache {
var cache []TileCache var cache []TileCache
s.st.View(ctx, func(tx *bbolt.Tx) error { _ = s.st.View(ctx, func(tx *bbolt.Tx) error {
return s.st.ForEachTile(tx, func(mapK, zoomK, coordK, v []byte) error { return s.st.ForEachTile(tx, func(mapK, zoomK, coordK, v []byte) error {
select { select {
case <-ctx.Done(): case <-ctx.Done():

View File

@@ -57,9 +57,11 @@ func TestGetConfig(t *testing.T) {
svc, st := newTestMapService(t) svc, st := newTestMapService(t)
ctx := context.Background() ctx := context.Background()
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
return st.PutConfig(tx, "title", []byte("Test Map")) return st.PutConfig(tx, "title", []byte("Test Map"))
}) }); err != nil {
t.Fatal(err)
}
config, err := svc.GetConfig(ctx, app.Auths{app.AUTH_MAP}) config, err := svc.GetConfig(ctx, app.Auths{app.AUTH_MAP})
if err != nil { if err != nil {
@@ -93,9 +95,11 @@ func TestGetConfig_Empty(t *testing.T) {
func TestGetPage(t *testing.T) { func TestGetPage(t *testing.T) {
svc, st := newTestMapService(t) svc, st := newTestMapService(t)
ctx := context.Background() ctx := context.Background()
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
return st.PutConfig(tx, "title", []byte("Map Page")) return st.PutConfig(tx, "title", []byte("Map Page"))
}) }); err != nil {
t.Fatal(err)
}
page, err := svc.GetPage(ctx) page, err := svc.GetPage(ctx)
if err != nil { if err != nil {
@@ -112,9 +116,11 @@ func TestGetGrid(t *testing.T) {
gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 5, Y: 10}} gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 5, Y: 10}}
raw, _ := json.Marshal(gd) raw, _ := json.Marshal(gd)
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
return st.PutGrid(tx, "g1", raw) return st.PutGrid(tx, "g1", raw)
}) }); err != nil {
t.Fatal(err)
}
got, err := svc.GetGrid(ctx, "g1") got, err := svc.GetGrid(ctx, "g1")
if err != nil { if err != nil {
@@ -158,11 +164,17 @@ func TestGetMaps_HiddenFilter(t *testing.T) {
mi2 := app.MapInfo{ID: 2, Name: "hidden", Hidden: true} mi2 := app.MapInfo{ID: 2, Name: "hidden", Hidden: true}
raw1, _ := json.Marshal(mi1) raw1, _ := json.Marshal(mi1)
raw2, _ := json.Marshal(mi2) raw2, _ := json.Marshal(mi2)
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
st.PutMap(tx, 1, raw1) if err := st.PutMap(tx, 1, raw1); err != nil {
st.PutMap(tx, 2, raw2) return err
}
if err := st.PutMap(tx, 2, raw2); err != nil {
return err
}
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
maps, err := svc.GetMaps(ctx, false) maps, err := svc.GetMaps(ctx, false)
if err != nil { if err != nil {
@@ -201,14 +213,18 @@ func TestGetMarkers_WithData(t *testing.T) {
m := app.Marker{Name: "Tower", ID: 1, GridID: "g1", Position: app.Position{X: 10, Y: 20}, Image: "gfx/terobjs/mm/tower"} m := app.Marker{Name: "Tower", ID: 1, GridID: "g1", Position: app.Position{X: 10, Y: 20}, Image: "gfx/terobjs/mm/tower"}
mRaw, _ := json.Marshal(m) mRaw, _ := json.Marshal(m)
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
st.PutGrid(tx, "g1", gdRaw) if err := st.PutGrid(tx, "g1", gdRaw); err != nil {
return err
}
grid, _, err := st.CreateMarkersBuckets(tx) grid, _, err := st.CreateMarkersBuckets(tx)
if err != nil { if err != nil {
return err return err
} }
return grid.Put([]byte("g1_10_20"), mRaw) return grid.Put([]byte("g1_10_20"), mRaw)
}) }); err != nil {
t.Fatal(err)
}
markers, err := svc.GetMarkers(ctx) markers, err := svc.GetMarkers(ctx)
if err != nil { if err != nil {
@@ -233,9 +249,11 @@ func TestGetTile(t *testing.T) {
td := app.TileData{MapID: 1, Coord: app.Coord{X: 0, Y: 0}, Zoom: 0, File: "grids/g1.png", Cache: 12345} td := app.TileData{MapID: 1, Coord: app.Coord{X: 0, Y: 0}, Zoom: 0, File: "grids/g1.png", Cache: 12345}
raw, _ := json.Marshal(td) raw, _ := json.Marshal(td)
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
return st.PutTile(tx, 1, 0, "0_0", raw) return st.PutTile(tx, 1, 0, "0_0", raw)
}) }); err != nil {
t.Fatal(err)
}
got := svc.GetTile(ctx, 1, app.Coord{X: 0, Y: 0}, 0) got := svc.GetTile(ctx, 1, app.Coord{X: 0, Y: 0}, 0)
if got == nil { if got == nil {
@@ -287,9 +305,11 @@ func TestGetAllTileCache_WithData(t *testing.T) {
td := app.TileData{MapID: 1, Coord: app.Coord{X: 1, Y: 2}, Zoom: 0, Cache: 999} td := app.TileData{MapID: 1, Coord: app.Coord{X: 1, Y: 2}, Zoom: 0, Cache: 999}
raw, _ := json.Marshal(td) raw, _ := json.Marshal(td)
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
return st.PutTile(tx, 1, 0, "1_2", raw) return st.PutTile(tx, 1, 0, "1_2", raw)
}) }); err != nil {
t.Fatal(err)
}
cache := svc.GetAllTileCache(ctx) cache := svc.GetAllTileCache(ctx)
if len(cache) != 1 { if len(cache) != 1 {

View File

@@ -25,12 +25,14 @@ func TestUserCRUD(t *testing.T) {
ctx := context.Background() ctx := context.Background()
// Verify user doesn't exist on empty DB. // Verify user doesn't exist on empty DB.
st.View(ctx, func(tx *bbolt.Tx) error { if err := st.View(ctx, func(tx *bbolt.Tx) error {
if got := st.GetUser(tx, "alice"); got != nil { if got := st.GetUser(tx, "alice"); got != nil {
t.Fatal("expected nil user before creation") t.Fatal("expected nil user before creation")
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
// Create user. // Create user.
if err := st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
@@ -40,7 +42,7 @@ func TestUserCRUD(t *testing.T) {
} }
// Verify user exists and count is correct (separate transaction for accurate Stats). // Verify user exists and count is correct (separate transaction for accurate Stats).
st.View(ctx, func(tx *bbolt.Tx) error { if err := st.View(ctx, func(tx *bbolt.Tx) error {
got := st.GetUser(tx, "alice") got := st.GetUser(tx, "alice")
if got == nil || string(got) != `{"pass":"hash"}` { if got == nil || string(got) != `{"pass":"hash"}` {
t.Fatalf("expected user data, got %s", got) t.Fatalf("expected user data, got %s", got)
@@ -49,7 +51,9 @@ func TestUserCRUD(t *testing.T) {
t.Fatalf("expected 1 user, got %d", c) t.Fatalf("expected 1 user, got %d", c)
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
// Delete user. // Delete user.
if err := st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
@@ -58,31 +62,41 @@ func TestUserCRUD(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
st.View(ctx, func(tx *bbolt.Tx) error { if err := st.View(ctx, func(tx *bbolt.Tx) error {
if got := st.GetUser(tx, "alice"); got != nil { if got := st.GetUser(tx, "alice"); got != nil {
t.Fatal("expected nil user after deletion") t.Fatal("expected nil user after deletion")
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
func TestForEachUser(t *testing.T) { func TestForEachUser(t *testing.T) {
st := newTestStore(t) st := newTestStore(t)
ctx := context.Background() ctx := context.Background()
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
st.PutUser(tx, "alice", []byte("1")) if err := st.PutUser(tx, "alice", []byte("1")); err != nil {
st.PutUser(tx, "bob", []byte("2")) return err
}
if err := st.PutUser(tx, "bob", []byte("2")); err != nil {
return err
}
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
var names []string var names []string
st.View(ctx, func(tx *bbolt.Tx) error { if err := st.View(ctx, func(tx *bbolt.Tx) error {
return st.ForEachUser(tx, func(k, _ []byte) error { return st.ForEachUser(tx, func(k, _ []byte) error {
names = append(names, string(k)) names = append(names, string(k))
return nil return nil
}) })
}) }); err != nil {
t.Fatal(err)
}
if len(names) != 2 { if len(names) != 2 {
t.Fatalf("expected 2 users, got %d", len(names)) t.Fatalf("expected 2 users, got %d", len(names))
} }
@@ -91,12 +105,14 @@ func TestForEachUser(t *testing.T) {
func TestUserCountEmptyBucket(t *testing.T) { func TestUserCountEmptyBucket(t *testing.T) {
st := newTestStore(t) st := newTestStore(t)
ctx := context.Background() ctx := context.Background()
st.View(ctx, func(tx *bbolt.Tx) error { if err := st.View(ctx, func(tx *bbolt.Tx) error {
if c := st.UserCount(tx); c != 0 { if c := st.UserCount(tx); c != 0 {
t.Fatalf("expected 0 users on empty db, got %d", c) t.Fatalf("expected 0 users on empty db, got %d", c)
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
func TestSessionCRUD(t *testing.T) { func TestSessionCRUD(t *testing.T) {
@@ -211,19 +227,27 @@ func TestForEachMap(t *testing.T) {
st := newTestStore(t) st := newTestStore(t)
ctx := context.Background() ctx := context.Background()
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
st.PutMap(tx, 1, []byte("a")) if err := st.PutMap(tx, 1, []byte("a")); err != nil {
st.PutMap(tx, 2, []byte("b")) return err
}
if err := st.PutMap(tx, 2, []byte("b")); err != nil {
return err
}
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
var count int var count int
st.View(ctx, func(tx *bbolt.Tx) error { if err := st.View(ctx, func(tx *bbolt.Tx) error {
return st.ForEachMap(tx, func(_, _ []byte) error { return st.ForEachMap(tx, func(_, _ []byte) error {
count++ count++
return nil return nil
}) })
}) }); err != nil {
t.Fatal(err)
}
if count != 2 { if count != 2 {
t.Fatalf("expected 2 maps, got %d", count) t.Fatalf("expected 2 maps, got %d", count)
} }
@@ -290,20 +314,30 @@ func TestForEachTile(t *testing.T) {
st := newTestStore(t) st := newTestStore(t)
ctx := context.Background() ctx := context.Background()
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
st.PutTile(tx, 1, 0, "0_0", []byte("a")) if err := st.PutTile(tx, 1, 0, "0_0", []byte("a")); err != nil {
st.PutTile(tx, 1, 1, "0_0", []byte("b")) return err
st.PutTile(tx, 2, 0, "1_1", []byte("c")) }
if err := st.PutTile(tx, 1, 1, "0_0", []byte("b")); err != nil {
return err
}
if err := st.PutTile(tx, 2, 0, "1_1", []byte("c")); err != nil {
return err
}
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
var count int var count int
st.View(ctx, func(tx *bbolt.Tx) error { if err := st.View(ctx, func(tx *bbolt.Tx) error {
return st.ForEachTile(tx, func(_, _, _, _ []byte) error { return st.ForEachTile(tx, func(_, _, _, _ []byte) error {
count++ count++
return nil return nil
}) })
}) }); err != nil {
t.Fatal(err)
}
if count != 3 { if count != 3 {
t.Fatalf("expected 3 tiles, got %d", count) t.Fatalf("expected 3 tiles, got %d", count)
} }
@@ -313,7 +347,7 @@ func TestTilesMapBucket(t *testing.T) {
st := newTestStore(t) st := newTestStore(t)
ctx := context.Background() ctx := context.Background()
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
if b := st.GetTilesMapBucket(tx, 1); b != nil { if b := st.GetTilesMapBucket(tx, 1); b != nil {
t.Fatal("expected nil bucket before creation") t.Fatal("expected nil bucket before creation")
} }
@@ -328,31 +362,39 @@ func TestTilesMapBucket(t *testing.T) {
t.Fatal("expected non-nil after create") t.Fatal("expected non-nil after create")
} }
return st.DeleteTilesMapBucket(tx, 1) return st.DeleteTilesMapBucket(tx, 1)
}) }); err != nil {
t.Fatal(err)
}
} }
func TestDeleteTilesBucket(t *testing.T) { func TestDeleteTilesBucket(t *testing.T) {
st := newTestStore(t) st := newTestStore(t)
ctx := context.Background() ctx := context.Background()
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
st.PutTile(tx, 1, 0, "0_0", []byte("a")) if err := st.PutTile(tx, 1, 0, "0_0", []byte("a")); err != nil {
return err
}
return st.DeleteTilesBucket(tx) return st.DeleteTilesBucket(tx)
}) }); err != nil {
t.Fatal(err)
}
st.View(ctx, func(tx *bbolt.Tx) error { if err := st.View(ctx, func(tx *bbolt.Tx) error {
if got := st.GetTile(tx, 1, 0, "0_0"); got != nil { if got := st.GetTile(tx, 1, 0, "0_0"); got != nil {
t.Fatal("expected nil after bucket deletion") t.Fatal("expected nil after bucket deletion")
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
func TestMarkerBuckets(t *testing.T) { func TestMarkerBuckets(t *testing.T) {
st := newTestStore(t) st := newTestStore(t)
ctx := context.Background() ctx := context.Background()
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
if b := st.GetMarkersGridBucket(tx); b != nil { if b := st.GetMarkersGridBucket(tx); b != nil {
t.Fatal("expected nil grid bucket before creation") t.Fatal("expected nil grid bucket before creation")
} }
@@ -376,7 +418,9 @@ func TestMarkerBuckets(t *testing.T) {
t.Fatal("expected non-zero sequence") t.Fatal("expected non-zero sequence")
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
func TestOAuthStateCRUD(t *testing.T) { func TestOAuthStateCRUD(t *testing.T) {
@@ -408,23 +452,29 @@ func TestBucketExistsAndDelete(t *testing.T) {
st := newTestStore(t) st := newTestStore(t)
ctx := context.Background() ctx := context.Background()
st.Update(ctx, func(tx *bbolt.Tx) error { if err := st.Update(ctx, func(tx *bbolt.Tx) error {
if st.BucketExists(tx, store.BucketUsers) { if st.BucketExists(tx, store.BucketUsers) {
t.Fatal("expected bucket to not exist") t.Fatal("expected bucket to not exist")
} }
st.PutUser(tx, "alice", []byte("x")) if err := st.PutUser(tx, "alice", []byte("x")); err != nil {
return err
}
if !st.BucketExists(tx, store.BucketUsers) { if !st.BucketExists(tx, store.BucketUsers) {
t.Fatal("expected bucket to exist") t.Fatal("expected bucket to exist")
} }
return st.DeleteBucket(tx, store.BucketUsers) return st.DeleteBucket(tx, store.BucketUsers)
}) }); err != nil {
t.Fatal(err)
}
st.View(ctx, func(tx *bbolt.Tx) error { if err := st.View(ctx, func(tx *bbolt.Tx) error {
if st.BucketExists(tx, store.BucketUsers) { if st.BucketExists(tx, store.BucketUsers) {
t.Fatal("expected bucket to be deleted") t.Fatal("expected bucket to be deleted")
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
func TestDeleteBucketNonExistent(t *testing.T) { func TestDeleteBucketNonExistent(t *testing.T) {