1package vnext23import (4 "archive/zip"5 "bytes"6 "fmt"7 "os"8 "strings"9 "testing"10)1112func TestCompilePartitionsFeaturesAndPagesThemWithoutMaterializingVolume(t *testing.T) {13 t.Parallel()1415 volume := sampleRegionVolume(9_000)16 bundle, err := Compile(volume)17 if err != nil {18 t.Fatalf("compile Sample Region: %v", err)19 }20 partitionCount := 021 for _, named := range bundle.Tables {22 if named.Name == featureTableName {23 t.Fatal("compiled bundle retained the monolithic feature table")24 }25 if strings.HasPrefix(named.Name, featurePartitionPrefix) {26 partitionCount++27 }28 }29 if partitionCount < 3 {30 t.Fatalf("feature partitions = %d, want at least 3", partitionCount)31 }3233 bundle.Release.CreatedAt = "2026-08-08T12:00:00Z"34 var encoded bytes.Buffer35 if err := Write(&encoded, bundle); err != nil {36 t.Fatalf("write Sample Region: %v", err)37 }38 reader, err := Open(bytes.NewReader(encoded.Bytes()), int64(encoded.Len()), StandardSchema())39 if err != nil {40 t.Fatalf("open Sample Region: %v", err)41 }42 page, err := reader.FeaturePage(FeaturePageRequest{43 FeatureSet: "roads",44 Bounds: &SpatialBounds{MinX: 100, MinY: -1, MaxX: 199, MaxY: 1},45 Limit: 10,46 })47 if err != nil {48 t.Fatalf("first feature page: %v", err)49 }50 if len(page.Features) != 10 || page.Next == "" {51 t.Fatalf("first page = %d features, next %q", len(page.Features), page.Next)52 }53 if page.PartitionsRead > 2 || page.BytesRead <= 0 {54 t.Fatalf("first page read %d partitions and %d bytes", page.PartitionsRead, page.BytesRead)55 }56 second, err := reader.FeaturePage(FeaturePageRequest{57 FeatureSet: "roads",58 Bounds: &SpatialBounds{MinX: 100, MinY: -1, MaxX: 199, MaxY: 1},59 After: page.Next,60 Limit: 10,61 })62 if err != nil {63 t.Fatalf("second feature page: %v", err)64 }65 if len(second.Features) != 10 || second.Features[0].ID == page.Features[0].ID {66 t.Fatalf("second page did not advance: %#v", second.Features)67 }6869 restored, err := reader.Volume()70 if err != nil {71 t.Fatalf("compatibility materialization: %v", err)72 }73 if got := len(restored.Worlds[0].FeatureSets[0].Features); got != 9_000 {74 t.Fatalf("restored features = %d, want 9000", got)75 }76 if space := restored.Worlds[0].CoordinateSpace; space.OriginX != 10 || space.OriginY != 20 {77 t.Fatalf("restored tile origin = %d,%d, want 10,20", space.OriginX, space.OriginY)78 }79}8081func TestOutlineDoesNotReadFeaturePartitions(t *testing.T) {82 t.Parallel()8384 bundle, err := Compile(sampleRegionVolume(300))85 if err != nil {86 t.Fatal(err)87 }88 bundle.Release.CreatedAt = "2026-08-08T12:00:00Z"89 var encoded bytes.Buffer90 if err := Write(&encoded, bundle); err != nil {91 t.Fatal(err)92 }93 corrupt := append([]byte(nil), encoded.Bytes()...)94 archive, err := zip.NewReader(bytes.NewReader(corrupt), int64(len(corrupt)))95 if err != nil {96 t.Fatal(err)97 }98 for _, entry := range archive.File {99 if !strings.HasSuffix(entry.Name, ".features.pack") {100 continue101 }102 offset, err := entry.DataOffset()103 if err != nil {104 t.Fatal(err)105 }106 corrupt[offset+int64(entry.UncompressedSize64)-1] ^= 0xff107 break108 }109 reader, err := Open(bytes.NewReader(corrupt), int64(len(corrupt)), StandardSchema())110 if err != nil {111 t.Fatalf("open with unread corrupt partition: %v", err)112 }113 summaries, err := reader.FeatureSetSummaries()114 if err != nil || len(summaries) != 1 || summaries[0].FeatureSet != "roads" || summaries[0].Rows != 300 {115 t.Fatalf("page-zero feature summary = %#v, %v", summaries, err)116 }117 outline, err := reader.Outline()118 if err != nil {119 t.Fatalf("outline read a feature partition: %v", err)120 }121 if got := outline.Worlds[0].FeatureSets[0].Features; len(got) != 0 {122 t.Fatalf("outline materialized %d features", len(got))123 }124 if _, err := reader.FeaturePage(FeaturePageRequest{FeatureSet: "roads", Limit: 1}); err == nil {125 t.Fatal("feature page accepted the corrupt demanded partition")126 }127}128129func TestPartitionAndShardGenerationIsCanonicalAcrossInputOrder(t *testing.T) {130 t.Parallel()131132 left := sampleRegionVolume(520)133 right := sampleRegionVolume(520)134 features := right.Worlds[0].FeatureSets[0].Features135 for low, high := 0, len(features)-1; low < high; low, high = low+1, high-1 {136 features[low], features[high] = features[high], features[low]137 }138 encode := func(volume Volume, reverseTiles bool) []byte {139 bundle, err := Compile(volume)140 if err != nil {141 t.Fatal(err)142 }143 bundle.Release.CreatedAt = "2026-08-08T12:00:00Z"144 tiles := []Blob{145 {Name: "tiles/background/0/0/0.png", Data: []byte("zero")},146 {Name: "tiles/background/1/1/0.jpg", Data: []byte("one")},147 }148 if reverseTiles {149 tiles[0], tiles[1] = tiles[1], tiles[0]150 }151 bundle.Blobs = append(bundle.Blobs, tiles...)152 var output bytes.Buffer153 if err := Write(&output, bundle); err != nil {154 t.Fatal(err)155 }156 return output.Bytes()157 }158 if first, second := encode(left, false), encode(right, true); !bytes.Equal(first, second) {159 t.Fatal("stable feature IDs and tile names did not canonicalize input order")160 }161}162163func TestValidateRejectsMalformedLogicalIndexes(t *testing.T) {164 t.Parallel()165166 featureBundle, err := Compile(sampleRegionVolume(1))167 if err != nil {168 t.Fatal(err)169 }170 for index := range featureBundle.Blobs {171 if featureBundle.Blobs[index].Name == featureIndexName {172 featureBundle.Blobs[index].Data = append(featureBundle.Blobs[index].Data, '\n')173 }174 }175 featureBundle.Release.CreatedAt = "2026-08-08T12:00:00Z"176 var featureBytes bytes.Buffer177 if err := Write(&featureBytes, featureBundle); err != nil {178 t.Fatal(err)179 }180 featureReader, err := Open(bytes.NewReader(featureBytes.Bytes()), int64(featureBytes.Len()), StandardSchema())181 if err != nil {182 t.Fatal(err)183 }184 if err := featureReader.Validate(); err == nil || !strings.Contains(err.Error(), "feature index") {185 t.Fatalf("malformed feature index validation = %v", err)186 }187188 rasterBundle, err := Compile(minimalVolume())189 if err != nil {190 t.Fatal(err)191 }192 rasterBundle.Release.CreatedAt = "2026-08-08T12:00:00Z"193 rasterBundle.Blobs = append(rasterBundle.Blobs,194 Blob{Name: rasterShardPrefix + "missing.rpack", Data: []byte("data")},195 Blob{Name: rasterIndexName, Data: []byte(`{"version":1,"tiles":[]}`)},196 )197 var rasterBytes bytes.Buffer198 if err := Write(&rasterBytes, rasterBundle); err != nil {199 t.Fatal(err)200 }201 rasterReader, err := Open(bytes.NewReader(rasterBytes.Bytes()), int64(rasterBytes.Len()), StandardSchema())202 if err != nil {203 t.Fatal(err)204 }205 if _, err := rasterReader.LogicalBlobNames(); err == nil || !strings.Contains(err.Error(), "not indexed") {206 t.Fatalf("orphan raster shard logical names = %v", err)207 }208}209210func TestRasterTileRangeRejectsCorruptShardSlice(t *testing.T) {211 t.Parallel()212213 bundle, err := Compile(minimalVolume())214 if err != nil {215 t.Fatal(err)216 }217 bundle.Release.CreatedAt = "2026-08-08T12:00:00Z"218 bundle.Blobs = append(bundle.Blobs, Blob{Name: "tiles/background/0/0/0.png", Data: []byte("zero tile")})219 var encoded bytes.Buffer220 if err := Write(&encoded, bundle); err != nil {221 t.Fatal(err)222 }223 corrupt := append([]byte(nil), encoded.Bytes()...)224 archive, err := zip.NewReader(bytes.NewReader(corrupt), int64(len(corrupt)))225 if err != nil {226 t.Fatal(err)227 }228 for _, entry := range archive.File {229 if !strings.HasPrefix(entry.Name, rasterShardPrefix) {230 continue231 }232 offset, err := entry.DataOffset()233 if err != nil {234 t.Fatal(err)235 }236 corrupt[offset] ^= 0xff237 }238 reader, err := Open(bytes.NewReader(corrupt), int64(len(corrupt)), StandardSchema())239 if err != nil {240 t.Fatal(err)241 }242 if _, err := reader.ReadRasterTile("tiles/background/0/0/0.png"); err == nil || !strings.Contains(err.Error(), "hash") {243 t.Fatalf("corrupt tile read = %v", err)244 }245}246247func TestRasterPackingSpoolsBoundedShardsWithoutRetainingTileCopies(t *testing.T) {248 t.Parallel()249250 packed, err := packRasterBlobs([]Blob{251 {Name: "tiles/background/1/0/0.png", Data: bytes.Repeat([]byte{1}, 1<<20)},252 {Name: "tiles/background/1/1/0.png", Data: bytes.Repeat([]byte{2}, 1<<20)},253 })254 if err != nil {255 t.Fatal(err)256 }257 var temporary []string258 for _, blob := range packed {259 if !strings.HasPrefix(blob.Name, rasterShardPrefix) {260 continue261 }262 if len(blob.Data) != 0 || blob.Path == "" || !blob.temporary {263 t.Fatalf("raster shard retained in memory: %#v", blob)264 }265 info, err := os.Stat(blob.Path)266 if err != nil {267 t.Fatal(err)268 }269 if info.Size() > maxRasterShardBytes {270 t.Fatalf("staged shard size = %d", info.Size())271 }272 temporary = append(temporary, blob.Path)273 }274 cleanupTemporaryBlobs(packed)275 for _, path := range temporary {276 if _, err := os.Stat(path); !os.IsNotExist(err) {277 t.Fatalf("temporary shard remains at %s: %v", path, err)278 }279 }280}281282func TestWritePacksRasterTilesAndReaderVerifiesLogicalTileSlices(t *testing.T) {283 t.Parallel()284285 bundle, err := Compile(minimalVolume())286 if err != nil {287 t.Fatal(err)288 }289 bundle.Release.CreatedAt = "2026-08-08T12:00:00Z"290 want := map[string][]byte{291 "tiles/background/0/0/0.png": []byte("zero tile"),292 "tiles/background/1/0/0.png": []byte("northwest"),293 "tiles/background/1/1/0.jpg": []byte("northeast"),294 }295 for name, data := range want {296 bundle.Blobs = append(bundle.Blobs, Blob{Name: name, Data: data})297 }298 var encoded bytes.Buffer299 if err := Write(&encoded, bundle); err != nil {300 t.Fatal(err)301 }302 archive, err := zip.NewReader(bytes.NewReader(encoded.Bytes()), int64(encoded.Len()))303 if err != nil {304 t.Fatal(err)305 }306 for _, entry := range archive.File {307 if strings.HasPrefix(entry.Name, "tiles/") {308 t.Fatalf("tile remained a ZIP entry: %s", entry.Name)309 }310 }311312 reader, err := Open(bytes.NewReader(encoded.Bytes()), int64(encoded.Len()), StandardSchema())313 if err != nil {314 t.Fatal(err)315 }316 for name, expected := range want {317 tile, err := reader.ReadRasterTile(name)318 if err != nil {319 t.Fatalf("read %s: %v", name, err)320 }321 if !bytes.Equal(tile.Data, expected) {322 t.Fatalf("read %s = %q, want %q", name, tile.Data, expected)323 }324 if strings.HasSuffix(name, ".jpg") && (tile.Format != "jpg" || tile.MediaType != "image/jpeg") {325 t.Fatalf("mixed tile metadata = %#v", tile)326 }327 }328}329330func sampleRegionVolume(features int) Volume {331 items := make([]Feature, features)332 for index := range items {333 items[index] = Feature{334 ID: fmt.Sprintf("road-%04d", index),335 Title: fmt.Sprintf("Sample Road %04d", index),336 Geometry: Geometry{Kind: GeometryPoint, Parts: []GeometryPart{{Rings: [][]Position{{337 {float64(index), 0},338 }}}}},339 Relationships: []Relationship{{Predicate: "next", Target: fmt.Sprintf("road-%04d", (index+1)%max(features, 1))}},340 Provenance: []Provenance{{Source: "sample", NativeID: fmt.Sprintf("%04d", index), CapturedAt: "2026-08-08T12:00:00Z"}},341 }342 }343 return Volume{344 ID: "sample-region", Title: "Sample Region",345 Worlds: []World{{346 ID: "sample-world", Title: "Sample Region",347 CoordinateSpace: CoordinateSpace{348 ID: "sample-space", Kind: "projected", Unit: "metre", Definition: "EPSG:3857",349 Extent: [4]float64{0, -1, float64(features), 1}, OriginX: 10, OriginY: 20,350 },351 FeatureSets: []FeatureSet{{ID: "roads", Title: "Roads", SemanticType: "transport.road", Features: items}},352 Presentation: Presentation{353 ID: "default", Title: "Default",354 Styles: []Style{{ID: "road-style"}},355 Layers: []Layer{{ID: "roads-layer", FeatureSet: "roads", Style: "road-style", Visible: true, MaxZoom: 22}},356 },357 }},358 }359}