github.com/PDOK/gokoala@v0.50.6/internal/ogc/features/datasources/geopackage/geopackage_test.go (about) 1 package geopackage 2 3 import ( 4 "context" 5 "path" 6 "runtime" 7 "testing" 8 "time" 9 10 "github.com/PDOK/gokoala/config" 11 12 "github.com/PDOK/gokoala/internal/ogc/features/datasources" 13 "github.com/PDOK/gokoala/internal/ogc/features/domain" 14 "github.com/go-spatial/geom/encoding/geojson" 15 "github.com/stretchr/testify/assert" 16 ) 17 18 var pwd string 19 20 func init() { 21 _, filename, _, _ := runtime.Caller(0) 22 pwd = path.Dir(filename) 23 } 24 25 func newAddressesGeoPackage() geoPackageBackend { 26 loadDriver() 27 return newLocalGeoPackage(&config.GeoPackageLocal{ 28 GeoPackageCommon: config.GeoPackageCommon{ 29 Fid: "feature_id", 30 QueryTimeout: config.Duration{Duration: 15 * time.Second}, 31 MaxBBoxSizeToUseWithRTree: 30000, 32 InMemoryCacheSize: -2000, 33 }, 34 File: pwd + "/testdata/bag.gpkg", 35 }) 36 } 37 38 func newTemporalAddressesGeoPackage() geoPackageBackend { 39 loadDriver() 40 return newLocalGeoPackage(&config.GeoPackageLocal{ 41 GeoPackageCommon: config.GeoPackageCommon{ 42 Fid: "feature_id", 43 QueryTimeout: config.Duration{Duration: 15 * time.Second}, 44 MaxBBoxSizeToUseWithRTree: 30000, 45 InMemoryCacheSize: -2000, 46 }, 47 File: pwd + "/testdata/bag-temporal.gpkg", 48 }) 49 } 50 51 func TestNewGeoPackage(t *testing.T) { 52 type args struct { 53 config config.GeoPackage 54 collection config.GeoSpatialCollections 55 } 56 tests := []struct { 57 name string 58 args args 59 wantNrOfFeatureTablesInGpkg int 60 }{ 61 { 62 name: "open local geopackage", 63 args: args{ 64 config: config.GeoPackage{ 65 Local: &config.GeoPackageLocal{ 66 GeoPackageCommon: config.GeoPackageCommon{ 67 Fid: "feature_id", 68 InMemoryCacheSize: -2000, 69 }, 70 File: pwd + "/testdata/bag.gpkg", 71 }, 72 }, 73 collection: []config.GeoSpatialCollection{ 74 { 75 ID: "ligplaatsen", 76 Features: &config.CollectionEntryFeatures{}, 77 }, 78 }, 79 }, 80 wantNrOfFeatureTablesInGpkg: 1, // 3 in geopackage, but we define only 1 collection 81 }, 82 } 83 for _, tt := range tests { 84 t.Run(tt.name, func(t *testing.T) { 85 assert.Equalf(t, tt.wantNrOfFeatureTablesInGpkg, len(NewGeoPackage(tt.args.collection, tt.args.config).featureTableByCollectionID), "NewGeoPackage(%v)", tt.args.config) 86 }) 87 } 88 } 89 90 func TestGeoPackage_GetFeatures(t *testing.T) { 91 type fields struct { 92 backend geoPackageBackend 93 fidColumn string 94 featureTableByID map[string]*featureTable 95 queryTimeout time.Duration 96 } 97 type args struct { 98 ctx context.Context 99 collection string 100 queryParams datasources.FeaturesCriteria 101 } 102 refDate, _ := time.Parse(time.RFC3339, "2023-12-31T00:00:00Z") 103 tests := []struct { 104 name string 105 fields fields 106 args args 107 wantFC *domain.FeatureCollection 108 wantCursor domain.Cursors 109 wantErr bool 110 }{ 111 { 112 name: "get first page of features", 113 fields: fields{ 114 backend: newAddressesGeoPackage(), 115 fidColumn: "feature_id", 116 featureTableByID: map[string]*featureTable{"ligplaatsen": {TableName: "ligplaatsen", GeometryColumnName: "geom"}}, 117 queryTimeout: 60 * time.Second, 118 }, 119 args: args{ 120 ctx: context.Background(), 121 collection: "ligplaatsen", 122 queryParams: datasources.FeaturesCriteria{ 123 Cursor: domain.DecodedCursor{FID: 0, FiltersChecksum: []byte{}}, 124 Limit: 2, 125 }, 126 }, 127 wantFC: &domain.FeatureCollection{ 128 NumberReturned: 2, 129 Features: []*domain.Feature{ 130 { 131 Feature: geojson.Feature{ 132 Properties: map[string]any{ 133 "straatnaam": "Van Diemenkade", 134 "nummer_id": "0363200000454013", 135 }, 136 }, 137 }, 138 { 139 Feature: geojson.Feature{ 140 Properties: map[string]any{ 141 "straatnaam": "Realengracht", 142 "nummer_id": "0363200000398886", 143 }, 144 }, 145 }, 146 }, 147 }, 148 wantCursor: domain.Cursors{ 149 Prev: "|", 150 Next: "Dv4|", // 3838 151 }, 152 wantErr: false, 153 }, 154 { 155 name: "get second page of features", 156 fields: fields{ 157 backend: newAddressesGeoPackage(), 158 fidColumn: "feature_id", 159 featureTableByID: map[string]*featureTable{"ligplaatsen": {TableName: "ligplaatsen", GeometryColumnName: "geom"}}, 160 queryTimeout: 5 * time.Second, 161 }, 162 args: args{ 163 ctx: context.Background(), 164 collection: "ligplaatsen", 165 queryParams: datasources.FeaturesCriteria{ 166 Cursor: domain.DecodedCursor{ 167 FID: 3838, // see next cursor from test above 168 FiltersChecksum: []byte{}, 169 }, 170 Limit: 3, 171 }, 172 }, 173 wantFC: &domain.FeatureCollection{ 174 NumberReturned: 3, 175 Features: []*domain.Feature{ 176 { 177 Feature: geojson.Feature{ 178 Properties: map[string]any{ 179 "straatnaam": "Realengracht", 180 "nummer_id": "0363200000398887", 181 }, 182 }, 183 }, 184 { 185 Feature: geojson.Feature{ 186 Properties: map[string]any{ 187 "straatnaam": "Realengracht", 188 "nummer_id": "0363200000398888", 189 }, 190 }, 191 }, 192 { 193 Feature: geojson.Feature{ 194 Properties: map[string]any{ 195 "straatnaam": "Realengracht", 196 "nummer_id": "0363200000398889", 197 }, 198 }, 199 }, 200 }, 201 }, 202 wantCursor: domain.Cursors{ 203 Prev: "|", 204 Next: "DwE|", 205 }, 206 wantErr: false, 207 }, 208 { 209 name: "get first page of features with reference date", 210 fields: fields{ 211 backend: newTemporalAddressesGeoPackage(), 212 fidColumn: "feature_id", 213 featureTableByID: map[string]*featureTable{"ligplaatsen": {TableName: "ligplaatsen", GeometryColumnName: "geom"}}, 214 queryTimeout: 60 * time.Second, 215 }, 216 args: args{ 217 ctx: context.Background(), 218 collection: "ligplaatsen", 219 queryParams: datasources.FeaturesCriteria{ 220 Cursor: domain.DecodedCursor{FID: 0, FiltersChecksum: []byte{}}, 221 Limit: 2, 222 TemporalCriteria: datasources.TemporalCriteria{ 223 ReferenceDate: refDate, 224 StartDateProperty: "datum_strt", 225 EndDateProperty: "datum_eind", 226 }, 227 }, 228 }, 229 wantFC: &domain.FeatureCollection{ 230 NumberReturned: 2, 231 Features: []*domain.Feature{ 232 { 233 Feature: geojson.Feature{ 234 Properties: map[string]any{ 235 "straatnaam": "Van Diemenkade", 236 "nummer_id": "0363200000454013", 237 }, 238 }, 239 }, 240 { 241 Feature: geojson.Feature{ 242 Properties: map[string]any{ 243 "straatnaam": "Realengracht", 244 "nummer_id": "0363200000398886", 245 }, 246 }, 247 }, 248 }, 249 }, 250 wantCursor: domain.Cursors{ 251 Prev: "|", 252 Next: "Dv4|", // 3838 253 }, 254 wantErr: false, 255 }, 256 { 257 name: "fail on non existing collection", 258 fields: fields{ 259 backend: newAddressesGeoPackage(), 260 fidColumn: "feature_id", 261 featureTableByID: map[string]*featureTable{"ligplaatsen": {TableName: "ligplaatsen", GeometryColumnName: "geom"}}, 262 queryTimeout: 5 * time.Second, 263 }, 264 args: args{ 265 ctx: context.Background(), 266 collection: "vakantiehuizen", // not in gpkg 267 queryParams: datasources.FeaturesCriteria{ 268 Cursor: domain.DecodedCursor{FID: 0, FiltersChecksum: []byte{}}, 269 Limit: 10, 270 }, 271 }, 272 wantFC: nil, 273 wantCursor: domain.Cursors{}, 274 wantErr: true, // should fail 275 }, 276 } 277 for _, tt := range tests { 278 t.Run(tt.name, func(t *testing.T) { 279 g := &GeoPackage{ 280 backend: tt.fields.backend, 281 fidColumn: tt.fields.fidColumn, 282 featureTableByCollectionID: tt.fields.featureTableByID, 283 queryTimeout: tt.fields.queryTimeout, 284 } 285 g.preparedStmtCache = NewCache() 286 287 fc, cursor, err := g.GetFeatures(tt.args.ctx, tt.args.collection, tt.args.queryParams) 288 if err != nil { 289 if !tt.wantErr { 290 t.Errorf("GetFeatures, error %v, wantErr %v", err, tt.wantErr) 291 } 292 return 293 } 294 assert.Equal(t, tt.wantFC.NumberReturned, fc.NumberReturned) 295 assert.Equal(t, len(tt.wantFC.Features), fc.NumberReturned) 296 for i, wantedFeature := range tt.wantFC.Features { 297 assert.Equal(t, wantedFeature.Properties["straatnaam"], fc.Features[i].Properties["straatnaam"]) 298 assert.Equal(t, wantedFeature.Properties["nummer_id"], fc.Features[i].Properties["nummer_id"]) 299 } 300 assert.Equal(t, tt.wantCursor.Prev, cursor.Prev) 301 assert.Equal(t, tt.wantCursor.Next, cursor.Next) 302 }) 303 } 304 } 305 306 func TestGeoPackage_GetFeature(t *testing.T) { 307 type fields struct { 308 backend geoPackageBackend 309 fidColumn string 310 featureTableByID map[string]*featureTable 311 queryTimeout time.Duration 312 } 313 type args struct { 314 ctx context.Context 315 collection string 316 featureID int64 317 } 318 tests := []struct { 319 name string 320 fields fields 321 args args 322 want *domain.Feature 323 wantErr bool 324 }{ 325 { 326 name: "get feature", 327 fields: fields{ 328 backend: newAddressesGeoPackage(), 329 fidColumn: "feature_id", 330 featureTableByID: map[string]*featureTable{"ligplaatsen": {TableName: "ligplaatsen", GeometryColumnName: "geom"}}, 331 queryTimeout: 5 * time.Second, 332 }, 333 args: args{ 334 ctx: context.Background(), 335 collection: "ligplaatsen", 336 featureID: 3837, 337 }, 338 want: &domain.Feature{ 339 ID: 0, 340 Links: nil, 341 Feature: geojson.Feature{ 342 Properties: map[string]any{ 343 "straatnaam": "Realengracht", 344 "nummer_id": "0363200000398886", 345 }, 346 }, 347 }, 348 wantErr: false, 349 }, 350 { 351 name: "get non existing feature", 352 fields: fields{ 353 backend: newAddressesGeoPackage(), 354 fidColumn: "feature_id", 355 featureTableByID: map[string]*featureTable{"ligplaatsen": {TableName: "ligplaatsen", GeometryColumnName: "geom"}}, 356 queryTimeout: 5 * time.Second, 357 }, 358 args: args{ 359 ctx: context.Background(), 360 collection: "ligplaatsen", 361 featureID: 999991111111111111, 362 }, 363 want: nil, 364 wantErr: false, // not an error situation 365 }, 366 { 367 name: "fail on non existing collection", 368 fields: fields{ 369 backend: newAddressesGeoPackage(), 370 fidColumn: "feature_id", 371 featureTableByID: map[string]*featureTable{"ligplaatsen": {TableName: "ligplaatsen", GeometryColumnName: "geom"}}, 372 queryTimeout: 5 * time.Second, 373 }, 374 args: args{ 375 ctx: context.Background(), 376 collection: "vakantieparken", // not in gpkg 377 featureID: 3837, 378 }, 379 want: nil, 380 wantErr: true, 381 }, 382 } 383 for _, tt := range tests { 384 t.Run(tt.name, func(t *testing.T) { 385 g := &GeoPackage{ 386 backend: tt.fields.backend, 387 fidColumn: tt.fields.fidColumn, 388 featureTableByCollectionID: tt.fields.featureTableByID, 389 queryTimeout: tt.fields.queryTimeout, 390 } 391 got, err := g.GetFeature(tt.args.ctx, tt.args.collection, tt.args.featureID) 392 if err != nil { 393 if !tt.wantErr { 394 t.Errorf("GetFeature, error %v, wantErr %v", err, tt.wantErr) 395 } 396 return 397 } 398 if tt.want != nil { 399 assert.Equal(t, tt.want.Properties["straatnaam"], got.Properties["straatnaam"]) 400 assert.Equal(t, tt.want.Properties["nummer_id"], got.Properties["nummer_id"]) 401 } 402 }) 403 } 404 } 405 406 func TestGeoPackage_Warmup(t *testing.T) { 407 t.Run("warmup", func(t *testing.T) { 408 g := &GeoPackage{ 409 backend: newAddressesGeoPackage(), 410 fidColumn: "feature_id", 411 featureTableByCollectionID: map[string]*featureTable{"ligplaatsen": {TableName: "ligplaatsen", GeometryColumnName: "geom"}}, 412 queryTimeout: 5 * time.Second, 413 } 414 collections := 415 []config.GeoSpatialCollection{ 416 { 417 ID: "ligplaatsen", 418 Features: &config.CollectionEntryFeatures{}, 419 }, 420 } 421 err := warmUpFeatureTables(collections, g.featureTableByCollectionID, g.backend.getDB()) 422 assert.NoError(t, err) 423 }) 424 }