clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

feat/atlas-next-horizon
atlas / format / bundle / fixture_test.go
fixture_test.go 4.3K
1package bundle_test23import (4 "bytes"5 "os"6 "path/filepath"7 "testing"89 "github.com/FelineStateMachine/atlas/format/bundle"10 "github.com/FelineStateMachine/atlas/format/semconv"11)1213// A fixture is a real bundle written through the real Writer, so the writer14// and the reader verify each other and nothing opaque is checked into the15// tree. Every field a test does not state is filled with something plausible.1617const (18 // fixtureLenses is the one lens every fixture world is pictured by: a19 // single level, one format, drawing from a pyramid named for the world.20 fixtureLenses = `"lenses":[{"tiles":"overworld","minZoom":0,"maxZoom":0,"formats":["jpg"]}]`21 // fixturePointCollection is the simplest sound collections array: one22 // point collection, which the packed payload's owner column indexes.23 fixturePointCollection = `"collections":[{"id":1,"title":"Marker","kind":"point","visible":true}]`24)2526type fixture struct {27 slug string28 title string29 createdAt string30 stamp string31 revision int32 conventions int33 worlds []fixtureWorld34 icons []string35 // omitTiles leaves the tile pyramid out, so a test can break the promise36 // that a lens's levels hold tiles.37 omitTiles bool38}3940type fixtureWorld struct {41 slug string42 detail string43 text string44 locations []bundle.Location45 // points, paths, and areas are what the manifest promises. Left zero,46 // points follows the locations actually packed.47 points, paths, areas int48 countsStated bool49}5051func (f fixture) build(t *testing.T, dir string) string {52 t.Helper()53 if f.slug == "" {54 f.slug = "fixture"55 }56 if f.title == "" {57 f.title = "Fixture"58 }59 if f.createdAt == "" {60 f.createdAt = "2026-01-01T00:00:00Z"61 }62 if f.stamp == "" {63 f.stamp = bundle.HashBytes([]byte(f.slug + f.createdAt))64 }65 if len(f.worlds) == 0 {66 f.worlds = []fixtureWorld{{67 slug: "overworld",68 locations: []bundle.Location{{ID: 1, Title: "Origin"}},69 }}70 }7172 manifest := bundle.Manifest{73 Format: bundle.Format,74 FormatVersion: bundle.FormatVersion,75 Conventions: f.conventions,76 Volume: bundle.Volume{Slug: f.slug, Title: f.title},77 Version: bundle.Version{Stamp: f.stamp, CreatedAt: f.createdAt, Revision: f.revision},78 TileGrid: bundle.TileGrid{SourceZoom: 13, FirstTile: 4064, TileSize: 256, Size: 8192},79 }80 for _, world := range f.worlds {81 points := world.points82 if !world.countsStated {83 points = len(world.locations)84 }85 manifest.Worlds = append(manifest.Worlds, bundle.WorldEntry{86 Slug: world.slug,87 Title: world.slug,88 Points: points,89 Paths: world.paths,90 Areas: world.areas,91 UpdatedAt: f.createdAt,92 })93 }9495 path := filepath.Join(dir, f.slug+bundle.Extension)96 file, err := os.Create(path)97 if err != nil {98 t.Fatal(err)99 }100 defer file.Close()101 writer, err := bundle.NewWriter(file, manifest)102 if err != nil {103 t.Fatal(err)104 }105106 for _, world := range f.worlds {107 detail := world.detail108 if detail == "" {109 detail = "{" + fixtureLenses + "," + fixturePointCollection + "}"110 }111 text := world.text112 if text == "" {113 text = "{}"114 }115 must(t, writer.AddDeflated(bundle.WorldEntryName(world.slug, bundle.WorldSuffix), []byte(detail)))116 must(t, writer.AddStored(bundle.WorldEntryName(world.slug, bundle.PackedSuffix),117 bytes.NewReader(bundle.PackLocations(world.locations))))118 must(t, writer.AddDeflated(bundle.WorldEntryName(world.slug, bundle.TextSuffix), []byte(text)))119 }120 // One shared pyramid: every fixture lens draws from "overworld", the way121 // several pieces of a split sheet draw from one raster.122 if !f.omitTiles {123 must(t, writer.AddStored(bundle.TilesPrefix+"overworld/0/0/0.jpg",124 bytes.NewReader([]byte("raster"))))125 }126 for _, icon := range f.icons {127 must(t, writer.AddDeflated(bundle.IconsPrefix+icon, []byte("<svg/>")))128 }129 must(t, writer.Close())130 must(t, file.Close())131 return path132}133134func (f fixture) open(t *testing.T, dir string) *bundle.Reader {135 t.Helper()136 reader, err := bundle.Open(f.build(t, dir))137 if err != nil {138 t.Fatal(err)139 }140 t.Cleanup(func() { reader.Close() })141 return reader142}143144// conventional turns a fixture into one that declares the current145// conventions, which is what makes validation strict about its attributes.146func (f fixture) conventional() fixture {147 f.conventions = semconv.Version148 return f149}150151func must(t *testing.T, err error) {152 t.Helper()153 if err != nil {154 t.Fatal(err)155 }156}