package services_test import ( "context" "encoding/json" "testing" "github.com/andyleap/hnh-map/internal/app" "github.com/andyleap/hnh-map/internal/app/services" "github.com/andyleap/hnh-map/internal/app/store" "go.etcd.io/bbolt" ) func newTestMapService(t *testing.T) (*services.MapService, *store.Store) { t.Helper() db := newTestDB(t) st := store.New(db) chars := []app.Character{ {Name: "Hero", ID: 1, Map: 1, Position: app.Position{X: 100, Y: 200}}, } svc := services.NewMapService(services.MapServiceDeps{ Store: st, GridStorage: t.TempDir(), GridUpdates: &app.Topic[app.TileData]{}, MergeUpdates: &app.Topic[app.Merge]{}, GetChars: func() []app.Character { return chars }, }) return svc, st } func TestGetCharacters(t *testing.T) { svc, _ := newTestMapService(t) chars := svc.GetCharacters() if len(chars) != 1 { t.Fatalf("expected 1 character, got %d", len(chars)) } if chars[0].Name != "Hero" { t.Fatalf("expected Hero, got %s", chars[0].Name) } } func TestGetCharacters_Nil(t *testing.T) { db := newTestDB(t) st := store.New(db) svc := services.NewMapService(services.MapServiceDeps{ Store: st, GridStorage: t.TempDir(), GridUpdates: &app.Topic[app.TileData]{}, }) chars := svc.GetCharacters() if chars != nil { t.Fatalf("expected nil characters, got %v", chars) } } func TestGetConfig(t *testing.T) { svc, st := newTestMapService(t) ctx := context.Background() st.Update(ctx, func(tx *bbolt.Tx) error { return st.PutConfig(tx, "title", []byte("Test Map")) }) config, err := svc.GetConfig(ctx, app.Auths{app.AUTH_MAP}) if err != nil { t.Fatal(err) } if config.Title != "Test Map" { t.Fatalf("expected Test Map, got %s", config.Title) } hasMap := false for _, a := range config.Auths { if a == app.AUTH_MAP { hasMap = true } } if !hasMap { t.Fatal("expected map auth in config") } } func TestGetConfig_Empty(t *testing.T) { svc, _ := newTestMapService(t) config, err := svc.GetConfig(context.Background(), app.Auths{app.AUTH_ADMIN}) if err != nil { t.Fatal(err) } if config.Title != "" { t.Fatalf("expected empty title, got %s", config.Title) } } func TestGetPage(t *testing.T) { svc, st := newTestMapService(t) ctx := context.Background() st.Update(ctx, func(tx *bbolt.Tx) error { return st.PutConfig(tx, "title", []byte("Map Page")) }) page, err := svc.GetPage(ctx) if err != nil { t.Fatal(err) } if page.Title != "Map Page" { t.Fatalf("expected Map Page, got %s", page.Title) } } func TestGetGrid(t *testing.T) { svc, st := newTestMapService(t) ctx := context.Background() gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 5, Y: 10}} raw, _ := json.Marshal(gd) st.Update(ctx, func(tx *bbolt.Tx) error { return st.PutGrid(tx, "g1", raw) }) got, err := svc.GetGrid(ctx, "g1") if err != nil { t.Fatal(err) } if got == nil { t.Fatal("expected grid data") } if got.Map != 1 || got.Coord.X != 5 || got.Coord.Y != 10 { t.Fatalf("unexpected grid data: %+v", got) } } func TestGetGrid_NotFound(t *testing.T) { svc, _ := newTestMapService(t) got, err := svc.GetGrid(context.Background(), "nonexistent") if err != nil { t.Fatal(err) } if got != nil { t.Fatal("expected nil for missing grid") } } func TestGetMaps_Empty(t *testing.T) { svc, _ := newTestMapService(t) maps, err := svc.GetMaps(context.Background(), false) if err != nil { t.Fatal(err) } if len(maps) != 0 { t.Fatalf("expected 0 maps, got %d", len(maps)) } } func TestGetMaps_HiddenFilter(t *testing.T) { svc, st := newTestMapService(t) ctx := context.Background() mi1 := app.MapInfo{ID: 1, Name: "visible", Hidden: false} mi2 := app.MapInfo{ID: 2, Name: "hidden", Hidden: true} raw1, _ := json.Marshal(mi1) raw2, _ := json.Marshal(mi2) st.Update(ctx, func(tx *bbolt.Tx) error { st.PutMap(tx, 1, raw1) st.PutMap(tx, 2, raw2) return nil }) maps, err := svc.GetMaps(ctx, false) if err != nil { t.Fatal(err) } if len(maps) != 1 { t.Fatalf("expected 1 visible map, got %d", len(maps)) } maps, err = svc.GetMaps(ctx, true) if err != nil { t.Fatal(err) } if len(maps) != 2 { t.Fatalf("expected 2 maps with showHidden, got %d", len(maps)) } } func TestGetMarkers_Empty(t *testing.T) { svc, _ := newTestMapService(t) markers, err := svc.GetMarkers(context.Background()) if err != nil { t.Fatal(err) } if len(markers) != 0 { t.Fatalf("expected 0 markers, got %d", len(markers)) } } func TestGetMarkers_WithData(t *testing.T) { svc, st := newTestMapService(t) ctx := context.Background() gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 2, Y: 3}} gdRaw, _ := json.Marshal(gd) 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) st.Update(ctx, func(tx *bbolt.Tx) error { st.PutGrid(tx, "g1", gdRaw) grid, _, err := st.CreateMarkersBuckets(tx) if err != nil { return err } return grid.Put([]byte("g1_10_20"), mRaw) }) markers, err := svc.GetMarkers(ctx) if err != nil { t.Fatal(err) } if len(markers) != 1 { t.Fatalf("expected 1 marker, got %d", len(markers)) } if markers[0].Name != "Tower" { t.Fatalf("expected Tower, got %s", markers[0].Name) } expectedX := 10 + 2*app.GridSize expectedY := 20 + 3*app.GridSize if markers[0].Position.X != expectedX || markers[0].Position.Y != expectedY { t.Fatalf("expected position (%d,%d), got (%d,%d)", expectedX, expectedY, markers[0].Position.X, markers[0].Position.Y) } } func TestGetTile(t *testing.T) { svc, st := newTestMapService(t) ctx := context.Background() td := app.TileData{MapID: 1, Coord: app.Coord{X: 0, Y: 0}, Zoom: 0, File: "grids/g1.png", Cache: 12345} raw, _ := json.Marshal(td) st.Update(ctx, func(tx *bbolt.Tx) error { return st.PutTile(tx, 1, 0, "0_0", raw) }) got := svc.GetTile(ctx, 1, app.Coord{X: 0, Y: 0}, 0) if got == nil { t.Fatal("expected tile data") } if got.File != "grids/g1.png" { t.Fatalf("expected grids/g1.png, got %s", got.File) } } func TestGetTile_NotFound(t *testing.T) { svc, _ := newTestMapService(t) got := svc.GetTile(context.Background(), 1, app.Coord{X: 0, Y: 0}, 0) if got != nil { t.Fatal("expected nil for missing tile") } } func TestGridStorage(t *testing.T) { svc, _ := newTestMapService(t) if svc.GridStorage() == "" { t.Fatal("expected non-empty grid storage path") } } func TestWatchTilesAndMerges(t *testing.T) { svc, _ := newTestMapService(t) tc := svc.WatchTiles() if tc == nil { t.Fatal("expected non-nil tile channel") } mc := svc.WatchMerges() if mc == nil { t.Fatal("expected non-nil merge channel") } } func TestGetAllTileCache_Empty(t *testing.T) { svc, _ := newTestMapService(t) cache := svc.GetAllTileCache(context.Background()) if len(cache) != 0 { t.Fatalf("expected 0 cache entries, got %d", len(cache)) } } func TestGetAllTileCache_WithData(t *testing.T) { svc, st := newTestMapService(t) ctx := context.Background() td := app.TileData{MapID: 1, Coord: app.Coord{X: 1, Y: 2}, Zoom: 0, Cache: 999} raw, _ := json.Marshal(td) st.Update(ctx, func(tx *bbolt.Tx) error { return st.PutTile(tx, 1, 0, "1_2", raw) }) cache := svc.GetAllTileCache(ctx) if len(cache) != 1 { t.Fatalf("expected 1 cache entry, got %d", len(cache)) } if cache[0].M != 1 || cache[0].X != 1 || cache[0].Y != 2 { t.Fatalf("unexpected cache entry: %+v", cache[0]) } }