• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geo-engine / geoengine / 13195982647

07 Feb 2025 08:25AM CUT coverage: 90.031% (-0.04%) from 90.073%
13195982647

Pull #1000

github

web-flow
Merge 2410e0a4e into 22fe2f6bb
Pull Request #1000: add tensor shape to ml model

109 of 194 new or added lines in 10 files covered. (56.19%)

3 existing lines in 3 files now uncovered.

126035 of 139990 relevant lines covered (90.03%)

57499.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.9
/services/src/contexts/postgres.rs
1
use self::migrations::all_migrations;
2
use crate::api::model::services::Volume;
3
use crate::config::{get_config_element, Cache, Oidc, Quota};
4
use crate::contexts::{
5
    initialize_database, migrations, ApplicationContext, CurrentSchemaMigration, MigrationResult,
6
    QueryContextImpl, SessionId,
7
};
8
use crate::contexts::{ExecutionContextImpl, QuotaCheckerImpl};
9
use crate::contexts::{GeoEngineDb, SessionContext};
10
use crate::datasets::upload::Volumes;
11
use crate::datasets::DatasetName;
12
use crate::error::{self, Error, Result};
13
use crate::layers::add_from_directory::{
14
    add_datasets_from_directory, add_layer_collections_from_directory, add_layers_from_directory,
15
    add_providers_from_directory,
16
};
17
use crate::machine_learning::error::MachineLearningError;
18
use crate::machine_learning::name::MlModelName;
19
use crate::quota::{initialize_quota_tracking, QuotaTrackingFactory};
20
use crate::tasks::SimpleTaskManagerContext;
21
use crate::tasks::{TypedTaskManagerBackend, UserTaskManager};
22
use crate::users::OidcManager;
23
use crate::users::{UserAuth, UserSession};
24
use async_trait::async_trait;
25
use bb8_postgres::{
26
    bb8::Pool,
27
    bb8::PooledConnection,
28
    tokio_postgres::{tls::MakeTlsConnect, tls::TlsConnect, Config, Socket},
29
    PostgresConnectionManager,
30
};
31
use geoengine_datatypes::raster::TilingSpecification;
32
use geoengine_datatypes::util::test::TestDefault;
33
use geoengine_operators::cache::shared_cache::SharedCache;
34
use geoengine_operators::engine::ChunkByteSize;
35
use geoengine_operators::meta::quota::QuotaChecker;
36
use geoengine_operators::util::create_rayon_thread_pool;
37
use log::info;
38
use rayon::ThreadPool;
39
use snafu::ResultExt;
40
use std::path::PathBuf;
41
use std::sync::Arc;
42
use tokio_postgres::error::SqlState;
43
use uuid::Uuid;
44

45
// TODO: do not report postgres error details to user
46

47
/// A contex with references to Postgres backends of the dbs. Automatically migrates schema on instantiation
48
#[derive(Clone)]
49
pub struct PostgresContext<Tls>
50
where
51
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
52
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
53
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
54
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
55
{
56
    thread_pool: Arc<ThreadPool>,
57
    exe_ctx_tiling_spec: TilingSpecification,
58
    query_ctx_chunk_size: ChunkByteSize,
59
    task_manager: Arc<TypedTaskManagerBackend>,
60
    oidc_manager: OidcManager,
61
    quota: QuotaTrackingFactory,
62
    pub(crate) pool: Pool<PostgresConnectionManager<Tls>>,
63
    volumes: Volumes,
64
    tile_cache: Arc<SharedCache>,
65
}
66

67
impl<Tls> PostgresContext<Tls>
68
where
69
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
70
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
71
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
72
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
73
{
74
    pub async fn new_with_context_spec(
302✔
75
        config: Config,
302✔
76
        tls: Tls,
302✔
77
        exe_ctx_tiling_spec: TilingSpecification,
302✔
78
        query_ctx_chunk_size: ChunkByteSize,
302✔
79
        quota_config: Quota,
302✔
80
        oidc_db: OidcManager,
302✔
81
    ) -> Result<Self> {
302✔
82
        let pg_mgr = PostgresConnectionManager::new(config, tls);
302✔
83

84
        let pool = Pool::builder().build(pg_mgr).await?;
302✔
85

86
        Self::create_pro_database(pool.get().await?).await?;
302✔
87

88
        let db = PostgresDb::new(pool.clone(), UserSession::admin_session());
302✔
89
        let quota = initialize_quota_tracking(
302✔
90
            quota_config.mode,
302✔
91
            db,
302✔
92
            quota_config.increment_quota_buffer_size,
302✔
93
            quota_config.increment_quota_buffer_timeout_seconds,
302✔
94
        );
302✔
95

302✔
96
        Ok(PostgresContext {
302✔
97
            task_manager: Default::default(),
302✔
98
            thread_pool: create_rayon_thread_pool(0),
302✔
99
            exe_ctx_tiling_spec,
302✔
100
            query_ctx_chunk_size,
302✔
101
            oidc_manager: oidc_db,
302✔
102
            quota,
302✔
103
            pool,
302✔
104
            volumes: Default::default(),
302✔
105
            tile_cache: Arc::new(SharedCache::test_default()),
302✔
106
        })
302✔
107
    }
302✔
108

109
    #[allow(clippy::missing_panics_doc)]
110
    pub async fn new_with_oidc(
×
111
        config: Config,
×
112
        tls: Tls,
×
113
        oidc_db: OidcManager,
×
114
        cache_config: Cache,
×
115
        quota_config: Quota,
×
116
    ) -> Result<Self> {
×
117
        let pg_mgr = PostgresConnectionManager::new(config, tls);
×
118

119
        let pool = Pool::builder().build(pg_mgr).await?;
×
120

121
        Self::create_pro_database(pool.get().await?).await?;
×
122

123
        let db = PostgresDb::new(pool.clone(), UserSession::admin_session());
×
124
        let quota = initialize_quota_tracking(
×
125
            quota_config.mode,
×
126
            db,
×
127
            quota_config.increment_quota_buffer_size,
×
128
            quota_config.increment_quota_buffer_timeout_seconds,
×
129
        );
×
130

×
131
        Ok(PostgresContext {
×
132
            task_manager: Default::default(),
×
133
            thread_pool: create_rayon_thread_pool(0),
×
134
            exe_ctx_tiling_spec: TestDefault::test_default(),
×
135
            query_ctx_chunk_size: TestDefault::test_default(),
×
136
            oidc_manager: oidc_db,
×
137
            quota,
×
138
            pool,
×
139
            volumes: Default::default(),
×
140
            tile_cache: Arc::new(
×
141
                SharedCache::new(cache_config.size_in_mb, cache_config.landing_zone_ratio)
×
142
                    .expect("tile cache creation should work because the config is valid"),
×
143
            ),
×
144
        })
×
145
    }
×
146

147
    // TODO: check if the datasets exist already and don't output warnings when skipping them
148
    #[allow(clippy::too_many_arguments, clippy::missing_panics_doc)]
149
    pub async fn new_with_data(
×
150
        config: Config,
×
151
        tls: Tls,
×
152
        dataset_defs_path: PathBuf,
×
153
        provider_defs_path: PathBuf,
×
154
        layer_defs_path: PathBuf,
×
155
        layer_collection_defs_path: PathBuf,
×
156
        exe_ctx_tiling_spec: TilingSpecification,
×
157
        query_ctx_chunk_size: ChunkByteSize,
×
158
        oidc_config: Oidc,
×
159
        cache_config: Cache,
×
160
        quota_config: Quota,
×
161
    ) -> Result<Self> {
×
162
        let pg_mgr = PostgresConnectionManager::new(config, tls);
×
163

164
        let pool = Pool::builder().build(pg_mgr).await?;
×
165

166
        let created_schema = Self::create_pro_database(pool.get().await?).await?;
×
167

168
        let db = PostgresDb::new(pool.clone(), UserSession::admin_session());
×
169
        let quota = initialize_quota_tracking(
×
170
            quota_config.mode,
×
171
            db,
×
172
            quota_config.increment_quota_buffer_size,
×
173
            quota_config.increment_quota_buffer_timeout_seconds,
×
174
        );
×
175

×
176
        let app_ctx = PostgresContext {
×
177
            task_manager: Default::default(),
×
178
            thread_pool: create_rayon_thread_pool(0),
×
179
            exe_ctx_tiling_spec,
×
180
            query_ctx_chunk_size,
×
181
            oidc_manager: OidcManager::from(oidc_config),
×
182
            quota,
×
183
            pool,
×
184
            volumes: Default::default(),
×
185
            tile_cache: Arc::new(
×
186
                SharedCache::new(cache_config.size_in_mb, cache_config.landing_zone_ratio)
×
187
                    .expect("tile cache creation should work because the config is valid"),
×
188
            ),
×
189
        };
×
190

×
191
        if created_schema {
×
192
            info!("Populating database with initial data...");
×
193

194
            let mut db = app_ctx.session_context(UserSession::admin_session()).db();
×
195

×
196
            add_layers_from_directory(&mut db, layer_defs_path).await;
×
197
            add_layer_collections_from_directory(&mut db, layer_collection_defs_path).await;
×
198

199
            add_datasets_from_directory(&mut db, dataset_defs_path).await;
×
200

201
            add_providers_from_directory(&mut db, provider_defs_path.clone()).await;
×
202
        }
×
203

204
        Ok(app_ctx)
×
205
    }
×
206

207
    #[allow(clippy::too_many_lines)]
208
    /// Creates the database schema. Returns true if the schema was created, false if it already existed.
209
    pub(crate) async fn create_pro_database(
302✔
210
        mut conn: PooledConnection<'_, PostgresConnectionManager<Tls>>,
302✔
211
    ) -> Result<bool> {
302✔
212
        Self::maybe_clear_database(&conn).await?;
302✔
213

214
        let migration = initialize_database(
302✔
215
            &mut conn,
302✔
216
            Box::new(CurrentSchemaMigration),
302✔
217
            &all_migrations(),
302✔
218
        )
302✔
219
        .await?;
302✔
220

221
        Ok(migration == MigrationResult::CreatedDatabase)
302✔
222
    }
302✔
223

224
    /// Clears the database if the Settings demand and the database properties allows it.
225
    pub(crate) async fn maybe_clear_database(
302✔
226
        conn: &PooledConnection<'_, PostgresConnectionManager<Tls>>,
302✔
227
    ) -> Result<()> {
302✔
228
        let postgres_config = get_config_element::<crate::config::Postgres>()?;
302✔
229
        let database_status = Self::check_schema_status(conn).await?;
302✔
230
        let schema_name = postgres_config.schema;
302✔
231

232
        match database_status {
×
233
            DatabaseStatus::InitializedClearDatabase
×
234
                if postgres_config.clear_database_on_start && schema_name != "pg_temp" =>
×
235
            {
×
236
                info!("Clearing schema {}.", schema_name);
×
237
                conn.batch_execute(&format!("DROP SCHEMA {schema_name} CASCADE;"))
×
238
                    .await?;
×
239
            }
240
            DatabaseStatus::InitializedKeepDatabase if postgres_config.clear_database_on_start => {
×
241
                return Err(Error::ClearDatabaseOnStartupNotAllowed)
×
242
            }
243
            DatabaseStatus::InitializedClearDatabase
244
            | DatabaseStatus::InitializedKeepDatabase
245
            | DatabaseStatus::Unitialized => (),
302✔
246
        };
247

248
        Ok(())
302✔
249
    }
302✔
250

251
    async fn check_schema_status(
302✔
252
        conn: &PooledConnection<'_, PostgresConnectionManager<Tls>>,
302✔
253
    ) -> Result<DatabaseStatus> {
302✔
254
        let stmt = match conn
302✔
255
            .prepare("SELECT clear_database_on_start from geoengine;")
302✔
256
            .await
302✔
257
        {
258
            Ok(stmt) => stmt,
×
259
            Err(e) => {
302✔
260
                if let Some(code) = e.code() {
302✔
261
                    if *code == SqlState::UNDEFINED_TABLE {
302✔
262
                        info!("Initializing schema.");
302✔
263
                        return Ok(DatabaseStatus::Unitialized);
302✔
264
                    }
×
265
                }
×
266
                return Err(error::Error::TokioPostgres { source: e });
×
267
            }
268
        };
269

270
        let row = conn.query_one(&stmt, &[]).await?;
×
271

272
        if row.get(0) {
×
273
            Ok(DatabaseStatus::InitializedClearDatabase)
×
274
        } else {
275
            Ok(DatabaseStatus::InitializedKeepDatabase)
×
276
        }
277
    }
302✔
278
}
279

280
enum DatabaseStatus {
281
    Unitialized,
282
    InitializedClearDatabase,
283
    InitializedKeepDatabase,
284
}
285

286
#[async_trait]
287
impl<Tls> ApplicationContext for PostgresContext<Tls>
288
where
289
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
290
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
291
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
292
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
293
{
294
    type SessionContext = PostgresSessionContext<Tls>;
295
    type Session = UserSession;
296

297
    fn session_context(&self, session: Self::Session) -> Self::SessionContext {
584✔
298
        PostgresSessionContext {
584✔
299
            session,
584✔
300
            context: self.clone(),
584✔
301
        }
584✔
302
    }
584✔
303

304
    async fn session_by_id(&self, session_id: SessionId) -> Result<Self::Session> {
174✔
305
        self.user_session_by_id(session_id)
174✔
306
            .await
174✔
307
            .map_err(Box::new)
170✔
308
            .context(error::Unauthorized)
170✔
309
    }
344✔
310

311
    fn oidc_manager(&self) -> &OidcManager {
30✔
312
        &self.oidc_manager
30✔
313
    }
30✔
314
}
315

316
#[derive(Clone)]
317
pub struct PostgresSessionContext<Tls>
318
where
319
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
320
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
321
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
322
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
323
{
324
    session: UserSession,
325
    context: PostgresContext<Tls>,
326
}
327

328
#[async_trait]
329
impl<Tls> SessionContext for PostgresSessionContext<Tls>
330
where
331
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
332
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
333
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
334
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
335
{
336
    type Session = UserSession;
337
    type GeoEngineDB = PostgresDb<Tls>;
338

339
    type TaskContext = SimpleTaskManagerContext;
340
    type TaskManager = UserTaskManager; // this does not persist across restarts
341
    type QueryContext = QueryContextImpl;
342
    type ExecutionContext = ExecutionContextImpl<Self::GeoEngineDB>;
343

344
    fn db(&self) -> Self::GeoEngineDB {
827✔
345
        PostgresDb::new(self.context.pool.clone(), self.session.clone())
827✔
346
    }
827✔
347

348
    fn tasks(&self) -> Self::TaskManager {
41✔
349
        UserTaskManager::new(self.context.task_manager.clone(), self.session.clone())
41✔
350
    }
41✔
351

352
    fn query_context(&self, workflow: Uuid, computation: Uuid) -> Result<Self::QueryContext> {
32✔
353
        // TODO: load config only once
32✔
354

32✔
355
        Ok(QueryContextImpl::new_with_extensions(
32✔
356
            self.context.query_ctx_chunk_size,
32✔
357
            self.context.thread_pool.clone(),
32✔
358
            Some(self.context.tile_cache.clone()),
32✔
359
            Some(
32✔
360
                self.context
32✔
361
                    .quota
32✔
362
                    .create_quota_tracking(&self.session, workflow, computation),
32✔
363
            ),
32✔
364
            Some(Box::new(QuotaCheckerImpl { user_db: self.db() }) as QuotaChecker),
32✔
365
        ))
32✔
366
    }
32✔
367

368
    fn execution_context(&self) -> Result<Self::ExecutionContext> {
57✔
369
        Ok(ExecutionContextImpl::<PostgresDb<Tls>>::new(
57✔
370
            self.db(),
57✔
371
            self.context.thread_pool.clone(),
57✔
372
            self.context.exe_ctx_tiling_spec,
57✔
373
        ))
57✔
374
    }
57✔
375

376
    fn volumes(&self) -> Result<Vec<Volume>> {
1✔
377
        Ok(self
1✔
378
            .context
1✔
379
            .volumes
1✔
380
            .volumes
1✔
381
            .iter()
1✔
382
            .map(|v| Volume {
1✔
383
                name: v.name.0.clone(),
1✔
384
                path: if self.session.is_admin() {
1✔
385
                    Some(v.path.to_string_lossy().to_string())
1✔
386
                } else {
387
                    None
×
388
                },
389
            })
1✔
390
            .collect())
1✔
391
    }
1✔
392

393
    fn session(&self) -> &Self::Session {
30✔
394
        &self.session
30✔
395
    }
30✔
396
}
397

398
#[derive(Debug)]
399
pub struct PostgresDb<Tls>
400
where
401
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
402
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
403
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
404
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
405
{
406
    pub(crate) conn_pool: Pool<PostgresConnectionManager<Tls>>,
407
    pub(crate) session: UserSession,
408
}
409

410
impl<Tls> PostgresDb<Tls>
411
where
412
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
413
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
414
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
415
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
416
{
417
    pub fn new(conn_pool: Pool<PostgresConnectionManager<Tls>>, session: UserSession) -> Self {
1,130✔
418
        Self { conn_pool, session }
1,130✔
419
    }
1,130✔
420

421
    /// Check whether the namepsace of the given dataset is allowed for insertion
422
    pub(crate) fn check_dataset_namespace(&self, id: &DatasetName) -> Result<()> {
97✔
423
        let is_ok = match &id.namespace {
97✔
424
            Some(namespace) => namespace.as_str() == self.session.user.id.to_string(),
32✔
425
            None => self.session.is_admin(),
65✔
426
        };
427

428
        if is_ok {
97✔
429
            Ok(())
97✔
430
        } else {
431
            Err(Error::InvalidDatasetIdNamespace)
×
432
        }
433
    }
97✔
434

435
    /// Check whether the namepsace of the given model is allowed for insertion
436
    pub(crate) fn check_ml_model_namespace(
3✔
437
        &self,
3✔
438
        name: &MlModelName,
3✔
439
    ) -> Result<(), MachineLearningError> {
3✔
440
        let is_ok = match &name.namespace {
3✔
441
            Some(namespace) => namespace.as_str() == self.session.user.id.to_string(),
1✔
442
            None => self.session.is_admin(),
2✔
443
        };
444

445
        if is_ok {
3✔
446
            Ok(())
3✔
447
        } else {
448
            Err(MachineLearningError::InvalidModelNamespace { name: name.clone() })
×
449
        }
450
    }
3✔
451
}
452

453
impl<Tls> GeoEngineDb for PostgresDb<Tls>
454
where
455
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
456
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
457
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
458
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
459
{
460
}
461

462
#[cfg(test)]
463
mod tests {
464
    use super::*;
465
    use crate::api::model::datatypes::{RasterDataType as ApiRasterDataType, TensorShape3D};
466
    use crate::config::QuotaTrackingMode;
467
    use crate::datasets::external::netcdfcf::NetCdfCfDataProviderDefinition;
468
    use crate::datasets::listing::{DatasetListOptions, DatasetListing, ProvenanceOutput};
469
    use crate::datasets::listing::{DatasetProvider, Provenance};
470
    use crate::datasets::storage::{DatasetStore, MetaDataDefinition};
471
    use crate::datasets::upload::{FileId, UploadId};
472
    use crate::datasets::upload::{FileUpload, Upload, UploadDb};
473
    use crate::datasets::{AddDataset, DatasetIdAndName};
474
    use crate::ge_context;
475
    use crate::layers::add_from_directory::UNSORTED_COLLECTION_ID;
476
    use crate::layers::layer::{
477
        AddLayer, AddLayerCollection, CollectionItem, LayerCollection, LayerCollectionListOptions,
478
        LayerCollectionListing, LayerListing, ProviderLayerCollectionId, ProviderLayerId,
479
    };
480
    use crate::layers::listing::{
481
        LayerCollectionId, LayerCollectionProvider, SearchParameters, SearchType,
482
    };
483
    use crate::layers::storage::{
484
        LayerDb, LayerProviderDb, LayerProviderListing, LayerProviderListingOptions,
485
        INTERNAL_PROVIDER_ID,
486
    };
487
    use crate::machine_learning::{MlModel, MlModelDb, MlModelIdAndName, MlModelMetadata};
488
    use crate::permissions::{Permission, PermissionDb, Role, RoleDescription, RoleId};
489
    use crate::projects::{
490
        CreateProject, LayerUpdate, LoadVersion, OrderBy, Plot, PlotUpdate, PointSymbology,
491
        ProjectDb, ProjectId, ProjectLayer, ProjectListOptions, ProjectListing, STRectangle,
492
        UpdateProject,
493
    };
494
    use crate::users::{OidcTokens, SessionTokenStore};
495
    use crate::users::{RoleDb, UserClaims, UserCredentials, UserDb, UserId, UserRegistration};
496
    use crate::util::tests::mock_oidc::{mock_refresh_server, MockRefreshServerConfig};
497
    use crate::util::tests::{admin_login, register_ndvi_workflow_helper, MockQuotaTracking};
498
    use crate::workflows::registry::WorkflowRegistry;
499
    use crate::workflows::workflow::Workflow;
500
    use bb8_postgres::tokio_postgres::NoTls;
501
    use futures::join;
502
    use geoengine_datatypes::collections::VectorDataType;
503
    use geoengine_datatypes::dataset::{DataProviderId, LayerId};
504
    use geoengine_datatypes::primitives::{
505
        BoundingBox2D, Coordinate2D, DateTime, Duration, FeatureDataType, Measurement,
506
        RasterQueryRectangle, SpatialResolution, TimeGranularity, TimeInstance, TimeInterval,
507
        TimeStep, VectorQueryRectangle,
508
    };
509
    use geoengine_datatypes::primitives::{CacheTtlSeconds, ColumnSelection};
510
    use geoengine_datatypes::raster::RasterDataType;
511
    use geoengine_datatypes::spatial_reference::{SpatialReference, SpatialReferenceOption};
512
    use geoengine_datatypes::test_data;
513
    use geoengine_datatypes::util::Identifier;
514
    use geoengine_operators::engine::{
515
        MetaData, MetaDataProvider, MultipleRasterOrSingleVectorSource, PlotOperator,
516
        RasterBandDescriptors, RasterResultDescriptor, StaticMetaData, TypedOperator,
517
        TypedResultDescriptor, VectorColumnInfo, VectorOperator, VectorResultDescriptor,
518
    };
519
    use geoengine_operators::mock::{MockPointSource, MockPointSourceParams};
520
    use geoengine_operators::plot::{Statistics, StatisticsParams};
521
    use geoengine_operators::source::{
522
        CsvHeader, FileNotFoundHandling, FormatSpecifics, GdalDatasetGeoTransform,
523
        GdalDatasetParameters, GdalLoadingInfo, GdalMetaDataList, GdalMetaDataRegular,
524
        GdalMetaDataStatic, GdalMetadataNetCdfCf, OgrSourceColumnSpec, OgrSourceDataset,
525
        OgrSourceDatasetTimeType, OgrSourceDurationSpec, OgrSourceErrorSpec, OgrSourceTimeFormat,
526
    };
527
    use geoengine_operators::util::input::MultiRasterOrVectorOperator::Raster;
528
    use httptest::Server;
529
    use oauth2::{AccessToken, RefreshToken};
530
    use openidconnect::SubjectIdentifier;
531
    use serde_json::json;
532
    use std::str::FromStr;
533

534
    #[ge_context::test]
1✔
535
    async fn test(app_ctx: PostgresContext<NoTls>) {
1✔
536
        anonymous(&app_ctx).await;
1✔
537

538
        let _user_id = user_reg_login(&app_ctx).await;
1✔
539

540
        let session = app_ctx
1✔
541
            .login(UserCredentials {
1✔
542
                email: "foo@example.com".into(),
1✔
543
                password: "secret123".into(),
1✔
544
            })
1✔
545
            .await
1✔
546
            .unwrap();
1✔
547

1✔
548
        create_projects(&app_ctx, &session).await;
1✔
549

550
        let projects = list_projects(&app_ctx, &session).await;
1✔
551

552
        set_session(&app_ctx, &projects).await;
1✔
553

554
        let project_id = projects[0].id;
1✔
555

1✔
556
        update_projects(&app_ctx, &session, project_id).await;
1✔
557

558
        add_permission(&app_ctx, &session, project_id).await;
1✔
559

560
        delete_project(&app_ctx, &session, project_id).await;
1✔
561
    }
1✔
562

563
    #[ge_context::test]
1✔
564
    async fn test_external(app_ctx: PostgresContext<NoTls>) {
1✔
565
        anonymous(&app_ctx).await;
1✔
566

567
        let session = external_user_login_twice(&app_ctx).await;
1✔
568

569
        create_projects(&app_ctx, &session).await;
1✔
570

571
        let projects = list_projects(&app_ctx, &session).await;
1✔
572

573
        set_session_external(&app_ctx, &projects).await;
1✔
574

575
        let project_id = projects[0].id;
1✔
576

1✔
577
        update_projects(&app_ctx, &session, project_id).await;
1✔
578

579
        add_permission(&app_ctx, &session, project_id).await;
1✔
580

581
        delete_project(&app_ctx, &session, project_id).await;
1✔
582
    }
1✔
583

584
    fn tokens_from_duration(duration: Duration) -> OidcTokens {
3✔
585
        OidcTokens {
3✔
586
            access: AccessToken::new("AccessToken".to_string()),
3✔
587
            refresh: None,
3✔
588
            expires_in: duration,
3✔
589
        }
3✔
590
    }
3✔
591

592
    async fn set_session(app_ctx: &PostgresContext<NoTls>, projects: &[ProjectListing]) {
1✔
593
        let credentials = UserCredentials {
1✔
594
            email: "foo@example.com".into(),
1✔
595
            password: "secret123".into(),
1✔
596
        };
1✔
597

598
        let session = app_ctx.login(credentials).await.unwrap();
1✔
599

1✔
600
        set_session_in_database(app_ctx, projects, session).await;
1✔
601
    }
1✔
602

603
    async fn set_session_external(app_ctx: &PostgresContext<NoTls>, projects: &[ProjectListing]) {
1✔
604
        let external_user_claims = UserClaims {
1✔
605
            external_id: SubjectIdentifier::new("Foo bar Id".into()),
1✔
606
            email: "foo@bar.de".into(),
1✔
607
            real_name: "Foo Bar".into(),
1✔
608
        };
1✔
609

610
        let session = app_ctx
1✔
611
            .login_external(
1✔
612
                external_user_claims,
1✔
613
                tokens_from_duration(Duration::minutes(10)),
1✔
614
            )
1✔
615
            .await
1✔
616
            .unwrap();
1✔
617

1✔
618
        set_session_in_database(app_ctx, projects, session).await;
1✔
619
    }
1✔
620

621
    async fn set_session_in_database(
2✔
622
        app_ctx: &PostgresContext<NoTls>,
2✔
623
        projects: &[ProjectListing],
2✔
624
        session: UserSession,
2✔
625
    ) {
2✔
626
        let db = app_ctx.session_context(session.clone()).db();
2✔
627

2✔
628
        db.set_session_project(projects[0].id).await.unwrap();
2✔
629

630
        assert_eq!(
2✔
631
            app_ctx.session_by_id(session.id).await.unwrap().project,
2✔
632
            Some(projects[0].id)
2✔
633
        );
634

635
        let rect = STRectangle::new_unchecked(SpatialReference::epsg_4326(), 0., 1., 2., 3., 1, 2);
2✔
636
        db.set_session_view(rect.clone()).await.unwrap();
2✔
637
        assert_eq!(
2✔
638
            app_ctx.session_by_id(session.id).await.unwrap().view,
2✔
639
            Some(rect)
2✔
640
        );
641
    }
2✔
642

643
    async fn delete_project(
2✔
644
        app_ctx: &PostgresContext<NoTls>,
2✔
645
        session: &UserSession,
2✔
646
        project_id: ProjectId,
2✔
647
    ) {
2✔
648
        let db = app_ctx.session_context(session.clone()).db();
2✔
649

2✔
650
        db.delete_project(project_id).await.unwrap();
2✔
651

2✔
652
        assert!(db.load_project(project_id).await.is_err());
2✔
653
    }
2✔
654

655
    async fn add_permission(
2✔
656
        app_ctx: &PostgresContext<NoTls>,
2✔
657
        session: &UserSession,
2✔
658
        project_id: ProjectId,
2✔
659
    ) {
2✔
660
        let db = app_ctx.session_context(session.clone()).db();
2✔
661

2✔
662
        assert!(db
2✔
663
            .has_permission(project_id, Permission::Owner)
2✔
664
            .await
2✔
665
            .unwrap());
2✔
666

667
        let user2 = app_ctx
2✔
668
            .register_user(UserRegistration {
2✔
669
                email: "user2@example.com".into(),
2✔
670
                password: "12345678".into(),
2✔
671
                real_name: "User2".into(),
2✔
672
            })
2✔
673
            .await
2✔
674
            .unwrap();
2✔
675

676
        let session2 = app_ctx
2✔
677
            .login(UserCredentials {
2✔
678
                email: "user2@example.com".into(),
2✔
679
                password: "12345678".into(),
2✔
680
            })
2✔
681
            .await
2✔
682
            .unwrap();
2✔
683

2✔
684
        let db2 = app_ctx.session_context(session2.clone()).db();
2✔
685
        assert!(!db2
2✔
686
            .has_permission(project_id, Permission::Owner)
2✔
687
            .await
2✔
688
            .unwrap());
2✔
689

690
        db.add_permission(user2.into(), project_id, Permission::Read)
2✔
691
            .await
2✔
692
            .unwrap();
2✔
693

2✔
694
        assert!(db2
2✔
695
            .has_permission(project_id, Permission::Read)
2✔
696
            .await
2✔
697
            .unwrap());
2✔
698
    }
2✔
699

700
    #[allow(clippy::too_many_lines)]
701
    async fn update_projects(
2✔
702
        app_ctx: &PostgresContext<NoTls>,
2✔
703
        session: &UserSession,
2✔
704
        project_id: ProjectId,
2✔
705
    ) {
2✔
706
        let db = app_ctx.session_context(session.clone()).db();
2✔
707

708
        let project = db
2✔
709
            .load_project_version(project_id, LoadVersion::Latest)
2✔
710
            .await
2✔
711
            .unwrap();
2✔
712

713
        let layer_workflow_id = db
2✔
714
            .register_workflow(Workflow {
2✔
715
                operator: TypedOperator::Vector(
2✔
716
                    MockPointSource {
2✔
717
                        params: MockPointSourceParams {
2✔
718
                            points: vec![Coordinate2D::new(1., 2.); 3],
2✔
719
                        },
2✔
720
                    }
2✔
721
                    .boxed(),
2✔
722
                ),
2✔
723
            })
2✔
724
            .await
2✔
725
            .unwrap();
2✔
726

2✔
727
        assert!(db.load_workflow(&layer_workflow_id).await.is_ok());
2✔
728

729
        let plot_workflow_id = db
2✔
730
            .register_workflow(Workflow {
2✔
731
                operator: Statistics {
2✔
732
                    params: StatisticsParams {
2✔
733
                        column_names: vec![],
2✔
734
                        percentiles: vec![],
2✔
735
                    },
2✔
736
                    sources: MultipleRasterOrSingleVectorSource {
2✔
737
                        source: Raster(vec![]),
2✔
738
                    },
2✔
739
                }
2✔
740
                .boxed()
2✔
741
                .into(),
2✔
742
            })
2✔
743
            .await
2✔
744
            .unwrap();
2✔
745

2✔
746
        assert!(db.load_workflow(&plot_workflow_id).await.is_ok());
2✔
747

748
        // add a plot
749
        let update = UpdateProject {
2✔
750
            id: project.id,
2✔
751
            name: Some("Test9 Updated".into()),
2✔
752
            description: None,
2✔
753
            layers: Some(vec![LayerUpdate::UpdateOrInsert(ProjectLayer {
2✔
754
                workflow: layer_workflow_id,
2✔
755
                name: "TestLayer".into(),
2✔
756
                symbology: PointSymbology::default().into(),
2✔
757
                visibility: Default::default(),
2✔
758
            })]),
2✔
759
            plots: Some(vec![PlotUpdate::UpdateOrInsert(Plot {
2✔
760
                workflow: plot_workflow_id,
2✔
761
                name: "Test Plot".into(),
2✔
762
            })]),
2✔
763
            bounds: None,
2✔
764
            time_step: None,
2✔
765
        };
2✔
766
        db.update_project(update).await.unwrap();
2✔
767

768
        let versions = db.list_project_versions(project_id).await.unwrap();
2✔
769
        assert_eq!(versions.len(), 2);
2✔
770

771
        // add second plot
772
        let update = UpdateProject {
2✔
773
            id: project.id,
2✔
774
            name: Some("Test9 Updated".into()),
2✔
775
            description: None,
2✔
776
            layers: Some(vec![LayerUpdate::UpdateOrInsert(ProjectLayer {
2✔
777
                workflow: layer_workflow_id,
2✔
778
                name: "TestLayer".into(),
2✔
779
                symbology: PointSymbology::default().into(),
2✔
780
                visibility: Default::default(),
2✔
781
            })]),
2✔
782
            plots: Some(vec![
2✔
783
                PlotUpdate::UpdateOrInsert(Plot {
2✔
784
                    workflow: plot_workflow_id,
2✔
785
                    name: "Test Plot".into(),
2✔
786
                }),
2✔
787
                PlotUpdate::UpdateOrInsert(Plot {
2✔
788
                    workflow: plot_workflow_id,
2✔
789
                    name: "Test Plot".into(),
2✔
790
                }),
2✔
791
            ]),
2✔
792
            bounds: None,
2✔
793
            time_step: None,
2✔
794
        };
2✔
795
        db.update_project(update).await.unwrap();
2✔
796

797
        let versions = db.list_project_versions(project_id).await.unwrap();
2✔
798
        assert_eq!(versions.len(), 3);
2✔
799

800
        // delete plots
801
        let update = UpdateProject {
2✔
802
            id: project.id,
2✔
803
            name: None,
2✔
804
            description: None,
2✔
805
            layers: None,
2✔
806
            plots: Some(vec![]),
2✔
807
            bounds: None,
2✔
808
            time_step: None,
2✔
809
        };
2✔
810
        db.update_project(update).await.unwrap();
2✔
811

812
        let versions = db.list_project_versions(project_id).await.unwrap();
2✔
813
        assert_eq!(versions.len(), 4);
2✔
814
    }
2✔
815

816
    async fn list_projects(
2✔
817
        app_ctx: &PostgresContext<NoTls>,
2✔
818
        session: &UserSession,
2✔
819
    ) -> Vec<ProjectListing> {
2✔
820
        let options = ProjectListOptions {
2✔
821
            order: OrderBy::NameDesc,
2✔
822
            offset: 0,
2✔
823
            limit: 2,
2✔
824
        };
2✔
825

2✔
826
        let db = app_ctx.session_context(session.clone()).db();
2✔
827

828
        let projects = db.list_projects(options).await.unwrap();
2✔
829

2✔
830
        assert_eq!(projects.len(), 2);
2✔
831
        assert_eq!(projects[0].name, "Test9");
2✔
832
        assert_eq!(projects[1].name, "Test8");
2✔
833
        projects
2✔
834
    }
2✔
835

836
    async fn create_projects(app_ctx: &PostgresContext<NoTls>, session: &UserSession) {
2✔
837
        let db = app_ctx.session_context(session.clone()).db();
2✔
838

839
        for i in 0..10 {
22✔
840
            let create = CreateProject {
20✔
841
                name: format!("Test{i}"),
20✔
842
                description: format!("Test{}", 10 - i),
20✔
843
                bounds: STRectangle::new(
20✔
844
                    SpatialReferenceOption::Unreferenced,
20✔
845
                    0.,
20✔
846
                    0.,
20✔
847
                    1.,
20✔
848
                    1.,
20✔
849
                    0,
20✔
850
                    1,
20✔
851
                )
20✔
852
                .unwrap(),
20✔
853
                time_step: None,
20✔
854
            };
20✔
855
            db.create_project(create).await.unwrap();
20✔
856
        }
857
    }
2✔
858

859
    async fn user_reg_login(app_ctx: &PostgresContext<NoTls>) -> UserId {
1✔
860
        let user_registration = UserRegistration {
1✔
861
            email: "foo@example.com".into(),
1✔
862
            password: "secret123".into(),
1✔
863
            real_name: "Foo Bar".into(),
1✔
864
        };
1✔
865

866
        let user_id = app_ctx.register_user(user_registration).await.unwrap();
1✔
867

1✔
868
        let credentials = UserCredentials {
1✔
869
            email: "foo@example.com".into(),
1✔
870
            password: "secret123".into(),
1✔
871
        };
1✔
872

873
        let session = app_ctx.login(credentials).await.unwrap();
1✔
874

1✔
875
        let db = app_ctx.session_context(session.clone()).db();
1✔
876

1✔
877
        app_ctx.session_by_id(session.id).await.unwrap();
1✔
878

1✔
879
        db.logout().await.unwrap();
1✔
880

1✔
881
        assert!(app_ctx.session_by_id(session.id).await.is_err());
1✔
882

883
        user_id
1✔
884
    }
1✔
885

886
    async fn external_user_login_twice(app_ctx: &PostgresContext<NoTls>) -> UserSession {
1✔
887
        let external_user_claims = UserClaims {
1✔
888
            external_id: SubjectIdentifier::new("Foo bar Id".into()),
1✔
889
            email: "foo@bar.de".into(),
1✔
890
            real_name: "Foo Bar".into(),
1✔
891
        };
1✔
892
        let duration = Duration::minutes(30);
1✔
893

894
        let login_result = app_ctx
1✔
895
            .login_external(external_user_claims.clone(), tokens_from_duration(duration))
1✔
896
            .await;
1✔
897
        assert!(login_result.is_ok());
1✔
898

899
        let session_1 = login_result.unwrap();
1✔
900
        let user_id = session_1.user.id; //TODO: Not a deterministic test.
1✔
901

1✔
902
        let db1 = app_ctx.session_context(session_1.clone()).db();
1✔
903

1✔
904
        assert!(session_1.user.email.is_some());
1✔
905
        assert_eq!(session_1.user.email.unwrap(), "foo@bar.de");
1✔
906
        assert!(session_1.user.real_name.is_some());
1✔
907
        assert_eq!(session_1.user.real_name.unwrap(), "Foo Bar");
1✔
908

909
        let expected_duration = session_1.created + duration;
1✔
910
        assert_eq!(session_1.valid_until, expected_duration);
1✔
911

912
        assert!(app_ctx.session_by_id(session_1.id).await.is_ok());
1✔
913

914
        assert!(db1.logout().await.is_ok());
1✔
915

916
        assert!(app_ctx.session_by_id(session_1.id).await.is_err());
1✔
917

918
        let duration = Duration::minutes(10);
1✔
919
        let login_result = app_ctx
1✔
920
            .login_external(external_user_claims.clone(), tokens_from_duration(duration))
1✔
921
            .await;
1✔
922
        assert!(login_result.is_ok());
1✔
923

924
        let session_2 = login_result.unwrap();
1✔
925
        let result = session_2.clone();
1✔
926

1✔
927
        assert!(session_2.user.email.is_some()); //TODO: Technically, user details could change for each login. For simplicity, this is not covered yet.
1✔
928
        assert_eq!(session_2.user.email.unwrap(), "foo@bar.de");
1✔
929
        assert!(session_2.user.real_name.is_some());
1✔
930
        assert_eq!(session_2.user.real_name.unwrap(), "Foo Bar");
1✔
931
        assert_eq!(session_2.user.id, user_id);
1✔
932

933
        let expected_duration = session_2.created + duration;
1✔
934
        assert_eq!(session_2.valid_until, expected_duration);
1✔
935

936
        assert!(app_ctx.session_by_id(session_2.id).await.is_ok());
1✔
937

938
        result
1✔
939
    }
1✔
940

941
    async fn anonymous(app_ctx: &PostgresContext<NoTls>) {
2✔
942
        let now: DateTime = chrono::offset::Utc::now().into();
2✔
943
        let session = app_ctx.create_anonymous_session().await.unwrap();
2✔
944
        let then: DateTime = chrono::offset::Utc::now().into();
2✔
945

2✔
946
        assert!(session.created >= now && session.created <= then);
2✔
947
        assert!(session.valid_until > session.created);
2✔
948

949
        let session = app_ctx.session_by_id(session.id).await.unwrap();
2✔
950

2✔
951
        let db = app_ctx.session_context(session.clone()).db();
2✔
952

2✔
953
        db.logout().await.unwrap();
2✔
954

2✔
955
        assert!(app_ctx.session_by_id(session.id).await.is_err());
2✔
956
    }
2✔
957

958
    #[ge_context::test]
1✔
959
    async fn it_persists_workflows(app_ctx: PostgresContext<NoTls>) {
1✔
960
        let workflow = Workflow {
1✔
961
            operator: TypedOperator::Vector(
1✔
962
                MockPointSource {
1✔
963
                    params: MockPointSourceParams {
1✔
964
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
965
                    },
1✔
966
                }
1✔
967
                .boxed(),
1✔
968
            ),
1✔
969
        };
1✔
970

971
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
972
        let ctx = app_ctx.session_context(session);
1✔
973

1✔
974
        let db = ctx.db();
1✔
975
        let id = db.register_workflow(workflow).await.unwrap();
1✔
976

1✔
977
        drop(ctx);
1✔
978

979
        let workflow = db.load_workflow(&id).await.unwrap();
1✔
980

1✔
981
        let json = serde_json::to_string(&workflow).unwrap();
1✔
982
        assert_eq!(
1✔
983
            json,
1✔
984
            r#"{"type":"Vector","operator":{"type":"MockPointSource","params":{"points":[{"x":1.0,"y":2.0},{"x":1.0,"y":2.0},{"x":1.0,"y":2.0}]}}}"#
1✔
985
        );
1✔
986
    }
1✔
987

988
    #[allow(clippy::too_many_lines)]
989
    #[ge_context::test]
1✔
990
    async fn it_persists_datasets(app_ctx: PostgresContext<NoTls>) {
1✔
991
        let loading_info = OgrSourceDataset {
1✔
992
            file_name: PathBuf::from("test.csv"),
1✔
993
            layer_name: "test.csv".to_owned(),
1✔
994
            data_type: Some(VectorDataType::MultiPoint),
1✔
995
            time: OgrSourceDatasetTimeType::Start {
1✔
996
                start_field: "start".to_owned(),
1✔
997
                start_format: OgrSourceTimeFormat::Auto,
1✔
998
                duration: OgrSourceDurationSpec::Zero,
1✔
999
            },
1✔
1000
            default_geometry: None,
1✔
1001
            columns: Some(OgrSourceColumnSpec {
1✔
1002
                format_specifics: Some(FormatSpecifics::Csv {
1✔
1003
                    header: CsvHeader::Auto,
1✔
1004
                }),
1✔
1005
                x: "x".to_owned(),
1✔
1006
                y: None,
1✔
1007
                int: vec![],
1✔
1008
                float: vec![],
1✔
1009
                text: vec![],
1✔
1010
                bool: vec![],
1✔
1011
                datetime: vec![],
1✔
1012
                rename: None,
1✔
1013
            }),
1✔
1014
            force_ogr_time_filter: false,
1✔
1015
            force_ogr_spatial_filter: false,
1✔
1016
            on_error: OgrSourceErrorSpec::Ignore,
1✔
1017
            sql_query: None,
1✔
1018
            attribute_query: None,
1✔
1019
            cache_ttl: CacheTtlSeconds::default(),
1✔
1020
        };
1✔
1021

1✔
1022
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
1023
            OgrSourceDataset,
1✔
1024
            VectorResultDescriptor,
1✔
1025
            VectorQueryRectangle,
1✔
1026
        > {
1✔
1027
            loading_info: loading_info.clone(),
1✔
1028
            result_descriptor: VectorResultDescriptor {
1✔
1029
                data_type: VectorDataType::MultiPoint,
1✔
1030
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1031
                columns: [(
1✔
1032
                    "foo".to_owned(),
1✔
1033
                    VectorColumnInfo {
1✔
1034
                        data_type: FeatureDataType::Float,
1✔
1035
                        measurement: Measurement::Unitless,
1✔
1036
                    },
1✔
1037
                )]
1✔
1038
                .into_iter()
1✔
1039
                .collect(),
1✔
1040
                time: None,
1✔
1041
                bbox: None,
1✔
1042
            },
1✔
1043
            phantom: Default::default(),
1✔
1044
        });
1✔
1045

1046
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
1047

1✔
1048
        let dataset_name = DatasetName::new(Some(session.user.id.to_string()), "my_dataset");
1✔
1049

1✔
1050
        let db = app_ctx.session_context(session.clone()).db();
1✔
1051
        let DatasetIdAndName {
1052
            id: dataset_id,
1✔
1053
            name: dataset_name,
1✔
1054
        } = db
1✔
1055
            .add_dataset(
1✔
1056
                AddDataset {
1✔
1057
                    name: Some(dataset_name.clone()),
1✔
1058
                    display_name: "Ogr Test".to_owned(),
1✔
1059
                    description: "desc".to_owned(),
1✔
1060
                    source_operator: "OgrSource".to_owned(),
1✔
1061
                    symbology: None,
1✔
1062
                    provenance: Some(vec![Provenance {
1✔
1063
                        citation: "citation".to_owned(),
1✔
1064
                        license: "license".to_owned(),
1✔
1065
                        uri: "uri".to_owned(),
1✔
1066
                    }]),
1✔
1067
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1068
                },
1✔
1069
                meta_data,
1✔
1070
            )
1✔
1071
            .await
1✔
1072
            .unwrap();
1✔
1073

1074
        let datasets = db
1✔
1075
            .list_datasets(DatasetListOptions {
1✔
1076
                filter: None,
1✔
1077
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
1078
                offset: 0,
1✔
1079
                limit: 10,
1✔
1080
                tags: None,
1✔
1081
            })
1✔
1082
            .await
1✔
1083
            .unwrap();
1✔
1084

1✔
1085
        assert_eq!(datasets.len(), 1);
1✔
1086

1087
        assert_eq!(
1✔
1088
            datasets[0],
1✔
1089
            DatasetListing {
1✔
1090
                id: dataset_id,
1✔
1091
                name: dataset_name,
1✔
1092
                display_name: "Ogr Test".to_owned(),
1✔
1093
                description: "desc".to_owned(),
1✔
1094
                source_operator: "OgrSource".to_owned(),
1✔
1095
                symbology: None,
1✔
1096
                tags: vec!["upload".to_owned(), "test".to_owned()],
1✔
1097
                result_descriptor: TypedResultDescriptor::Vector(VectorResultDescriptor {
1✔
1098
                    data_type: VectorDataType::MultiPoint,
1✔
1099
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1100
                    columns: [(
1✔
1101
                        "foo".to_owned(),
1✔
1102
                        VectorColumnInfo {
1✔
1103
                            data_type: FeatureDataType::Float,
1✔
1104
                            measurement: Measurement::Unitless
1✔
1105
                        }
1✔
1106
                    )]
1✔
1107
                    .into_iter()
1✔
1108
                    .collect(),
1✔
1109
                    time: None,
1✔
1110
                    bbox: None,
1✔
1111
                })
1✔
1112
            },
1✔
1113
        );
1✔
1114

1115
        let provenance = db.load_provenance(&dataset_id).await.unwrap();
1✔
1116

1✔
1117
        assert_eq!(
1✔
1118
            provenance,
1✔
1119
            ProvenanceOutput {
1✔
1120
                data: dataset_id.into(),
1✔
1121
                provenance: Some(vec![Provenance {
1✔
1122
                    citation: "citation".to_owned(),
1✔
1123
                    license: "license".to_owned(),
1✔
1124
                    uri: "uri".to_owned(),
1✔
1125
                }])
1✔
1126
            }
1✔
1127
        );
1✔
1128

1129
        let meta_data: Box<dyn MetaData<OgrSourceDataset, _, _>> =
1✔
1130
            db.meta_data(&dataset_id.into()).await.unwrap();
1✔
1131

1132
        assert_eq!(
1✔
1133
            meta_data
1✔
1134
                .loading_info(VectorQueryRectangle {
1✔
1135
                    spatial_bounds: BoundingBox2D::new_unchecked(
1✔
1136
                        (-180., -90.).into(),
1✔
1137
                        (180., 90.).into()
1✔
1138
                    ),
1✔
1139
                    time_interval: TimeInterval::default(),
1✔
1140
                    spatial_resolution: SpatialResolution::zero_point_one(),
1✔
1141
                    attributes: ColumnSelection::all()
1✔
1142
                })
1✔
1143
                .await
1✔
1144
                .unwrap(),
1✔
1145
            loading_info
1146
        );
1147
    }
1✔
1148

1149
    #[ge_context::test]
1✔
1150
    async fn it_persists_uploads(app_ctx: PostgresContext<NoTls>) {
1✔
1151
        let id = UploadId::from_str("2de18cd8-4a38-4111-a445-e3734bc18a80").unwrap();
1✔
1152
        let input = Upload {
1✔
1153
            id,
1✔
1154
            files: vec![FileUpload {
1✔
1155
                id: FileId::from_str("e80afab0-831d-4d40-95d6-1e4dfd277e72").unwrap(),
1✔
1156
                name: "test.csv".to_owned(),
1✔
1157
                byte_size: 1337,
1✔
1158
            }],
1✔
1159
        };
1✔
1160

1161
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
1162

1✔
1163
        let db = app_ctx.session_context(session.clone()).db();
1✔
1164

1✔
1165
        db.create_upload(input.clone()).await.unwrap();
1✔
1166

1167
        let upload = db.load_upload(id).await.unwrap();
1✔
1168

1✔
1169
        assert_eq!(upload, input);
1✔
1170
    }
1✔
1171

1172
    #[allow(clippy::too_many_lines)]
1173
    #[ge_context::test]
1✔
1174
    async fn it_persists_layer_providers(app_ctx: PostgresContext<NoTls>) {
1✔
1175
        let db = app_ctx.session_context(UserSession::admin_session()).db();
1✔
1176

1✔
1177
        let provider = NetCdfCfDataProviderDefinition {
1✔
1178
            name: "netcdfcf".to_string(),
1✔
1179
            description: "NetCdfCfProviderDefinition".to_string(),
1✔
1180
            priority: Some(33),
1✔
1181
            data: test_data!("netcdf4d/").into(),
1✔
1182
            overviews: test_data!("netcdf4d/overviews/").into(),
1✔
1183
            cache_ttl: CacheTtlSeconds::new(0),
1✔
1184
        };
1✔
1185

1186
        let provider_id = db.add_layer_provider(provider.into()).await.unwrap();
1✔
1187

1188
        let providers = db
1✔
1189
            .list_layer_providers(LayerProviderListingOptions {
1✔
1190
                offset: 0,
1✔
1191
                limit: 10,
1✔
1192
            })
1✔
1193
            .await
1✔
1194
            .unwrap();
1✔
1195

1✔
1196
        assert_eq!(providers.len(), 1);
1✔
1197

1198
        assert_eq!(
1✔
1199
            providers[0],
1✔
1200
            LayerProviderListing {
1✔
1201
                id: provider_id,
1✔
1202
                name: "netcdfcf".to_owned(),
1✔
1203
                priority: 33,
1✔
1204
            }
1✔
1205
        );
1✔
1206

1207
        let provider = db.load_layer_provider(provider_id).await.unwrap();
1✔
1208

1209
        let datasets = provider
1✔
1210
            .load_layer_collection(
1✔
1211
                &provider.get_root_layer_collection_id().await.unwrap(),
1✔
1212
                LayerCollectionListOptions {
1✔
1213
                    offset: 0,
1✔
1214
                    limit: 10,
1✔
1215
                },
1✔
1216
            )
1✔
1217
            .await
1✔
1218
            .unwrap();
1✔
1219

1✔
1220
        assert_eq!(datasets.items.len(), 5);
1✔
1221
    }
1✔
1222

1223
    #[ge_context::test]
1✔
1224
    async fn it_lists_only_permitted_datasets(app_ctx: PostgresContext<NoTls>) {
1✔
1225
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1226
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1227

1✔
1228
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1229
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1230

1✔
1231
        let descriptor = VectorResultDescriptor {
1✔
1232
            data_type: VectorDataType::Data,
1✔
1233
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1234
            columns: Default::default(),
1✔
1235
            time: None,
1✔
1236
            bbox: None,
1✔
1237
        };
1✔
1238

1✔
1239
        let ds = AddDataset {
1✔
1240
            name: None,
1✔
1241
            display_name: "OgrDataset".to_string(),
1✔
1242
            description: "My Ogr dataset".to_string(),
1✔
1243
            source_operator: "OgrSource".to_string(),
1✔
1244
            symbology: None,
1✔
1245
            provenance: None,
1✔
1246
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1247
        };
1✔
1248

1✔
1249
        let meta = StaticMetaData {
1✔
1250
            loading_info: OgrSourceDataset {
1✔
1251
                file_name: Default::default(),
1✔
1252
                layer_name: String::new(),
1✔
1253
                data_type: None,
1✔
1254
                time: Default::default(),
1✔
1255
                default_geometry: None,
1✔
1256
                columns: None,
1✔
1257
                force_ogr_time_filter: false,
1✔
1258
                force_ogr_spatial_filter: false,
1✔
1259
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1260
                sql_query: None,
1✔
1261
                attribute_query: None,
1✔
1262
                cache_ttl: CacheTtlSeconds::default(),
1✔
1263
            },
1✔
1264
            result_descriptor: descriptor.clone(),
1✔
1265
            phantom: Default::default(),
1✔
1266
        };
1✔
1267

1268
        let _id = db1.add_dataset(ds, meta.into()).await.unwrap();
1✔
1269

1270
        let list1 = db1
1✔
1271
            .list_datasets(DatasetListOptions {
1✔
1272
                filter: None,
1✔
1273
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
1274
                offset: 0,
1✔
1275
                limit: 1,
1✔
1276
                tags: None,
1✔
1277
            })
1✔
1278
            .await
1✔
1279
            .unwrap();
1✔
1280

1✔
1281
        assert_eq!(list1.len(), 1);
1✔
1282

1283
        let list2 = db2
1✔
1284
            .list_datasets(DatasetListOptions {
1✔
1285
                filter: None,
1✔
1286
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
1287
                offset: 0,
1✔
1288
                limit: 1,
1✔
1289
                tags: None,
1✔
1290
            })
1✔
1291
            .await
1✔
1292
            .unwrap();
1✔
1293

1✔
1294
        assert_eq!(list2.len(), 0);
1✔
1295
    }
1✔
1296

1297
    #[ge_context::test]
1✔
1298
    async fn it_shows_only_permitted_provenance(app_ctx: PostgresContext<NoTls>) {
1✔
1299
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1300
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1301

1✔
1302
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1303
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1304

1✔
1305
        let descriptor = VectorResultDescriptor {
1✔
1306
            data_type: VectorDataType::Data,
1✔
1307
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1308
            columns: Default::default(),
1✔
1309
            time: None,
1✔
1310
            bbox: None,
1✔
1311
        };
1✔
1312

1✔
1313
        let ds = AddDataset {
1✔
1314
            name: None,
1✔
1315
            display_name: "OgrDataset".to_string(),
1✔
1316
            description: "My Ogr dataset".to_string(),
1✔
1317
            source_operator: "OgrSource".to_string(),
1✔
1318
            symbology: None,
1✔
1319
            provenance: None,
1✔
1320
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1321
        };
1✔
1322

1✔
1323
        let meta = StaticMetaData {
1✔
1324
            loading_info: OgrSourceDataset {
1✔
1325
                file_name: Default::default(),
1✔
1326
                layer_name: String::new(),
1✔
1327
                data_type: None,
1✔
1328
                time: Default::default(),
1✔
1329
                default_geometry: None,
1✔
1330
                columns: None,
1✔
1331
                force_ogr_time_filter: false,
1✔
1332
                force_ogr_spatial_filter: false,
1✔
1333
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1334
                sql_query: None,
1✔
1335
                attribute_query: None,
1✔
1336
                cache_ttl: CacheTtlSeconds::default(),
1✔
1337
            },
1✔
1338
            result_descriptor: descriptor.clone(),
1✔
1339
            phantom: Default::default(),
1✔
1340
        };
1✔
1341

1342
        let id = db1.add_dataset(ds, meta.into()).await.unwrap().id;
1✔
1343

1✔
1344
        assert!(db1.load_provenance(&id).await.is_ok());
1✔
1345

1346
        assert!(db2.load_provenance(&id).await.is_err());
1✔
1347
    }
1✔
1348

1349
    #[ge_context::test]
1✔
1350
    async fn it_updates_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
1351
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1352
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1353

1✔
1354
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1355
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1356

1✔
1357
        let descriptor = VectorResultDescriptor {
1✔
1358
            data_type: VectorDataType::Data,
1✔
1359
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1360
            columns: Default::default(),
1✔
1361
            time: None,
1✔
1362
            bbox: None,
1✔
1363
        };
1✔
1364

1✔
1365
        let ds = AddDataset {
1✔
1366
            name: None,
1✔
1367
            display_name: "OgrDataset".to_string(),
1✔
1368
            description: "My Ogr dataset".to_string(),
1✔
1369
            source_operator: "OgrSource".to_string(),
1✔
1370
            symbology: None,
1✔
1371
            provenance: None,
1✔
1372
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1373
        };
1✔
1374

1✔
1375
        let meta = StaticMetaData {
1✔
1376
            loading_info: OgrSourceDataset {
1✔
1377
                file_name: Default::default(),
1✔
1378
                layer_name: String::new(),
1✔
1379
                data_type: None,
1✔
1380
                time: Default::default(),
1✔
1381
                default_geometry: None,
1✔
1382
                columns: None,
1✔
1383
                force_ogr_time_filter: false,
1✔
1384
                force_ogr_spatial_filter: false,
1✔
1385
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1386
                sql_query: None,
1✔
1387
                attribute_query: None,
1✔
1388
                cache_ttl: CacheTtlSeconds::default(),
1✔
1389
            },
1✔
1390
            result_descriptor: descriptor.clone(),
1✔
1391
            phantom: Default::default(),
1✔
1392
        };
1✔
1393

1394
        let id = db1.add_dataset(ds, meta.into()).await.unwrap().id;
1✔
1395

1✔
1396
        assert!(db1.load_dataset(&id).await.is_ok());
1✔
1397

1398
        assert!(db2.load_dataset(&id).await.is_err());
1✔
1399

1400
        db1.add_permission(session2.user.id.into(), id, Permission::Read)
1✔
1401
            .await
1✔
1402
            .unwrap();
1✔
1403

1✔
1404
        assert!(db2.load_dataset(&id).await.is_ok());
1✔
1405
    }
1✔
1406

1407
    #[ge_context::test]
1✔
1408
    async fn it_uses_roles_for_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
1409
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1410
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1411

1✔
1412
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1413
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1414

1✔
1415
        let descriptor = VectorResultDescriptor {
1✔
1416
            data_type: VectorDataType::Data,
1✔
1417
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1418
            columns: Default::default(),
1✔
1419
            time: None,
1✔
1420
            bbox: None,
1✔
1421
        };
1✔
1422

1✔
1423
        let ds = AddDataset {
1✔
1424
            name: None,
1✔
1425
            display_name: "OgrDataset".to_string(),
1✔
1426
            description: "My Ogr dataset".to_string(),
1✔
1427
            source_operator: "OgrSource".to_string(),
1✔
1428
            symbology: None,
1✔
1429
            provenance: None,
1✔
1430
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1431
        };
1✔
1432

1✔
1433
        let meta = StaticMetaData {
1✔
1434
            loading_info: OgrSourceDataset {
1✔
1435
                file_name: Default::default(),
1✔
1436
                layer_name: String::new(),
1✔
1437
                data_type: None,
1✔
1438
                time: Default::default(),
1✔
1439
                default_geometry: None,
1✔
1440
                columns: None,
1✔
1441
                force_ogr_time_filter: false,
1✔
1442
                force_ogr_spatial_filter: false,
1✔
1443
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1444
                sql_query: None,
1✔
1445
                attribute_query: None,
1✔
1446
                cache_ttl: CacheTtlSeconds::default(),
1✔
1447
            },
1✔
1448
            result_descriptor: descriptor.clone(),
1✔
1449
            phantom: Default::default(),
1✔
1450
        };
1✔
1451

1452
        let id = db1.add_dataset(ds, meta.into()).await.unwrap().id;
1✔
1453

1✔
1454
        assert!(db1.load_dataset(&id).await.is_ok());
1✔
1455

1456
        assert!(db2.load_dataset(&id).await.is_err());
1✔
1457

1458
        db1.add_permission(session2.user.id.into(), id, Permission::Read)
1✔
1459
            .await
1✔
1460
            .unwrap();
1✔
1461

1✔
1462
        assert!(db2.load_dataset(&id).await.is_ok());
1✔
1463
    }
1✔
1464

1465
    #[ge_context::test]
1✔
1466
    async fn it_secures_meta_data(app_ctx: PostgresContext<NoTls>) {
1✔
1467
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1468
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1469

1✔
1470
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1471
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1472

1✔
1473
        let descriptor = VectorResultDescriptor {
1✔
1474
            data_type: VectorDataType::Data,
1✔
1475
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1476
            columns: Default::default(),
1✔
1477
            time: None,
1✔
1478
            bbox: None,
1✔
1479
        };
1✔
1480

1✔
1481
        let ds = AddDataset {
1✔
1482
            name: None,
1✔
1483
            display_name: "OgrDataset".to_string(),
1✔
1484
            description: "My Ogr dataset".to_string(),
1✔
1485
            source_operator: "OgrSource".to_string(),
1✔
1486
            symbology: None,
1✔
1487
            provenance: None,
1✔
1488
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1489
        };
1✔
1490

1✔
1491
        let meta = StaticMetaData {
1✔
1492
            loading_info: OgrSourceDataset {
1✔
1493
                file_name: Default::default(),
1✔
1494
                layer_name: String::new(),
1✔
1495
                data_type: None,
1✔
1496
                time: Default::default(),
1✔
1497
                default_geometry: None,
1✔
1498
                columns: None,
1✔
1499
                force_ogr_time_filter: false,
1✔
1500
                force_ogr_spatial_filter: false,
1✔
1501
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1502
                sql_query: None,
1✔
1503
                attribute_query: None,
1✔
1504
                cache_ttl: CacheTtlSeconds::default(),
1✔
1505
            },
1✔
1506
            result_descriptor: descriptor.clone(),
1✔
1507
            phantom: Default::default(),
1✔
1508
        };
1✔
1509

1510
        let id = db1.add_dataset(ds, meta.into()).await.unwrap().id;
1✔
1511

1512
        let meta: geoengine_operators::util::Result<
1✔
1513
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1514
        > = db1.meta_data(&id.into()).await;
1✔
1515

1516
        assert!(meta.is_ok());
1✔
1517

1518
        let meta: geoengine_operators::util::Result<
1✔
1519
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1520
        > = db2.meta_data(&id.into()).await;
1✔
1521

1522
        assert!(meta.is_err());
1✔
1523

1524
        db1.add_permission(session2.user.id.into(), id, Permission::Read)
1✔
1525
            .await
1✔
1526
            .unwrap();
1✔
1527

1528
        let meta: geoengine_operators::util::Result<
1✔
1529
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1530
        > = db2.meta_data(&id.into()).await;
1✔
1531

1532
        assert!(meta.is_ok());
1✔
1533
    }
1✔
1534

1535
    #[allow(clippy::too_many_lines)]
1536
    #[ge_context::test]
1✔
1537
    async fn it_loads_all_meta_data_types(app_ctx: PostgresContext<NoTls>) {
1✔
1538
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
1539

1✔
1540
        let db = app_ctx.session_context(session.clone()).db();
1✔
1541

1✔
1542
        let vector_descriptor = VectorResultDescriptor {
1✔
1543
            data_type: VectorDataType::Data,
1✔
1544
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1545
            columns: Default::default(),
1✔
1546
            time: None,
1✔
1547
            bbox: None,
1✔
1548
        };
1✔
1549

1✔
1550
        let raster_descriptor = RasterResultDescriptor {
1✔
1551
            data_type: RasterDataType::U8,
1✔
1552
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1553
            time: None,
1✔
1554
            bbox: None,
1✔
1555
            resolution: None,
1✔
1556
            bands: RasterBandDescriptors::new_single_band(),
1✔
1557
        };
1✔
1558

1✔
1559
        let vector_ds = AddDataset {
1✔
1560
            name: None,
1✔
1561
            display_name: "OgrDataset".to_string(),
1✔
1562
            description: "My Ogr dataset".to_string(),
1✔
1563
            source_operator: "OgrSource".to_string(),
1✔
1564
            symbology: None,
1✔
1565
            provenance: None,
1✔
1566
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1567
        };
1✔
1568

1✔
1569
        let raster_ds = AddDataset {
1✔
1570
            name: None,
1✔
1571
            display_name: "GdalDataset".to_string(),
1✔
1572
            description: "My Gdal dataset".to_string(),
1✔
1573
            source_operator: "GdalSource".to_string(),
1✔
1574
            symbology: None,
1✔
1575
            provenance: None,
1✔
1576
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1577
        };
1✔
1578

1✔
1579
        let gdal_params = GdalDatasetParameters {
1✔
1580
            file_path: Default::default(),
1✔
1581
            rasterband_channel: 0,
1✔
1582
            geo_transform: GdalDatasetGeoTransform {
1✔
1583
                origin_coordinate: Default::default(),
1✔
1584
                x_pixel_size: 0.0,
1✔
1585
                y_pixel_size: 0.0,
1✔
1586
            },
1✔
1587
            width: 0,
1✔
1588
            height: 0,
1✔
1589
            file_not_found_handling: FileNotFoundHandling::NoData,
1✔
1590
            no_data_value: None,
1✔
1591
            properties_mapping: None,
1✔
1592
            gdal_open_options: None,
1✔
1593
            gdal_config_options: None,
1✔
1594
            allow_alphaband_as_mask: false,
1✔
1595
            retry: None,
1✔
1596
        };
1✔
1597

1✔
1598
        let meta = StaticMetaData {
1✔
1599
            loading_info: OgrSourceDataset {
1✔
1600
                file_name: Default::default(),
1✔
1601
                layer_name: String::new(),
1✔
1602
                data_type: None,
1✔
1603
                time: Default::default(),
1✔
1604
                default_geometry: None,
1✔
1605
                columns: None,
1✔
1606
                force_ogr_time_filter: false,
1✔
1607
                force_ogr_spatial_filter: false,
1✔
1608
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1609
                sql_query: None,
1✔
1610
                attribute_query: None,
1✔
1611
                cache_ttl: CacheTtlSeconds::default(),
1✔
1612
            },
1✔
1613
            result_descriptor: vector_descriptor.clone(),
1✔
1614
            phantom: Default::default(),
1✔
1615
        };
1✔
1616

1617
        let id = db.add_dataset(vector_ds, meta.into()).await.unwrap().id;
1✔
1618

1619
        let meta: geoengine_operators::util::Result<
1✔
1620
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1621
        > = db.meta_data(&id.into()).await;
1✔
1622

1623
        assert!(meta.is_ok());
1✔
1624

1625
        let meta = GdalMetaDataRegular {
1✔
1626
            result_descriptor: raster_descriptor.clone(),
1✔
1627
            params: gdal_params.clone(),
1✔
1628
            time_placeholders: Default::default(),
1✔
1629
            data_time: Default::default(),
1✔
1630
            step: TimeStep {
1✔
1631
                granularity: TimeGranularity::Millis,
1✔
1632
                step: 0,
1✔
1633
            },
1✔
1634
            cache_ttl: CacheTtlSeconds::default(),
1✔
1635
        };
1✔
1636

1637
        let id = db
1✔
1638
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1639
            .await
1✔
1640
            .unwrap()
1✔
1641
            .id;
1642

1643
        let meta: geoengine_operators::util::Result<
1✔
1644
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1645
        > = db.meta_data(&id.into()).await;
1✔
1646

1647
        assert!(meta.is_ok());
1✔
1648

1649
        let meta = GdalMetaDataStatic {
1✔
1650
            time: None,
1✔
1651
            params: gdal_params.clone(),
1✔
1652
            result_descriptor: raster_descriptor.clone(),
1✔
1653
            cache_ttl: CacheTtlSeconds::default(),
1✔
1654
        };
1✔
1655

1656
        let id = db
1✔
1657
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1658
            .await
1✔
1659
            .unwrap()
1✔
1660
            .id;
1661

1662
        let meta: geoengine_operators::util::Result<
1✔
1663
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1664
        > = db.meta_data(&id.into()).await;
1✔
1665

1666
        assert!(meta.is_ok());
1✔
1667

1668
        let meta = GdalMetaDataList {
1✔
1669
            result_descriptor: raster_descriptor.clone(),
1✔
1670
            params: vec![],
1✔
1671
        };
1✔
1672

1673
        let id = db
1✔
1674
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1675
            .await
1✔
1676
            .unwrap()
1✔
1677
            .id;
1678

1679
        let meta: geoengine_operators::util::Result<
1✔
1680
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1681
        > = db.meta_data(&id.into()).await;
1✔
1682

1683
        assert!(meta.is_ok());
1✔
1684

1685
        let meta = GdalMetadataNetCdfCf {
1✔
1686
            result_descriptor: raster_descriptor.clone(),
1✔
1687
            params: gdal_params.clone(),
1✔
1688
            start: TimeInstance::MIN,
1✔
1689
            end: TimeInstance::MAX,
1✔
1690
            step: TimeStep {
1✔
1691
                granularity: TimeGranularity::Millis,
1✔
1692
                step: 0,
1✔
1693
            },
1✔
1694
            band_offset: 0,
1✔
1695
            cache_ttl: CacheTtlSeconds::default(),
1✔
1696
        };
1✔
1697

1698
        let id = db
1✔
1699
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1700
            .await
1✔
1701
            .unwrap()
1✔
1702
            .id;
1703

1704
        let meta: geoengine_operators::util::Result<
1✔
1705
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1706
        > = db.meta_data(&id.into()).await;
1✔
1707

1708
        assert!(meta.is_ok());
1✔
1709
    }
1✔
1710

1711
    #[ge_context::test]
1✔
1712
    async fn it_secures_uploads(app_ctx: PostgresContext<NoTls>) {
1✔
1713
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1714
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1715

1✔
1716
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1717
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1718

1✔
1719
        let upload_id = UploadId::new();
1✔
1720

1✔
1721
        let upload = Upload {
1✔
1722
            id: upload_id,
1✔
1723
            files: vec![FileUpload {
1✔
1724
                id: FileId::new(),
1✔
1725
                name: "test.bin".to_owned(),
1✔
1726
                byte_size: 1024,
1✔
1727
            }],
1✔
1728
        };
1✔
1729

1✔
1730
        db1.create_upload(upload).await.unwrap();
1✔
1731

1✔
1732
        assert!(db1.load_upload(upload_id).await.is_ok());
1✔
1733

1734
        assert!(db2.load_upload(upload_id).await.is_err());
1✔
1735
    }
1✔
1736

1737
    #[allow(clippy::too_many_lines)]
1738
    #[ge_context::test]
1✔
1739
    async fn it_collects_layers(app_ctx: PostgresContext<NoTls>) {
1✔
1740
        let session = admin_login(&app_ctx).await;
1✔
1741

1742
        let layer_db = app_ctx.session_context(session).db();
1✔
1743

1✔
1744
        let workflow = Workflow {
1✔
1745
            operator: TypedOperator::Vector(
1✔
1746
                MockPointSource {
1✔
1747
                    params: MockPointSourceParams {
1✔
1748
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1749
                    },
1✔
1750
                }
1✔
1751
                .boxed(),
1✔
1752
            ),
1✔
1753
        };
1✔
1754

1755
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1756

1757
        let layer1 = layer_db
1✔
1758
            .add_layer(
1✔
1759
                AddLayer {
1✔
1760
                    name: "Layer1".to_string(),
1✔
1761
                    description: "Layer 1".to_string(),
1✔
1762
                    symbology: None,
1✔
1763
                    workflow: workflow.clone(),
1✔
1764
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1765
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1766
                },
1✔
1767
                &root_collection_id,
1✔
1768
            )
1✔
1769
            .await
1✔
1770
            .unwrap();
1✔
1771

1772
        assert_eq!(
1✔
1773
            layer_db.load_layer(&layer1).await.unwrap(),
1✔
1774
            crate::layers::layer::Layer {
1✔
1775
                id: ProviderLayerId {
1✔
1776
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1777
                    layer_id: layer1.clone(),
1✔
1778
                },
1✔
1779
                name: "Layer1".to_string(),
1✔
1780
                description: "Layer 1".to_string(),
1✔
1781
                symbology: None,
1✔
1782
                workflow: workflow.clone(),
1✔
1783
                metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1784
                properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1785
            }
1✔
1786
        );
1787

1788
        let collection1_id = layer_db
1✔
1789
            .add_layer_collection(
1✔
1790
                AddLayerCollection {
1✔
1791
                    name: "Collection1".to_string(),
1✔
1792
                    description: "Collection 1".to_string(),
1✔
1793
                    properties: Default::default(),
1✔
1794
                },
1✔
1795
                &root_collection_id,
1✔
1796
            )
1✔
1797
            .await
1✔
1798
            .unwrap();
1✔
1799

1800
        let layer2 = layer_db
1✔
1801
            .add_layer(
1✔
1802
                AddLayer {
1✔
1803
                    name: "Layer2".to_string(),
1✔
1804
                    description: "Layer 2".to_string(),
1✔
1805
                    symbology: None,
1✔
1806
                    workflow: workflow.clone(),
1✔
1807
                    metadata: Default::default(),
1✔
1808
                    properties: Default::default(),
1✔
1809
                },
1✔
1810
                &collection1_id,
1✔
1811
            )
1✔
1812
            .await
1✔
1813
            .unwrap();
1✔
1814

1815
        let collection2_id = layer_db
1✔
1816
            .add_layer_collection(
1✔
1817
                AddLayerCollection {
1✔
1818
                    name: "Collection2".to_string(),
1✔
1819
                    description: "Collection 2".to_string(),
1✔
1820
                    properties: Default::default(),
1✔
1821
                },
1✔
1822
                &collection1_id,
1✔
1823
            )
1✔
1824
            .await
1✔
1825
            .unwrap();
1✔
1826

1✔
1827
        layer_db
1✔
1828
            .add_collection_to_parent(&collection2_id, &collection1_id)
1✔
1829
            .await
1✔
1830
            .unwrap();
1✔
1831

1832
        let root_collection = layer_db
1✔
1833
            .load_layer_collection(
1✔
1834
                &root_collection_id,
1✔
1835
                LayerCollectionListOptions {
1✔
1836
                    offset: 0,
1✔
1837
                    limit: 20,
1✔
1838
                },
1✔
1839
            )
1✔
1840
            .await
1✔
1841
            .unwrap();
1✔
1842

1✔
1843
        assert_eq!(
1✔
1844
            root_collection,
1✔
1845
            LayerCollection {
1✔
1846
                id: ProviderLayerCollectionId {
1✔
1847
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1848
                    collection_id: root_collection_id,
1✔
1849
                },
1✔
1850
                name: "Layers".to_string(),
1✔
1851
                description: "All available Geo Engine layers".to_string(),
1✔
1852
                items: vec![
1✔
1853
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1854
                        id: ProviderLayerCollectionId {
1✔
1855
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1856
                            collection_id: collection1_id.clone(),
1✔
1857
                        },
1✔
1858
                        name: "Collection1".to_string(),
1✔
1859
                        description: "Collection 1".to_string(),
1✔
1860
                        properties: Default::default(),
1✔
1861
                    }),
1✔
1862
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1863
                        id: ProviderLayerCollectionId {
1✔
1864
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1865
                            collection_id: LayerCollectionId(UNSORTED_COLLECTION_ID.to_string()),
1✔
1866
                        },
1✔
1867
                        name: "Unsorted".to_string(),
1✔
1868
                        description: "Unsorted Layers".to_string(),
1✔
1869
                        properties: Default::default(),
1✔
1870
                    }),
1✔
1871
                    CollectionItem::Layer(LayerListing {
1✔
1872
                        id: ProviderLayerId {
1✔
1873
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1874
                            layer_id: layer1,
1✔
1875
                        },
1✔
1876
                        name: "Layer1".to_string(),
1✔
1877
                        description: "Layer 1".to_string(),
1✔
1878
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1879
                    })
1✔
1880
                ],
1✔
1881
                entry_label: None,
1✔
1882
                properties: vec![],
1✔
1883
            }
1✔
1884
        );
1✔
1885

1886
        let collection1 = layer_db
1✔
1887
            .load_layer_collection(
1✔
1888
                &collection1_id,
1✔
1889
                LayerCollectionListOptions {
1✔
1890
                    offset: 0,
1✔
1891
                    limit: 20,
1✔
1892
                },
1✔
1893
            )
1✔
1894
            .await
1✔
1895
            .unwrap();
1✔
1896

1✔
1897
        assert_eq!(
1✔
1898
            collection1,
1✔
1899
            LayerCollection {
1✔
1900
                id: ProviderLayerCollectionId {
1✔
1901
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1902
                    collection_id: collection1_id,
1✔
1903
                },
1✔
1904
                name: "Collection1".to_string(),
1✔
1905
                description: "Collection 1".to_string(),
1✔
1906
                items: vec![
1✔
1907
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1908
                        id: ProviderLayerCollectionId {
1✔
1909
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1910
                            collection_id: collection2_id,
1✔
1911
                        },
1✔
1912
                        name: "Collection2".to_string(),
1✔
1913
                        description: "Collection 2".to_string(),
1✔
1914
                        properties: Default::default(),
1✔
1915
                    }),
1✔
1916
                    CollectionItem::Layer(LayerListing {
1✔
1917
                        id: ProviderLayerId {
1✔
1918
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1919
                            layer_id: layer2,
1✔
1920
                        },
1✔
1921
                        name: "Layer2".to_string(),
1✔
1922
                        description: "Layer 2".to_string(),
1✔
1923
                        properties: vec![],
1✔
1924
                    })
1✔
1925
                ],
1✔
1926
                entry_label: None,
1✔
1927
                properties: vec![],
1✔
1928
            }
1✔
1929
        );
1✔
1930
    }
1✔
1931

1932
    #[allow(clippy::too_many_lines)]
1933
    #[ge_context::test]
1✔
1934
    async fn it_searches_layers(app_ctx: PostgresContext<NoTls>) {
1✔
1935
        let session = admin_login(&app_ctx).await;
1✔
1936

1937
        let layer_db = app_ctx.session_context(session).db();
1✔
1938

1✔
1939
        let workflow = Workflow {
1✔
1940
            operator: TypedOperator::Vector(
1✔
1941
                MockPointSource {
1✔
1942
                    params: MockPointSourceParams {
1✔
1943
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1944
                    },
1✔
1945
                }
1✔
1946
                .boxed(),
1✔
1947
            ),
1✔
1948
        };
1✔
1949

1950
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1951

1952
        let layer1 = layer_db
1✔
1953
            .add_layer(
1✔
1954
                AddLayer {
1✔
1955
                    name: "Layer1".to_string(),
1✔
1956
                    description: "Layer 1".to_string(),
1✔
1957
                    symbology: None,
1✔
1958
                    workflow: workflow.clone(),
1✔
1959
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1960
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1961
                },
1✔
1962
                &root_collection_id,
1✔
1963
            )
1✔
1964
            .await
1✔
1965
            .unwrap();
1✔
1966

1967
        let collection1_id = layer_db
1✔
1968
            .add_layer_collection(
1✔
1969
                AddLayerCollection {
1✔
1970
                    name: "Collection1".to_string(),
1✔
1971
                    description: "Collection 1".to_string(),
1✔
1972
                    properties: Default::default(),
1✔
1973
                },
1✔
1974
                &root_collection_id,
1✔
1975
            )
1✔
1976
            .await
1✔
1977
            .unwrap();
1✔
1978

1979
        let layer2 = layer_db
1✔
1980
            .add_layer(
1✔
1981
                AddLayer {
1✔
1982
                    name: "Layer2".to_string(),
1✔
1983
                    description: "Layer 2".to_string(),
1✔
1984
                    symbology: None,
1✔
1985
                    workflow: workflow.clone(),
1✔
1986
                    metadata: Default::default(),
1✔
1987
                    properties: Default::default(),
1✔
1988
                },
1✔
1989
                &collection1_id,
1✔
1990
            )
1✔
1991
            .await
1✔
1992
            .unwrap();
1✔
1993

1994
        let collection2_id = layer_db
1✔
1995
            .add_layer_collection(
1✔
1996
                AddLayerCollection {
1✔
1997
                    name: "Collection2".to_string(),
1✔
1998
                    description: "Collection 2".to_string(),
1✔
1999
                    properties: Default::default(),
1✔
2000
                },
1✔
2001
                &collection1_id,
1✔
2002
            )
1✔
2003
            .await
1✔
2004
            .unwrap();
1✔
2005

2006
        let root_collection_all = layer_db
1✔
2007
            .search(
1✔
2008
                &root_collection_id,
1✔
2009
                SearchParameters {
1✔
2010
                    search_type: SearchType::Fulltext,
1✔
2011
                    search_string: String::new(),
1✔
2012
                    limit: 10,
1✔
2013
                    offset: 0,
1✔
2014
                },
1✔
2015
            )
1✔
2016
            .await
1✔
2017
            .unwrap();
1✔
2018

1✔
2019
        assert_eq!(
1✔
2020
            root_collection_all,
1✔
2021
            LayerCollection {
1✔
2022
                id: ProviderLayerCollectionId {
1✔
2023
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2024
                    collection_id: root_collection_id.clone(),
1✔
2025
                },
1✔
2026
                name: "Layers".to_string(),
1✔
2027
                description: "All available Geo Engine layers".to_string(),
1✔
2028
                items: vec![
1✔
2029
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2030
                        id: ProviderLayerCollectionId {
1✔
2031
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2032
                            collection_id: collection1_id.clone(),
1✔
2033
                        },
1✔
2034
                        name: "Collection1".to_string(),
1✔
2035
                        description: "Collection 1".to_string(),
1✔
2036
                        properties: Default::default(),
1✔
2037
                    }),
1✔
2038
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2039
                        id: ProviderLayerCollectionId {
1✔
2040
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2041
                            collection_id: collection2_id.clone(),
1✔
2042
                        },
1✔
2043
                        name: "Collection2".to_string(),
1✔
2044
                        description: "Collection 2".to_string(),
1✔
2045
                        properties: Default::default(),
1✔
2046
                    }),
1✔
2047
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2048
                        id: ProviderLayerCollectionId {
1✔
2049
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2050
                            collection_id: LayerCollectionId(
1✔
2051
                                "ffb2dd9e-f5ad-427c-b7f1-c9a0c7a0ae3f".to_string()
1✔
2052
                            ),
1✔
2053
                        },
1✔
2054
                        name: "Unsorted".to_string(),
1✔
2055
                        description: "Unsorted Layers".to_string(),
1✔
2056
                        properties: Default::default(),
1✔
2057
                    }),
1✔
2058
                    CollectionItem::Layer(LayerListing {
1✔
2059
                        id: ProviderLayerId {
1✔
2060
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2061
                            layer_id: layer1.clone(),
1✔
2062
                        },
1✔
2063
                        name: "Layer1".to_string(),
1✔
2064
                        description: "Layer 1".to_string(),
1✔
2065
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2066
                    }),
1✔
2067
                    CollectionItem::Layer(LayerListing {
1✔
2068
                        id: ProviderLayerId {
1✔
2069
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2070
                            layer_id: layer2.clone(),
1✔
2071
                        },
1✔
2072
                        name: "Layer2".to_string(),
1✔
2073
                        description: "Layer 2".to_string(),
1✔
2074
                        properties: vec![],
1✔
2075
                    }),
1✔
2076
                ],
1✔
2077
                entry_label: None,
1✔
2078
                properties: vec![],
1✔
2079
            }
1✔
2080
        );
1✔
2081

2082
        let root_collection_filtered = layer_db
1✔
2083
            .search(
1✔
2084
                &root_collection_id,
1✔
2085
                SearchParameters {
1✔
2086
                    search_type: SearchType::Fulltext,
1✔
2087
                    search_string: "lection".to_string(),
1✔
2088
                    limit: 10,
1✔
2089
                    offset: 0,
1✔
2090
                },
1✔
2091
            )
1✔
2092
            .await
1✔
2093
            .unwrap();
1✔
2094

1✔
2095
        assert_eq!(
1✔
2096
            root_collection_filtered,
1✔
2097
            LayerCollection {
1✔
2098
                id: ProviderLayerCollectionId {
1✔
2099
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2100
                    collection_id: root_collection_id.clone(),
1✔
2101
                },
1✔
2102
                name: "Layers".to_string(),
1✔
2103
                description: "All available Geo Engine layers".to_string(),
1✔
2104
                items: vec![
1✔
2105
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2106
                        id: ProviderLayerCollectionId {
1✔
2107
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2108
                            collection_id: collection1_id.clone(),
1✔
2109
                        },
1✔
2110
                        name: "Collection1".to_string(),
1✔
2111
                        description: "Collection 1".to_string(),
1✔
2112
                        properties: Default::default(),
1✔
2113
                    }),
1✔
2114
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2115
                        id: ProviderLayerCollectionId {
1✔
2116
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2117
                            collection_id: collection2_id.clone(),
1✔
2118
                        },
1✔
2119
                        name: "Collection2".to_string(),
1✔
2120
                        description: "Collection 2".to_string(),
1✔
2121
                        properties: Default::default(),
1✔
2122
                    }),
1✔
2123
                ],
1✔
2124
                entry_label: None,
1✔
2125
                properties: vec![],
1✔
2126
            }
1✔
2127
        );
1✔
2128

2129
        let collection1_all = layer_db
1✔
2130
            .search(
1✔
2131
                &collection1_id,
1✔
2132
                SearchParameters {
1✔
2133
                    search_type: SearchType::Fulltext,
1✔
2134
                    search_string: String::new(),
1✔
2135
                    limit: 10,
1✔
2136
                    offset: 0,
1✔
2137
                },
1✔
2138
            )
1✔
2139
            .await
1✔
2140
            .unwrap();
1✔
2141

1✔
2142
        assert_eq!(
1✔
2143
            collection1_all,
1✔
2144
            LayerCollection {
1✔
2145
                id: ProviderLayerCollectionId {
1✔
2146
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2147
                    collection_id: collection1_id.clone(),
1✔
2148
                },
1✔
2149
                name: "Collection1".to_string(),
1✔
2150
                description: "Collection 1".to_string(),
1✔
2151
                items: vec![
1✔
2152
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2153
                        id: ProviderLayerCollectionId {
1✔
2154
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2155
                            collection_id: collection2_id.clone(),
1✔
2156
                        },
1✔
2157
                        name: "Collection2".to_string(),
1✔
2158
                        description: "Collection 2".to_string(),
1✔
2159
                        properties: Default::default(),
1✔
2160
                    }),
1✔
2161
                    CollectionItem::Layer(LayerListing {
1✔
2162
                        id: ProviderLayerId {
1✔
2163
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2164
                            layer_id: layer2.clone(),
1✔
2165
                        },
1✔
2166
                        name: "Layer2".to_string(),
1✔
2167
                        description: "Layer 2".to_string(),
1✔
2168
                        properties: vec![],
1✔
2169
                    }),
1✔
2170
                ],
1✔
2171
                entry_label: None,
1✔
2172
                properties: vec![],
1✔
2173
            }
1✔
2174
        );
1✔
2175

2176
        let collection1_filtered_fulltext = layer_db
1✔
2177
            .search(
1✔
2178
                &collection1_id,
1✔
2179
                SearchParameters {
1✔
2180
                    search_type: SearchType::Fulltext,
1✔
2181
                    search_string: "ay".to_string(),
1✔
2182
                    limit: 10,
1✔
2183
                    offset: 0,
1✔
2184
                },
1✔
2185
            )
1✔
2186
            .await
1✔
2187
            .unwrap();
1✔
2188

1✔
2189
        assert_eq!(
1✔
2190
            collection1_filtered_fulltext,
1✔
2191
            LayerCollection {
1✔
2192
                id: ProviderLayerCollectionId {
1✔
2193
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2194
                    collection_id: collection1_id.clone(),
1✔
2195
                },
1✔
2196
                name: "Collection1".to_string(),
1✔
2197
                description: "Collection 1".to_string(),
1✔
2198
                items: vec![CollectionItem::Layer(LayerListing {
1✔
2199
                    id: ProviderLayerId {
1✔
2200
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
2201
                        layer_id: layer2.clone(),
1✔
2202
                    },
1✔
2203
                    name: "Layer2".to_string(),
1✔
2204
                    description: "Layer 2".to_string(),
1✔
2205
                    properties: vec![],
1✔
2206
                }),],
1✔
2207
                entry_label: None,
1✔
2208
                properties: vec![],
1✔
2209
            }
1✔
2210
        );
1✔
2211

2212
        let collection1_filtered_prefix = layer_db
1✔
2213
            .search(
1✔
2214
                &collection1_id,
1✔
2215
                SearchParameters {
1✔
2216
                    search_type: SearchType::Prefix,
1✔
2217
                    search_string: "ay".to_string(),
1✔
2218
                    limit: 10,
1✔
2219
                    offset: 0,
1✔
2220
                },
1✔
2221
            )
1✔
2222
            .await
1✔
2223
            .unwrap();
1✔
2224

1✔
2225
        assert_eq!(
1✔
2226
            collection1_filtered_prefix,
1✔
2227
            LayerCollection {
1✔
2228
                id: ProviderLayerCollectionId {
1✔
2229
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2230
                    collection_id: collection1_id.clone(),
1✔
2231
                },
1✔
2232
                name: "Collection1".to_string(),
1✔
2233
                description: "Collection 1".to_string(),
1✔
2234
                items: vec![],
1✔
2235
                entry_label: None,
1✔
2236
                properties: vec![],
1✔
2237
            }
1✔
2238
        );
1✔
2239

2240
        let collection1_filtered_prefix2 = layer_db
1✔
2241
            .search(
1✔
2242
                &collection1_id,
1✔
2243
                SearchParameters {
1✔
2244
                    search_type: SearchType::Prefix,
1✔
2245
                    search_string: "Lay".to_string(),
1✔
2246
                    limit: 10,
1✔
2247
                    offset: 0,
1✔
2248
                },
1✔
2249
            )
1✔
2250
            .await
1✔
2251
            .unwrap();
1✔
2252

1✔
2253
        assert_eq!(
1✔
2254
            collection1_filtered_prefix2,
1✔
2255
            LayerCollection {
1✔
2256
                id: ProviderLayerCollectionId {
1✔
2257
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2258
                    collection_id: collection1_id.clone(),
1✔
2259
                },
1✔
2260
                name: "Collection1".to_string(),
1✔
2261
                description: "Collection 1".to_string(),
1✔
2262
                items: vec![CollectionItem::Layer(LayerListing {
1✔
2263
                    id: ProviderLayerId {
1✔
2264
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
2265
                        layer_id: layer2.clone(),
1✔
2266
                    },
1✔
2267
                    name: "Layer2".to_string(),
1✔
2268
                    description: "Layer 2".to_string(),
1✔
2269
                    properties: vec![],
1✔
2270
                }),],
1✔
2271
                entry_label: None,
1✔
2272
                properties: vec![],
1✔
2273
            }
1✔
2274
        );
1✔
2275
    }
1✔
2276

2277
    #[allow(clippy::too_many_lines)]
2278
    #[ge_context::test]
1✔
2279
    async fn it_searches_layers_with_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
2280
        let admin_session = admin_login(&app_ctx).await;
1✔
2281
        let admin_layer_db = app_ctx.session_context(admin_session).db();
1✔
2282

2283
        let user_session = app_ctx.create_anonymous_session().await.unwrap();
1✔
2284
        let user_layer_db = app_ctx.session_context(user_session.clone()).db();
1✔
2285

1✔
2286
        let workflow = Workflow {
1✔
2287
            operator: TypedOperator::Vector(
1✔
2288
                MockPointSource {
1✔
2289
                    params: MockPointSourceParams {
1✔
2290
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2291
                    },
1✔
2292
                }
1✔
2293
                .boxed(),
1✔
2294
            ),
1✔
2295
        };
1✔
2296

2297
        let root_collection_id = admin_layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2298

2299
        let layer1 = admin_layer_db
1✔
2300
            .add_layer(
1✔
2301
                AddLayer {
1✔
2302
                    name: "Layer1".to_string(),
1✔
2303
                    description: "Layer 1".to_string(),
1✔
2304
                    symbology: None,
1✔
2305
                    workflow: workflow.clone(),
1✔
2306
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
2307
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2308
                },
1✔
2309
                &root_collection_id,
1✔
2310
            )
1✔
2311
            .await
1✔
2312
            .unwrap();
1✔
2313

2314
        let collection1_id = admin_layer_db
1✔
2315
            .add_layer_collection(
1✔
2316
                AddLayerCollection {
1✔
2317
                    name: "Collection1".to_string(),
1✔
2318
                    description: "Collection 1".to_string(),
1✔
2319
                    properties: Default::default(),
1✔
2320
                },
1✔
2321
                &root_collection_id,
1✔
2322
            )
1✔
2323
            .await
1✔
2324
            .unwrap();
1✔
2325

2326
        let layer2 = admin_layer_db
1✔
2327
            .add_layer(
1✔
2328
                AddLayer {
1✔
2329
                    name: "Layer2".to_string(),
1✔
2330
                    description: "Layer 2".to_string(),
1✔
2331
                    symbology: None,
1✔
2332
                    workflow: workflow.clone(),
1✔
2333
                    metadata: Default::default(),
1✔
2334
                    properties: Default::default(),
1✔
2335
                },
1✔
2336
                &collection1_id,
1✔
2337
            )
1✔
2338
            .await
1✔
2339
            .unwrap();
1✔
2340

2341
        let collection2_id = admin_layer_db
1✔
2342
            .add_layer_collection(
1✔
2343
                AddLayerCollection {
1✔
2344
                    name: "Collection2".to_string(),
1✔
2345
                    description: "Collection 2".to_string(),
1✔
2346
                    properties: Default::default(),
1✔
2347
                },
1✔
2348
                &collection1_id,
1✔
2349
            )
1✔
2350
            .await
1✔
2351
            .unwrap();
1✔
2352

2353
        let collection3_id = admin_layer_db
1✔
2354
            .add_layer_collection(
1✔
2355
                AddLayerCollection {
1✔
2356
                    name: "Collection3".to_string(),
1✔
2357
                    description: "Collection 3".to_string(),
1✔
2358
                    properties: Default::default(),
1✔
2359
                },
1✔
2360
                &collection1_id,
1✔
2361
            )
1✔
2362
            .await
1✔
2363
            .unwrap();
1✔
2364

2365
        let layer3 = admin_layer_db
1✔
2366
            .add_layer(
1✔
2367
                AddLayer {
1✔
2368
                    name: "Layer3".to_string(),
1✔
2369
                    description: "Layer 3".to_string(),
1✔
2370
                    symbology: None,
1✔
2371
                    workflow: workflow.clone(),
1✔
2372
                    metadata: Default::default(),
1✔
2373
                    properties: Default::default(),
1✔
2374
                },
1✔
2375
                &collection2_id,
1✔
2376
            )
1✔
2377
            .await
1✔
2378
            .unwrap();
1✔
2379

1✔
2380
        // Grant user permissions for collection1, collection2, layer1 and layer2
1✔
2381
        admin_layer_db
1✔
2382
            .add_permission(
1✔
2383
                user_session.user.id.into(),
1✔
2384
                collection1_id.clone(),
1✔
2385
                Permission::Read,
1✔
2386
            )
1✔
2387
            .await
1✔
2388
            .unwrap();
1✔
2389

1✔
2390
        admin_layer_db
1✔
2391
            .add_permission(
1✔
2392
                user_session.user.id.into(),
1✔
2393
                collection2_id.clone(),
1✔
2394
                Permission::Read,
1✔
2395
            )
1✔
2396
            .await
1✔
2397
            .unwrap();
1✔
2398

1✔
2399
        admin_layer_db
1✔
2400
            .add_permission(
1✔
2401
                user_session.user.id.into(),
1✔
2402
                layer1.clone(),
1✔
2403
                Permission::Read,
1✔
2404
            )
1✔
2405
            .await
1✔
2406
            .unwrap();
1✔
2407

1✔
2408
        admin_layer_db
1✔
2409
            .add_permission(
1✔
2410
                user_session.user.id.into(),
1✔
2411
                layer2.clone(),
1✔
2412
                Permission::Read,
1✔
2413
            )
1✔
2414
            .await
1✔
2415
            .unwrap();
1✔
2416

2417
        // Ensure admin sees everything we added
2418
        let admin_root_collection_all = admin_layer_db
1✔
2419
            .search(
1✔
2420
                &root_collection_id,
1✔
2421
                SearchParameters {
1✔
2422
                    search_type: SearchType::Fulltext,
1✔
2423
                    search_string: String::new(),
1✔
2424
                    limit: 10,
1✔
2425
                    offset: 0,
1✔
2426
                },
1✔
2427
            )
1✔
2428
            .await
1✔
2429
            .unwrap();
1✔
2430

1✔
2431
        assert_eq!(
1✔
2432
            admin_root_collection_all,
1✔
2433
            LayerCollection {
1✔
2434
                id: ProviderLayerCollectionId {
1✔
2435
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2436
                    collection_id: root_collection_id.clone(),
1✔
2437
                },
1✔
2438
                name: "Layers".to_string(),
1✔
2439
                description: "All available Geo Engine layers".to_string(),
1✔
2440
                items: vec![
1✔
2441
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2442
                        id: ProviderLayerCollectionId {
1✔
2443
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2444
                            collection_id: collection1_id.clone(),
1✔
2445
                        },
1✔
2446
                        name: "Collection1".to_string(),
1✔
2447
                        description: "Collection 1".to_string(),
1✔
2448
                        properties: Default::default(),
1✔
2449
                    }),
1✔
2450
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2451
                        id: ProviderLayerCollectionId {
1✔
2452
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2453
                            collection_id: collection2_id.clone(),
1✔
2454
                        },
1✔
2455
                        name: "Collection2".to_string(),
1✔
2456
                        description: "Collection 2".to_string(),
1✔
2457
                        properties: Default::default(),
1✔
2458
                    }),
1✔
2459
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2460
                        id: ProviderLayerCollectionId {
1✔
2461
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2462
                            collection_id: collection3_id.clone(),
1✔
2463
                        },
1✔
2464
                        name: "Collection3".to_string(),
1✔
2465
                        description: "Collection 3".to_string(),
1✔
2466
                        properties: Default::default(),
1✔
2467
                    }),
1✔
2468
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2469
                        id: ProviderLayerCollectionId {
1✔
2470
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2471
                            collection_id: LayerCollectionId(
1✔
2472
                                "ffb2dd9e-f5ad-427c-b7f1-c9a0c7a0ae3f".to_string()
1✔
2473
                            ),
1✔
2474
                        },
1✔
2475
                        name: "Unsorted".to_string(),
1✔
2476
                        description: "Unsorted Layers".to_string(),
1✔
2477
                        properties: Default::default(),
1✔
2478
                    }),
1✔
2479
                    CollectionItem::Layer(LayerListing {
1✔
2480
                        id: ProviderLayerId {
1✔
2481
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2482
                            layer_id: layer1.clone(),
1✔
2483
                        },
1✔
2484
                        name: "Layer1".to_string(),
1✔
2485
                        description: "Layer 1".to_string(),
1✔
2486
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2487
                    }),
1✔
2488
                    CollectionItem::Layer(LayerListing {
1✔
2489
                        id: ProviderLayerId {
1✔
2490
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2491
                            layer_id: layer2.clone(),
1✔
2492
                        },
1✔
2493
                        name: "Layer2".to_string(),
1✔
2494
                        description: "Layer 2".to_string(),
1✔
2495
                        properties: vec![],
1✔
2496
                    }),
1✔
2497
                    CollectionItem::Layer(LayerListing {
1✔
2498
                        id: ProviderLayerId {
1✔
2499
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2500
                            layer_id: layer3.clone(),
1✔
2501
                        },
1✔
2502
                        name: "Layer3".to_string(),
1✔
2503
                        description: "Layer 3".to_string(),
1✔
2504
                        properties: vec![],
1✔
2505
                    }),
1✔
2506
                ],
1✔
2507
                entry_label: None,
1✔
2508
                properties: vec![],
1✔
2509
            }
1✔
2510
        );
1✔
2511

2512
        let root_collection_all = user_layer_db
1✔
2513
            .search(
1✔
2514
                &root_collection_id,
1✔
2515
                SearchParameters {
1✔
2516
                    search_type: SearchType::Fulltext,
1✔
2517
                    search_string: String::new(),
1✔
2518
                    limit: 10,
1✔
2519
                    offset: 0,
1✔
2520
                },
1✔
2521
            )
1✔
2522
            .await
1✔
2523
            .unwrap();
1✔
2524

1✔
2525
        assert_eq!(
1✔
2526
            root_collection_all,
1✔
2527
            LayerCollection {
1✔
2528
                id: ProviderLayerCollectionId {
1✔
2529
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2530
                    collection_id: root_collection_id.clone(),
1✔
2531
                },
1✔
2532
                name: "Layers".to_string(),
1✔
2533
                description: "All available Geo Engine layers".to_string(),
1✔
2534
                items: vec![
1✔
2535
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2536
                        id: ProviderLayerCollectionId {
1✔
2537
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2538
                            collection_id: collection1_id.clone(),
1✔
2539
                        },
1✔
2540
                        name: "Collection1".to_string(),
1✔
2541
                        description: "Collection 1".to_string(),
1✔
2542
                        properties: Default::default(),
1✔
2543
                    }),
1✔
2544
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2545
                        id: ProviderLayerCollectionId {
1✔
2546
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2547
                            collection_id: collection2_id.clone(),
1✔
2548
                        },
1✔
2549
                        name: "Collection2".to_string(),
1✔
2550
                        description: "Collection 2".to_string(),
1✔
2551
                        properties: Default::default(),
1✔
2552
                    }),
1✔
2553
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2554
                        id: ProviderLayerCollectionId {
1✔
2555
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2556
                            collection_id: LayerCollectionId(
1✔
2557
                                "ffb2dd9e-f5ad-427c-b7f1-c9a0c7a0ae3f".to_string()
1✔
2558
                            ),
1✔
2559
                        },
1✔
2560
                        name: "Unsorted".to_string(),
1✔
2561
                        description: "Unsorted Layers".to_string(),
1✔
2562
                        properties: Default::default(),
1✔
2563
                    }),
1✔
2564
                    CollectionItem::Layer(LayerListing {
1✔
2565
                        id: ProviderLayerId {
1✔
2566
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2567
                            layer_id: layer1.clone(),
1✔
2568
                        },
1✔
2569
                        name: "Layer1".to_string(),
1✔
2570
                        description: "Layer 1".to_string(),
1✔
2571
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2572
                    }),
1✔
2573
                    CollectionItem::Layer(LayerListing {
1✔
2574
                        id: ProviderLayerId {
1✔
2575
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2576
                            layer_id: layer2.clone(),
1✔
2577
                        },
1✔
2578
                        name: "Layer2".to_string(),
1✔
2579
                        description: "Layer 2".to_string(),
1✔
2580
                        properties: vec![],
1✔
2581
                    }),
1✔
2582
                ],
1✔
2583
                entry_label: None,
1✔
2584
                properties: vec![],
1✔
2585
            }
1✔
2586
        );
1✔
2587

2588
        let root_collection_filtered = user_layer_db
1✔
2589
            .search(
1✔
2590
                &root_collection_id,
1✔
2591
                SearchParameters {
1✔
2592
                    search_type: SearchType::Fulltext,
1✔
2593
                    search_string: "lection".to_string(),
1✔
2594
                    limit: 10,
1✔
2595
                    offset: 0,
1✔
2596
                },
1✔
2597
            )
1✔
2598
            .await
1✔
2599
            .unwrap();
1✔
2600

1✔
2601
        assert_eq!(
1✔
2602
            root_collection_filtered,
1✔
2603
            LayerCollection {
1✔
2604
                id: ProviderLayerCollectionId {
1✔
2605
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2606
                    collection_id: root_collection_id.clone(),
1✔
2607
                },
1✔
2608
                name: "Layers".to_string(),
1✔
2609
                description: "All available Geo Engine layers".to_string(),
1✔
2610
                items: vec![
1✔
2611
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2612
                        id: ProviderLayerCollectionId {
1✔
2613
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2614
                            collection_id: collection1_id.clone(),
1✔
2615
                        },
1✔
2616
                        name: "Collection1".to_string(),
1✔
2617
                        description: "Collection 1".to_string(),
1✔
2618
                        properties: Default::default(),
1✔
2619
                    }),
1✔
2620
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2621
                        id: ProviderLayerCollectionId {
1✔
2622
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2623
                            collection_id: collection2_id.clone(),
1✔
2624
                        },
1✔
2625
                        name: "Collection2".to_string(),
1✔
2626
                        description: "Collection 2".to_string(),
1✔
2627
                        properties: Default::default(),
1✔
2628
                    }),
1✔
2629
                ],
1✔
2630
                entry_label: None,
1✔
2631
                properties: vec![],
1✔
2632
            }
1✔
2633
        );
1✔
2634

2635
        let collection1_all = user_layer_db
1✔
2636
            .search(
1✔
2637
                &collection1_id,
1✔
2638
                SearchParameters {
1✔
2639
                    search_type: SearchType::Fulltext,
1✔
2640
                    search_string: String::new(),
1✔
2641
                    limit: 10,
1✔
2642
                    offset: 0,
1✔
2643
                },
1✔
2644
            )
1✔
2645
            .await
1✔
2646
            .unwrap();
1✔
2647

1✔
2648
        assert_eq!(
1✔
2649
            collection1_all,
1✔
2650
            LayerCollection {
1✔
2651
                id: ProviderLayerCollectionId {
1✔
2652
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2653
                    collection_id: collection1_id.clone(),
1✔
2654
                },
1✔
2655
                name: "Collection1".to_string(),
1✔
2656
                description: "Collection 1".to_string(),
1✔
2657
                items: vec![
1✔
2658
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2659
                        id: ProviderLayerCollectionId {
1✔
2660
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2661
                            collection_id: collection2_id.clone(),
1✔
2662
                        },
1✔
2663
                        name: "Collection2".to_string(),
1✔
2664
                        description: "Collection 2".to_string(),
1✔
2665
                        properties: Default::default(),
1✔
2666
                    }),
1✔
2667
                    CollectionItem::Layer(LayerListing {
1✔
2668
                        id: ProviderLayerId {
1✔
2669
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2670
                            layer_id: layer2.clone(),
1✔
2671
                        },
1✔
2672
                        name: "Layer2".to_string(),
1✔
2673
                        description: "Layer 2".to_string(),
1✔
2674
                        properties: vec![],
1✔
2675
                    }),
1✔
2676
                ],
1✔
2677
                entry_label: None,
1✔
2678
                properties: vec![],
1✔
2679
            }
1✔
2680
        );
1✔
2681

2682
        let collection1_filtered_fulltext = user_layer_db
1✔
2683
            .search(
1✔
2684
                &collection1_id,
1✔
2685
                SearchParameters {
1✔
2686
                    search_type: SearchType::Fulltext,
1✔
2687
                    search_string: "ay".to_string(),
1✔
2688
                    limit: 10,
1✔
2689
                    offset: 0,
1✔
2690
                },
1✔
2691
            )
1✔
2692
            .await
1✔
2693
            .unwrap();
1✔
2694

1✔
2695
        assert_eq!(
1✔
2696
            collection1_filtered_fulltext,
1✔
2697
            LayerCollection {
1✔
2698
                id: ProviderLayerCollectionId {
1✔
2699
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2700
                    collection_id: collection1_id.clone(),
1✔
2701
                },
1✔
2702
                name: "Collection1".to_string(),
1✔
2703
                description: "Collection 1".to_string(),
1✔
2704
                items: vec![CollectionItem::Layer(LayerListing {
1✔
2705
                    id: ProviderLayerId {
1✔
2706
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
2707
                        layer_id: layer2.clone(),
1✔
2708
                    },
1✔
2709
                    name: "Layer2".to_string(),
1✔
2710
                    description: "Layer 2".to_string(),
1✔
2711
                    properties: vec![],
1✔
2712
                }),],
1✔
2713
                entry_label: None,
1✔
2714
                properties: vec![],
1✔
2715
            }
1✔
2716
        );
1✔
2717

2718
        let collection1_filtered_prefix = user_layer_db
1✔
2719
            .search(
1✔
2720
                &collection1_id,
1✔
2721
                SearchParameters {
1✔
2722
                    search_type: SearchType::Prefix,
1✔
2723
                    search_string: "ay".to_string(),
1✔
2724
                    limit: 10,
1✔
2725
                    offset: 0,
1✔
2726
                },
1✔
2727
            )
1✔
2728
            .await
1✔
2729
            .unwrap();
1✔
2730

1✔
2731
        assert_eq!(
1✔
2732
            collection1_filtered_prefix,
1✔
2733
            LayerCollection {
1✔
2734
                id: ProviderLayerCollectionId {
1✔
2735
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2736
                    collection_id: collection1_id.clone(),
1✔
2737
                },
1✔
2738
                name: "Collection1".to_string(),
1✔
2739
                description: "Collection 1".to_string(),
1✔
2740
                items: vec![],
1✔
2741
                entry_label: None,
1✔
2742
                properties: vec![],
1✔
2743
            }
1✔
2744
        );
1✔
2745

2746
        let collection1_filtered_prefix2 = user_layer_db
1✔
2747
            .search(
1✔
2748
                &collection1_id,
1✔
2749
                SearchParameters {
1✔
2750
                    search_type: SearchType::Prefix,
1✔
2751
                    search_string: "Lay".to_string(),
1✔
2752
                    limit: 10,
1✔
2753
                    offset: 0,
1✔
2754
                },
1✔
2755
            )
1✔
2756
            .await
1✔
2757
            .unwrap();
1✔
2758

1✔
2759
        assert_eq!(
1✔
2760
            collection1_filtered_prefix2,
1✔
2761
            LayerCollection {
1✔
2762
                id: ProviderLayerCollectionId {
1✔
2763
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2764
                    collection_id: collection1_id.clone(),
1✔
2765
                },
1✔
2766
                name: "Collection1".to_string(),
1✔
2767
                description: "Collection 1".to_string(),
1✔
2768
                items: vec![CollectionItem::Layer(LayerListing {
1✔
2769
                    id: ProviderLayerId {
1✔
2770
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
2771
                        layer_id: layer2.clone(),
1✔
2772
                    },
1✔
2773
                    name: "Layer2".to_string(),
1✔
2774
                    description: "Layer 2".to_string(),
1✔
2775
                    properties: vec![],
1✔
2776
                }),],
1✔
2777
                entry_label: None,
1✔
2778
                properties: vec![],
1✔
2779
            }
1✔
2780
        );
1✔
2781
    }
1✔
2782

2783
    #[allow(clippy::too_many_lines)]
2784
    #[ge_context::test]
1✔
2785
    async fn it_autocompletes_layers(app_ctx: PostgresContext<NoTls>) {
1✔
2786
        let session = admin_login(&app_ctx).await;
1✔
2787

2788
        let layer_db = app_ctx.session_context(session).db();
1✔
2789

1✔
2790
        let workflow = Workflow {
1✔
2791
            operator: TypedOperator::Vector(
1✔
2792
                MockPointSource {
1✔
2793
                    params: MockPointSourceParams {
1✔
2794
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2795
                    },
1✔
2796
                }
1✔
2797
                .boxed(),
1✔
2798
            ),
1✔
2799
        };
1✔
2800

2801
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2802

2803
        let _layer1 = layer_db
1✔
2804
            .add_layer(
1✔
2805
                AddLayer {
1✔
2806
                    name: "Layer1".to_string(),
1✔
2807
                    description: "Layer 1".to_string(),
1✔
2808
                    symbology: None,
1✔
2809
                    workflow: workflow.clone(),
1✔
2810
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
2811
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2812
                },
1✔
2813
                &root_collection_id,
1✔
2814
            )
1✔
2815
            .await
1✔
2816
            .unwrap();
1✔
2817

2818
        let collection1_id = layer_db
1✔
2819
            .add_layer_collection(
1✔
2820
                AddLayerCollection {
1✔
2821
                    name: "Collection1".to_string(),
1✔
2822
                    description: "Collection 1".to_string(),
1✔
2823
                    properties: Default::default(),
1✔
2824
                },
1✔
2825
                &root_collection_id,
1✔
2826
            )
1✔
2827
            .await
1✔
2828
            .unwrap();
1✔
2829

2830
        let _layer2 = layer_db
1✔
2831
            .add_layer(
1✔
2832
                AddLayer {
1✔
2833
                    name: "Layer2".to_string(),
1✔
2834
                    description: "Layer 2".to_string(),
1✔
2835
                    symbology: None,
1✔
2836
                    workflow: workflow.clone(),
1✔
2837
                    metadata: Default::default(),
1✔
2838
                    properties: Default::default(),
1✔
2839
                },
1✔
2840
                &collection1_id,
1✔
2841
            )
1✔
2842
            .await
1✔
2843
            .unwrap();
1✔
2844

2845
        let _collection2_id = layer_db
1✔
2846
            .add_layer_collection(
1✔
2847
                AddLayerCollection {
1✔
2848
                    name: "Collection2".to_string(),
1✔
2849
                    description: "Collection 2".to_string(),
1✔
2850
                    properties: Default::default(),
1✔
2851
                },
1✔
2852
                &collection1_id,
1✔
2853
            )
1✔
2854
            .await
1✔
2855
            .unwrap();
1✔
2856

2857
        let root_collection_all = layer_db
1✔
2858
            .autocomplete_search(
1✔
2859
                &root_collection_id,
1✔
2860
                SearchParameters {
1✔
2861
                    search_type: SearchType::Fulltext,
1✔
2862
                    search_string: String::new(),
1✔
2863
                    limit: 10,
1✔
2864
                    offset: 0,
1✔
2865
                },
1✔
2866
            )
1✔
2867
            .await
1✔
2868
            .unwrap();
1✔
2869

1✔
2870
        assert_eq!(
1✔
2871
            root_collection_all,
1✔
2872
            vec![
1✔
2873
                "Collection1".to_string(),
1✔
2874
                "Collection2".to_string(),
1✔
2875
                "Layer1".to_string(),
1✔
2876
                "Layer2".to_string(),
1✔
2877
                "Unsorted".to_string(),
1✔
2878
            ]
1✔
2879
        );
1✔
2880

2881
        let root_collection_filtered = layer_db
1✔
2882
            .autocomplete_search(
1✔
2883
                &root_collection_id,
1✔
2884
                SearchParameters {
1✔
2885
                    search_type: SearchType::Fulltext,
1✔
2886
                    search_string: "lection".to_string(),
1✔
2887
                    limit: 10,
1✔
2888
                    offset: 0,
1✔
2889
                },
1✔
2890
            )
1✔
2891
            .await
1✔
2892
            .unwrap();
1✔
2893

1✔
2894
        assert_eq!(
1✔
2895
            root_collection_filtered,
1✔
2896
            vec!["Collection1".to_string(), "Collection2".to_string(),]
1✔
2897
        );
1✔
2898

2899
        let collection1_all = layer_db
1✔
2900
            .autocomplete_search(
1✔
2901
                &collection1_id,
1✔
2902
                SearchParameters {
1✔
2903
                    search_type: SearchType::Fulltext,
1✔
2904
                    search_string: String::new(),
1✔
2905
                    limit: 10,
1✔
2906
                    offset: 0,
1✔
2907
                },
1✔
2908
            )
1✔
2909
            .await
1✔
2910
            .unwrap();
1✔
2911

1✔
2912
        assert_eq!(
1✔
2913
            collection1_all,
1✔
2914
            vec!["Collection2".to_string(), "Layer2".to_string(),]
1✔
2915
        );
1✔
2916

2917
        let collection1_filtered_fulltext = layer_db
1✔
2918
            .autocomplete_search(
1✔
2919
                &collection1_id,
1✔
2920
                SearchParameters {
1✔
2921
                    search_type: SearchType::Fulltext,
1✔
2922
                    search_string: "ay".to_string(),
1✔
2923
                    limit: 10,
1✔
2924
                    offset: 0,
1✔
2925
                },
1✔
2926
            )
1✔
2927
            .await
1✔
2928
            .unwrap();
1✔
2929

1✔
2930
        assert_eq!(collection1_filtered_fulltext, vec!["Layer2".to_string(),]);
1✔
2931

2932
        let collection1_filtered_prefix = layer_db
1✔
2933
            .autocomplete_search(
1✔
2934
                &collection1_id,
1✔
2935
                SearchParameters {
1✔
2936
                    search_type: SearchType::Prefix,
1✔
2937
                    search_string: "ay".to_string(),
1✔
2938
                    limit: 10,
1✔
2939
                    offset: 0,
1✔
2940
                },
1✔
2941
            )
1✔
2942
            .await
1✔
2943
            .unwrap();
1✔
2944

1✔
2945
        assert_eq!(collection1_filtered_prefix, Vec::<String>::new());
1✔
2946

2947
        let collection1_filtered_prefix2 = layer_db
1✔
2948
            .autocomplete_search(
1✔
2949
                &collection1_id,
1✔
2950
                SearchParameters {
1✔
2951
                    search_type: SearchType::Prefix,
1✔
2952
                    search_string: "Lay".to_string(),
1✔
2953
                    limit: 10,
1✔
2954
                    offset: 0,
1✔
2955
                },
1✔
2956
            )
1✔
2957
            .await
1✔
2958
            .unwrap();
1✔
2959

1✔
2960
        assert_eq!(collection1_filtered_prefix2, vec!["Layer2".to_string(),]);
1✔
2961
    }
1✔
2962

2963
    #[allow(clippy::too_many_lines)]
2964
    #[ge_context::test]
1✔
2965
    async fn it_autocompletes_layers_with_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
2966
        let admin_session = admin_login(&app_ctx).await;
1✔
2967
        let admin_layer_db = app_ctx.session_context(admin_session).db();
1✔
2968

2969
        let user_session = app_ctx.create_anonymous_session().await.unwrap();
1✔
2970
        let user_layer_db = app_ctx.session_context(user_session.clone()).db();
1✔
2971

1✔
2972
        let workflow = Workflow {
1✔
2973
            operator: TypedOperator::Vector(
1✔
2974
                MockPointSource {
1✔
2975
                    params: MockPointSourceParams {
1✔
2976
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2977
                    },
1✔
2978
                }
1✔
2979
                .boxed(),
1✔
2980
            ),
1✔
2981
        };
1✔
2982

2983
        let root_collection_id = admin_layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2984

2985
        let layer1 = admin_layer_db
1✔
2986
            .add_layer(
1✔
2987
                AddLayer {
1✔
2988
                    name: "Layer1".to_string(),
1✔
2989
                    description: "Layer 1".to_string(),
1✔
2990
                    symbology: None,
1✔
2991
                    workflow: workflow.clone(),
1✔
2992
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
2993
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2994
                },
1✔
2995
                &root_collection_id,
1✔
2996
            )
1✔
2997
            .await
1✔
2998
            .unwrap();
1✔
2999

3000
        let collection1_id = admin_layer_db
1✔
3001
            .add_layer_collection(
1✔
3002
                AddLayerCollection {
1✔
3003
                    name: "Collection1".to_string(),
1✔
3004
                    description: "Collection 1".to_string(),
1✔
3005
                    properties: Default::default(),
1✔
3006
                },
1✔
3007
                &root_collection_id,
1✔
3008
            )
1✔
3009
            .await
1✔
3010
            .unwrap();
1✔
3011

3012
        let layer2 = admin_layer_db
1✔
3013
            .add_layer(
1✔
3014
                AddLayer {
1✔
3015
                    name: "Layer2".to_string(),
1✔
3016
                    description: "Layer 2".to_string(),
1✔
3017
                    symbology: None,
1✔
3018
                    workflow: workflow.clone(),
1✔
3019
                    metadata: Default::default(),
1✔
3020
                    properties: Default::default(),
1✔
3021
                },
1✔
3022
                &collection1_id,
1✔
3023
            )
1✔
3024
            .await
1✔
3025
            .unwrap();
1✔
3026

3027
        let collection2_id = admin_layer_db
1✔
3028
            .add_layer_collection(
1✔
3029
                AddLayerCollection {
1✔
3030
                    name: "Collection2".to_string(),
1✔
3031
                    description: "Collection 2".to_string(),
1✔
3032
                    properties: Default::default(),
1✔
3033
                },
1✔
3034
                &collection1_id,
1✔
3035
            )
1✔
3036
            .await
1✔
3037
            .unwrap();
1✔
3038

3039
        let _collection3_id = admin_layer_db
1✔
3040
            .add_layer_collection(
1✔
3041
                AddLayerCollection {
1✔
3042
                    name: "Collection3".to_string(),
1✔
3043
                    description: "Collection 3".to_string(),
1✔
3044
                    properties: Default::default(),
1✔
3045
                },
1✔
3046
                &collection1_id,
1✔
3047
            )
1✔
3048
            .await
1✔
3049
            .unwrap();
1✔
3050

3051
        let _layer3 = admin_layer_db
1✔
3052
            .add_layer(
1✔
3053
                AddLayer {
1✔
3054
                    name: "Layer3".to_string(),
1✔
3055
                    description: "Layer 3".to_string(),
1✔
3056
                    symbology: None,
1✔
3057
                    workflow: workflow.clone(),
1✔
3058
                    metadata: Default::default(),
1✔
3059
                    properties: Default::default(),
1✔
3060
                },
1✔
3061
                &collection2_id,
1✔
3062
            )
1✔
3063
            .await
1✔
3064
            .unwrap();
1✔
3065

1✔
3066
        // Grant user permissions for collection1, collection2, layer1 and layer2
1✔
3067
        admin_layer_db
1✔
3068
            .add_permission(
1✔
3069
                user_session.user.id.into(),
1✔
3070
                collection1_id.clone(),
1✔
3071
                Permission::Read,
1✔
3072
            )
1✔
3073
            .await
1✔
3074
            .unwrap();
1✔
3075

1✔
3076
        admin_layer_db
1✔
3077
            .add_permission(
1✔
3078
                user_session.user.id.into(),
1✔
3079
                collection2_id.clone(),
1✔
3080
                Permission::Read,
1✔
3081
            )
1✔
3082
            .await
1✔
3083
            .unwrap();
1✔
3084

1✔
3085
        admin_layer_db
1✔
3086
            .add_permission(
1✔
3087
                user_session.user.id.into(),
1✔
3088
                layer1.clone(),
1✔
3089
                Permission::Read,
1✔
3090
            )
1✔
3091
            .await
1✔
3092
            .unwrap();
1✔
3093

1✔
3094
        admin_layer_db
1✔
3095
            .add_permission(
1✔
3096
                user_session.user.id.into(),
1✔
3097
                layer2.clone(),
1✔
3098
                Permission::Read,
1✔
3099
            )
1✔
3100
            .await
1✔
3101
            .unwrap();
1✔
3102

3103
        // Ensure admin sees everything we added
3104
        let admin_root_collection_all = admin_layer_db
1✔
3105
            .autocomplete_search(
1✔
3106
                &root_collection_id,
1✔
3107
                SearchParameters {
1✔
3108
                    search_type: SearchType::Fulltext,
1✔
3109
                    search_string: String::new(),
1✔
3110
                    limit: 10,
1✔
3111
                    offset: 0,
1✔
3112
                },
1✔
3113
            )
1✔
3114
            .await
1✔
3115
            .unwrap();
1✔
3116

1✔
3117
        assert_eq!(
1✔
3118
            admin_root_collection_all,
1✔
3119
            vec![
1✔
3120
                "Collection1".to_string(),
1✔
3121
                "Collection2".to_string(),
1✔
3122
                "Collection3".to_string(),
1✔
3123
                "Layer1".to_string(),
1✔
3124
                "Layer2".to_string(),
1✔
3125
                "Layer3".to_string(),
1✔
3126
                "Unsorted".to_string(),
1✔
3127
            ]
1✔
3128
        );
1✔
3129

3130
        let root_collection_all = user_layer_db
1✔
3131
            .autocomplete_search(
1✔
3132
                &root_collection_id,
1✔
3133
                SearchParameters {
1✔
3134
                    search_type: SearchType::Fulltext,
1✔
3135
                    search_string: String::new(),
1✔
3136
                    limit: 10,
1✔
3137
                    offset: 0,
1✔
3138
                },
1✔
3139
            )
1✔
3140
            .await
1✔
3141
            .unwrap();
1✔
3142

1✔
3143
        assert_eq!(
1✔
3144
            root_collection_all,
1✔
3145
            vec![
1✔
3146
                "Collection1".to_string(),
1✔
3147
                "Collection2".to_string(),
1✔
3148
                "Layer1".to_string(),
1✔
3149
                "Layer2".to_string(),
1✔
3150
                "Unsorted".to_string(),
1✔
3151
            ]
1✔
3152
        );
1✔
3153

3154
        let root_collection_filtered = user_layer_db
1✔
3155
            .autocomplete_search(
1✔
3156
                &root_collection_id,
1✔
3157
                SearchParameters {
1✔
3158
                    search_type: SearchType::Fulltext,
1✔
3159
                    search_string: "lection".to_string(),
1✔
3160
                    limit: 10,
1✔
3161
                    offset: 0,
1✔
3162
                },
1✔
3163
            )
1✔
3164
            .await
1✔
3165
            .unwrap();
1✔
3166

1✔
3167
        assert_eq!(
1✔
3168
            root_collection_filtered,
1✔
3169
            vec!["Collection1".to_string(), "Collection2".to_string(),]
1✔
3170
        );
1✔
3171

3172
        let collection1_all = user_layer_db
1✔
3173
            .autocomplete_search(
1✔
3174
                &collection1_id,
1✔
3175
                SearchParameters {
1✔
3176
                    search_type: SearchType::Fulltext,
1✔
3177
                    search_string: String::new(),
1✔
3178
                    limit: 10,
1✔
3179
                    offset: 0,
1✔
3180
                },
1✔
3181
            )
1✔
3182
            .await
1✔
3183
            .unwrap();
1✔
3184

1✔
3185
        assert_eq!(
1✔
3186
            collection1_all,
1✔
3187
            vec!["Collection2".to_string(), "Layer2".to_string(),]
1✔
3188
        );
1✔
3189

3190
        let collection1_filtered_fulltext = user_layer_db
1✔
3191
            .autocomplete_search(
1✔
3192
                &collection1_id,
1✔
3193
                SearchParameters {
1✔
3194
                    search_type: SearchType::Fulltext,
1✔
3195
                    search_string: "ay".to_string(),
1✔
3196
                    limit: 10,
1✔
3197
                    offset: 0,
1✔
3198
                },
1✔
3199
            )
1✔
3200
            .await
1✔
3201
            .unwrap();
1✔
3202

1✔
3203
        assert_eq!(collection1_filtered_fulltext, vec!["Layer2".to_string(),]);
1✔
3204

3205
        let collection1_filtered_prefix = user_layer_db
1✔
3206
            .autocomplete_search(
1✔
3207
                &collection1_id,
1✔
3208
                SearchParameters {
1✔
3209
                    search_type: SearchType::Prefix,
1✔
3210
                    search_string: "ay".to_string(),
1✔
3211
                    limit: 10,
1✔
3212
                    offset: 0,
1✔
3213
                },
1✔
3214
            )
1✔
3215
            .await
1✔
3216
            .unwrap();
1✔
3217

1✔
3218
        assert_eq!(collection1_filtered_prefix, Vec::<String>::new());
1✔
3219

3220
        let collection1_filtered_prefix2 = user_layer_db
1✔
3221
            .autocomplete_search(
1✔
3222
                &collection1_id,
1✔
3223
                SearchParameters {
1✔
3224
                    search_type: SearchType::Prefix,
1✔
3225
                    search_string: "Lay".to_string(),
1✔
3226
                    limit: 10,
1✔
3227
                    offset: 0,
1✔
3228
                },
1✔
3229
            )
1✔
3230
            .await
1✔
3231
            .unwrap();
1✔
3232

1✔
3233
        assert_eq!(collection1_filtered_prefix2, vec!["Layer2".to_string(),]);
1✔
3234
    }
1✔
3235

3236
    #[allow(clippy::too_many_lines)]
3237
    #[ge_context::test]
1✔
3238
    async fn it_reports_search_capabilities(app_ctx: PostgresContext<NoTls>) {
1✔
3239
        let session = admin_login(&app_ctx).await;
1✔
3240

3241
        let layer_db = app_ctx.session_context(session).db();
1✔
3242

1✔
3243
        let capabilities = layer_db.capabilities().search;
1✔
3244

3245
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
3246

1✔
3247
        if capabilities.search_types.fulltext {
1✔
3248
            assert!(layer_db
1✔
3249
                .search(
1✔
3250
                    &root_collection_id,
1✔
3251
                    SearchParameters {
1✔
3252
                        search_type: SearchType::Fulltext,
1✔
3253
                        search_string: String::new(),
1✔
3254
                        limit: 10,
1✔
3255
                        offset: 0,
1✔
3256
                    },
1✔
3257
                )
1✔
3258
                .await
1✔
3259
                .is_ok());
1✔
3260

3261
            if capabilities.autocomplete {
1✔
3262
                assert!(layer_db
1✔
3263
                    .autocomplete_search(
1✔
3264
                        &root_collection_id,
1✔
3265
                        SearchParameters {
1✔
3266
                            search_type: SearchType::Fulltext,
1✔
3267
                            search_string: String::new(),
1✔
3268
                            limit: 10,
1✔
3269
                            offset: 0,
1✔
3270
                        },
1✔
3271
                    )
1✔
3272
                    .await
1✔
3273
                    .is_ok());
1✔
3274
            } else {
3275
                assert!(layer_db
×
3276
                    .autocomplete_search(
×
3277
                        &root_collection_id,
×
3278
                        SearchParameters {
×
3279
                            search_type: SearchType::Fulltext,
×
3280
                            search_string: String::new(),
×
3281
                            limit: 10,
×
3282
                            offset: 0,
×
3283
                        },
×
3284
                    )
×
3285
                    .await
×
3286
                    .is_err());
×
3287
            }
3288
        }
×
3289
        if capabilities.search_types.prefix {
1✔
3290
            assert!(layer_db
1✔
3291
                .search(
1✔
3292
                    &root_collection_id,
1✔
3293
                    SearchParameters {
1✔
3294
                        search_type: SearchType::Prefix,
1✔
3295
                        search_string: String::new(),
1✔
3296
                        limit: 10,
1✔
3297
                        offset: 0,
1✔
3298
                    },
1✔
3299
                )
1✔
3300
                .await
1✔
3301
                .is_ok());
1✔
3302

3303
            if capabilities.autocomplete {
1✔
3304
                assert!(layer_db
1✔
3305
                    .autocomplete_search(
1✔
3306
                        &root_collection_id,
1✔
3307
                        SearchParameters {
1✔
3308
                            search_type: SearchType::Prefix,
1✔
3309
                            search_string: String::new(),
1✔
3310
                            limit: 10,
1✔
3311
                            offset: 0,
1✔
3312
                        },
1✔
3313
                    )
1✔
3314
                    .await
1✔
3315
                    .is_ok());
1✔
3316
            } else {
3317
                assert!(layer_db
×
3318
                    .autocomplete_search(
×
3319
                        &root_collection_id,
×
3320
                        SearchParameters {
×
3321
                            search_type: SearchType::Prefix,
×
3322
                            search_string: String::new(),
×
3323
                            limit: 10,
×
3324
                            offset: 0,
×
3325
                        },
×
3326
                    )
×
3327
                    .await
×
3328
                    .is_err());
×
3329
            }
3330
        }
×
3331
    }
1✔
3332

3333
    #[ge_context::test]
1✔
3334
    async fn it_tracks_used_quota_in_postgres(app_ctx: PostgresContext<NoTls>) {
1✔
3335
        let _user = app_ctx
1✔
3336
            .register_user(UserRegistration {
1✔
3337
                email: "foo@example.com".to_string(),
1✔
3338
                password: "secret1234".to_string(),
1✔
3339
                real_name: "Foo Bar".to_string(),
1✔
3340
            })
1✔
3341
            .await
1✔
3342
            .unwrap();
1✔
3343

3344
        let session = app_ctx
1✔
3345
            .login(UserCredentials {
1✔
3346
                email: "foo@example.com".to_string(),
1✔
3347
                password: "secret1234".to_string(),
1✔
3348
            })
1✔
3349
            .await
1✔
3350
            .unwrap();
1✔
3351

3352
        let admin_session = admin_login(&app_ctx).await;
1✔
3353

3354
        let quota = initialize_quota_tracking(
1✔
3355
            QuotaTrackingMode::Check,
1✔
3356
            app_ctx.session_context(admin_session).db(),
1✔
3357
            0,
1✔
3358
            60,
1✔
3359
        );
1✔
3360

1✔
3361
        let tracking = quota.create_quota_tracking(&session, Uuid::new_v4(), Uuid::new_v4());
1✔
3362

1✔
3363
        tracking.mock_work_unit_done();
1✔
3364
        tracking.mock_work_unit_done();
1✔
3365

1✔
3366
        let db = app_ctx.session_context(session).db();
1✔
3367

1✔
3368
        // wait for quota to be recorded
1✔
3369
        let mut success = false;
1✔
3370
        for _ in 0..10 {
2✔
3371
            let used = db.quota_used().await.unwrap();
2✔
3372
            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
2✔
3373

3374
            if used == 2 {
2✔
3375
                success = true;
1✔
3376
                break;
1✔
3377
            }
1✔
3378
        }
3379

3380
        assert!(success);
1✔
3381
    }
1✔
3382

3383
    #[ge_context::test]
1✔
3384
    async fn it_tracks_available_quota(app_ctx: PostgresContext<NoTls>) {
1✔
3385
        let user = app_ctx
1✔
3386
            .register_user(UserRegistration {
1✔
3387
                email: "foo@example.com".to_string(),
1✔
3388
                password: "secret1234".to_string(),
1✔
3389
                real_name: "Foo Bar".to_string(),
1✔
3390
            })
1✔
3391
            .await
1✔
3392
            .unwrap();
1✔
3393

3394
        let session = app_ctx
1✔
3395
            .login(UserCredentials {
1✔
3396
                email: "foo@example.com".to_string(),
1✔
3397
                password: "secret1234".to_string(),
1✔
3398
            })
1✔
3399
            .await
1✔
3400
            .unwrap();
1✔
3401

3402
        let admin_session = admin_login(&app_ctx).await;
1✔
3403

3404
        app_ctx
1✔
3405
            .session_context(admin_session.clone())
1✔
3406
            .db()
1✔
3407
            .update_quota_available_by_user(&user, 1)
1✔
3408
            .await
1✔
3409
            .unwrap();
1✔
3410

1✔
3411
        let quota = initialize_quota_tracking(
1✔
3412
            QuotaTrackingMode::Check,
1✔
3413
            app_ctx.session_context(admin_session).db(),
1✔
3414
            0,
1✔
3415
            60,
1✔
3416
        );
1✔
3417

1✔
3418
        let tracking = quota.create_quota_tracking(&session, Uuid::new_v4(), Uuid::new_v4());
1✔
3419

1✔
3420
        tracking.mock_work_unit_done();
1✔
3421
        tracking.mock_work_unit_done();
1✔
3422

1✔
3423
        let db = app_ctx.session_context(session).db();
1✔
3424

1✔
3425
        // wait for quota to be recorded
1✔
3426
        let mut success = false;
1✔
3427
        for _ in 0..10 {
1✔
3428
            let available = db.quota_available().await.unwrap();
1✔
3429
            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
1✔
3430

3431
            if available == -1 {
1✔
3432
                success = true;
1✔
3433
                break;
1✔
UNCOV
3434
            }
×
3435
        }
3436

3437
        assert!(success);
1✔
3438
    }
1✔
3439

3440
    #[ge_context::test]
1✔
3441
    async fn it_updates_quota_in_postgres(app_ctx: PostgresContext<NoTls>) {
1✔
3442
        let user = app_ctx
1✔
3443
            .register_user(UserRegistration {
1✔
3444
                email: "foo@example.com".to_string(),
1✔
3445
                password: "secret1234".to_string(),
1✔
3446
                real_name: "Foo Bar".to_string(),
1✔
3447
            })
1✔
3448
            .await
1✔
3449
            .unwrap();
1✔
3450

3451
        let session = app_ctx
1✔
3452
            .login(UserCredentials {
1✔
3453
                email: "foo@example.com".to_string(),
1✔
3454
                password: "secret1234".to_string(),
1✔
3455
            })
1✔
3456
            .await
1✔
3457
            .unwrap();
1✔
3458

1✔
3459
        let db = app_ctx.session_context(session.clone()).db();
1✔
3460
        let admin_db = app_ctx.session_context(UserSession::admin_session()).db();
1✔
3461

3462
        assert_eq!(
1✔
3463
            db.quota_available().await.unwrap(),
1✔
3464
            crate::config::get_config_element::<crate::config::Quota>()
1✔
3465
                .unwrap()
1✔
3466
                .initial_credits
3467
        );
3468

3469
        assert_eq!(
1✔
3470
            admin_db.quota_available_by_user(&user).await.unwrap(),
1✔
3471
            crate::config::get_config_element::<crate::config::Quota>()
1✔
3472
                .unwrap()
1✔
3473
                .initial_credits
3474
        );
3475

3476
        admin_db
1✔
3477
            .update_quota_available_by_user(&user, 123)
1✔
3478
            .await
1✔
3479
            .unwrap();
1✔
3480

3481
        assert_eq!(db.quota_available().await.unwrap(), 123);
1✔
3482

3483
        assert_eq!(admin_db.quota_available_by_user(&user).await.unwrap(), 123);
1✔
3484
    }
1✔
3485

3486
    #[allow(clippy::too_many_lines)]
3487
    #[ge_context::test]
1✔
3488
    async fn it_removes_layer_collections(app_ctx: PostgresContext<NoTls>) {
1✔
3489
        let session = admin_login(&app_ctx).await;
1✔
3490

3491
        let layer_db = app_ctx.session_context(session).db();
1✔
3492

1✔
3493
        let layer = AddLayer {
1✔
3494
            name: "layer".to_string(),
1✔
3495
            description: "description".to_string(),
1✔
3496
            workflow: Workflow {
1✔
3497
                operator: TypedOperator::Vector(
1✔
3498
                    MockPointSource {
1✔
3499
                        params: MockPointSourceParams {
1✔
3500
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3501
                        },
1✔
3502
                    }
1✔
3503
                    .boxed(),
1✔
3504
                ),
1✔
3505
            },
1✔
3506
            symbology: None,
1✔
3507
            metadata: Default::default(),
1✔
3508
            properties: Default::default(),
1✔
3509
        };
1✔
3510

3511
        let root_collection = &layer_db.get_root_layer_collection_id().await.unwrap();
1✔
3512

1✔
3513
        let collection = AddLayerCollection {
1✔
3514
            name: "top collection".to_string(),
1✔
3515
            description: "description".to_string(),
1✔
3516
            properties: Default::default(),
1✔
3517
        };
1✔
3518

3519
        let top_c_id = layer_db
1✔
3520
            .add_layer_collection(collection, root_collection)
1✔
3521
            .await
1✔
3522
            .unwrap();
1✔
3523

3524
        let l_id = layer_db.add_layer(layer, &top_c_id).await.unwrap();
1✔
3525

1✔
3526
        let collection = AddLayerCollection {
1✔
3527
            name: "empty collection".to_string(),
1✔
3528
            description: "description".to_string(),
1✔
3529
            properties: Default::default(),
1✔
3530
        };
1✔
3531

3532
        let empty_c_id = layer_db
1✔
3533
            .add_layer_collection(collection, &top_c_id)
1✔
3534
            .await
1✔
3535
            .unwrap();
1✔
3536

3537
        let items = layer_db
1✔
3538
            .load_layer_collection(
1✔
3539
                &top_c_id,
1✔
3540
                LayerCollectionListOptions {
1✔
3541
                    offset: 0,
1✔
3542
                    limit: 20,
1✔
3543
                },
1✔
3544
            )
1✔
3545
            .await
1✔
3546
            .unwrap();
1✔
3547

1✔
3548
        assert_eq!(
1✔
3549
            items,
1✔
3550
            LayerCollection {
1✔
3551
                id: ProviderLayerCollectionId {
1✔
3552
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
3553
                    collection_id: top_c_id.clone(),
1✔
3554
                },
1✔
3555
                name: "top collection".to_string(),
1✔
3556
                description: "description".to_string(),
1✔
3557
                items: vec![
1✔
3558
                    CollectionItem::Collection(LayerCollectionListing {
1✔
3559
                        id: ProviderLayerCollectionId {
1✔
3560
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
3561
                            collection_id: empty_c_id.clone(),
1✔
3562
                        },
1✔
3563
                        name: "empty collection".to_string(),
1✔
3564
                        description: "description".to_string(),
1✔
3565
                        properties: Default::default(),
1✔
3566
                    }),
1✔
3567
                    CollectionItem::Layer(LayerListing {
1✔
3568
                        id: ProviderLayerId {
1✔
3569
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
3570
                            layer_id: l_id.clone(),
1✔
3571
                        },
1✔
3572
                        name: "layer".to_string(),
1✔
3573
                        description: "description".to_string(),
1✔
3574
                        properties: vec![],
1✔
3575
                    })
1✔
3576
                ],
1✔
3577
                entry_label: None,
1✔
3578
                properties: vec![],
1✔
3579
            }
1✔
3580
        );
1✔
3581

3582
        // remove empty collection
3583
        layer_db.remove_layer_collection(&empty_c_id).await.unwrap();
1✔
3584

3585
        let items = layer_db
1✔
3586
            .load_layer_collection(
1✔
3587
                &top_c_id,
1✔
3588
                LayerCollectionListOptions {
1✔
3589
                    offset: 0,
1✔
3590
                    limit: 20,
1✔
3591
                },
1✔
3592
            )
1✔
3593
            .await
1✔
3594
            .unwrap();
1✔
3595

1✔
3596
        assert_eq!(
1✔
3597
            items,
1✔
3598
            LayerCollection {
1✔
3599
                id: ProviderLayerCollectionId {
1✔
3600
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
3601
                    collection_id: top_c_id.clone(),
1✔
3602
                },
1✔
3603
                name: "top collection".to_string(),
1✔
3604
                description: "description".to_string(),
1✔
3605
                items: vec![CollectionItem::Layer(LayerListing {
1✔
3606
                    id: ProviderLayerId {
1✔
3607
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
3608
                        layer_id: l_id.clone(),
1✔
3609
                    },
1✔
3610
                    name: "layer".to_string(),
1✔
3611
                    description: "description".to_string(),
1✔
3612
                    properties: vec![],
1✔
3613
                })],
1✔
3614
                entry_label: None,
1✔
3615
                properties: vec![],
1✔
3616
            }
1✔
3617
        );
1✔
3618

3619
        // remove top (not root) collection
3620
        layer_db.remove_layer_collection(&top_c_id).await.unwrap();
1✔
3621

1✔
3622
        layer_db
1✔
3623
            .load_layer_collection(
1✔
3624
                &top_c_id,
1✔
3625
                LayerCollectionListOptions {
1✔
3626
                    offset: 0,
1✔
3627
                    limit: 20,
1✔
3628
                },
1✔
3629
            )
1✔
3630
            .await
1✔
3631
            .unwrap_err();
1✔
3632

1✔
3633
        // should be deleted automatically
1✔
3634
        layer_db.load_layer(&l_id).await.unwrap_err();
1✔
3635

1✔
3636
        // it is not allowed to remove the root collection
1✔
3637
        layer_db
1✔
3638
            .remove_layer_collection(root_collection)
1✔
3639
            .await
1✔
3640
            .unwrap_err();
1✔
3641
        layer_db
1✔
3642
            .load_layer_collection(
1✔
3643
                root_collection,
1✔
3644
                LayerCollectionListOptions {
1✔
3645
                    offset: 0,
1✔
3646
                    limit: 20,
1✔
3647
                },
1✔
3648
            )
1✔
3649
            .await
1✔
3650
            .unwrap();
1✔
3651
    }
1✔
3652

3653
    #[ge_context::test]
1✔
3654
    #[allow(clippy::too_many_lines)]
3655
    async fn it_removes_collections_from_collections(app_ctx: PostgresContext<NoTls>) {
1✔
3656
        let session = admin_login(&app_ctx).await;
1✔
3657

3658
        let db = app_ctx.session_context(session).db();
1✔
3659

3660
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
3661

3662
        let mid_collection_id = db
1✔
3663
            .add_layer_collection(
1✔
3664
                AddLayerCollection {
1✔
3665
                    name: "mid collection".to_string(),
1✔
3666
                    description: "description".to_string(),
1✔
3667
                    properties: Default::default(),
1✔
3668
                },
1✔
3669
                root_collection_id,
1✔
3670
            )
1✔
3671
            .await
1✔
3672
            .unwrap();
1✔
3673

3674
        let bottom_collection_id = db
1✔
3675
            .add_layer_collection(
1✔
3676
                AddLayerCollection {
1✔
3677
                    name: "bottom collection".to_string(),
1✔
3678
                    description: "description".to_string(),
1✔
3679
                    properties: Default::default(),
1✔
3680
                },
1✔
3681
                &mid_collection_id,
1✔
3682
            )
1✔
3683
            .await
1✔
3684
            .unwrap();
1✔
3685

3686
        let layer_id = db
1✔
3687
            .add_layer(
1✔
3688
                AddLayer {
1✔
3689
                    name: "layer".to_string(),
1✔
3690
                    description: "description".to_string(),
1✔
3691
                    workflow: Workflow {
1✔
3692
                        operator: TypedOperator::Vector(
1✔
3693
                            MockPointSource {
1✔
3694
                                params: MockPointSourceParams {
1✔
3695
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3696
                                },
1✔
3697
                            }
1✔
3698
                            .boxed(),
1✔
3699
                        ),
1✔
3700
                    },
1✔
3701
                    symbology: None,
1✔
3702
                    metadata: Default::default(),
1✔
3703
                    properties: Default::default(),
1✔
3704
                },
1✔
3705
                &mid_collection_id,
1✔
3706
            )
1✔
3707
            .await
1✔
3708
            .unwrap();
1✔
3709

1✔
3710
        // removing the mid collection…
1✔
3711
        db.remove_layer_collection_from_parent(&mid_collection_id, root_collection_id)
1✔
3712
            .await
1✔
3713
            .unwrap();
1✔
3714

1✔
3715
        // …should remove itself
1✔
3716
        db.load_layer_collection(&mid_collection_id, LayerCollectionListOptions::default())
1✔
3717
            .await
1✔
3718
            .unwrap_err();
1✔
3719

1✔
3720
        // …should remove the bottom collection
1✔
3721
        db.load_layer_collection(&bottom_collection_id, LayerCollectionListOptions::default())
1✔
3722
            .await
1✔
3723
            .unwrap_err();
1✔
3724

1✔
3725
        // … and should remove the layer of the bottom collection
1✔
3726
        db.load_layer(&layer_id).await.unwrap_err();
1✔
3727

1✔
3728
        // the root collection is still there
1✔
3729
        db.load_layer_collection(root_collection_id, LayerCollectionListOptions::default())
1✔
3730
            .await
1✔
3731
            .unwrap();
1✔
3732
    }
1✔
3733

3734
    #[ge_context::test]
1✔
3735
    #[allow(clippy::too_many_lines)]
3736
    async fn it_removes_layers_from_collections(app_ctx: PostgresContext<NoTls>) {
1✔
3737
        let session = admin_login(&app_ctx).await;
1✔
3738

3739
        let db = app_ctx.session_context(session).db();
1✔
3740

3741
        let root_collection = &db.get_root_layer_collection_id().await.unwrap();
1✔
3742

3743
        let another_collection = db
1✔
3744
            .add_layer_collection(
1✔
3745
                AddLayerCollection {
1✔
3746
                    name: "top collection".to_string(),
1✔
3747
                    description: "description".to_string(),
1✔
3748
                    properties: Default::default(),
1✔
3749
                },
1✔
3750
                root_collection,
1✔
3751
            )
1✔
3752
            .await
1✔
3753
            .unwrap();
1✔
3754

3755
        let layer_in_one_collection = db
1✔
3756
            .add_layer(
1✔
3757
                AddLayer {
1✔
3758
                    name: "layer 1".to_string(),
1✔
3759
                    description: "description".to_string(),
1✔
3760
                    workflow: Workflow {
1✔
3761
                        operator: TypedOperator::Vector(
1✔
3762
                            MockPointSource {
1✔
3763
                                params: MockPointSourceParams {
1✔
3764
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3765
                                },
1✔
3766
                            }
1✔
3767
                            .boxed(),
1✔
3768
                        ),
1✔
3769
                    },
1✔
3770
                    symbology: None,
1✔
3771
                    metadata: Default::default(),
1✔
3772
                    properties: Default::default(),
1✔
3773
                },
1✔
3774
                &another_collection,
1✔
3775
            )
1✔
3776
            .await
1✔
3777
            .unwrap();
1✔
3778

3779
        let layer_in_two_collections = db
1✔
3780
            .add_layer(
1✔
3781
                AddLayer {
1✔
3782
                    name: "layer 2".to_string(),
1✔
3783
                    description: "description".to_string(),
1✔
3784
                    workflow: Workflow {
1✔
3785
                        operator: TypedOperator::Vector(
1✔
3786
                            MockPointSource {
1✔
3787
                                params: MockPointSourceParams {
1✔
3788
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3789
                                },
1✔
3790
                            }
1✔
3791
                            .boxed(),
1✔
3792
                        ),
1✔
3793
                    },
1✔
3794
                    symbology: None,
1✔
3795
                    metadata: Default::default(),
1✔
3796
                    properties: Default::default(),
1✔
3797
                },
1✔
3798
                &another_collection,
1✔
3799
            )
1✔
3800
            .await
1✔
3801
            .unwrap();
1✔
3802

1✔
3803
        db.load_layer(&layer_in_two_collections).await.unwrap();
1✔
3804

1✔
3805
        db.add_layer_to_collection(&layer_in_two_collections, root_collection)
1✔
3806
            .await
1✔
3807
            .unwrap();
1✔
3808

1✔
3809
        // remove first layer --> should be deleted entirely
1✔
3810

1✔
3811
        db.remove_layer_from_collection(&layer_in_one_collection, &another_collection)
1✔
3812
            .await
1✔
3813
            .unwrap();
1✔
3814

3815
        let number_of_layer_in_collection = db
1✔
3816
            .load_layer_collection(
1✔
3817
                &another_collection,
1✔
3818
                LayerCollectionListOptions {
1✔
3819
                    offset: 0,
1✔
3820
                    limit: 20,
1✔
3821
                },
1✔
3822
            )
1✔
3823
            .await
1✔
3824
            .unwrap()
1✔
3825
            .items
1✔
3826
            .len();
1✔
3827
        assert_eq!(
1✔
3828
            number_of_layer_in_collection,
1✔
3829
            1 /* only the other collection should be here */
1✔
3830
        );
1✔
3831

3832
        db.load_layer(&layer_in_one_collection).await.unwrap_err();
1✔
3833

1✔
3834
        // remove second layer --> should only be gone in collection
1✔
3835

1✔
3836
        db.remove_layer_from_collection(&layer_in_two_collections, &another_collection)
1✔
3837
            .await
1✔
3838
            .unwrap();
1✔
3839

3840
        let number_of_layer_in_collection = db
1✔
3841
            .load_layer_collection(
1✔
3842
                &another_collection,
1✔
3843
                LayerCollectionListOptions {
1✔
3844
                    offset: 0,
1✔
3845
                    limit: 20,
1✔
3846
                },
1✔
3847
            )
1✔
3848
            .await
1✔
3849
            .unwrap()
1✔
3850
            .items
1✔
3851
            .len();
1✔
3852
        assert_eq!(
1✔
3853
            number_of_layer_in_collection,
1✔
3854
            0 /* both layers were deleted */
1✔
3855
        );
1✔
3856

3857
        db.load_layer(&layer_in_two_collections).await.unwrap();
1✔
3858
    }
1✔
3859

3860
    #[ge_context::test]
1✔
3861
    #[allow(clippy::too_many_lines)]
3862
    async fn it_deletes_dataset(app_ctx: PostgresContext<NoTls>) {
1✔
3863
        let loading_info = OgrSourceDataset {
1✔
3864
            file_name: PathBuf::from("test.csv"),
1✔
3865
            layer_name: "test.csv".to_owned(),
1✔
3866
            data_type: Some(VectorDataType::MultiPoint),
1✔
3867
            time: OgrSourceDatasetTimeType::Start {
1✔
3868
                start_field: "start".to_owned(),
1✔
3869
                start_format: OgrSourceTimeFormat::Auto,
1✔
3870
                duration: OgrSourceDurationSpec::Zero,
1✔
3871
            },
1✔
3872
            default_geometry: None,
1✔
3873
            columns: Some(OgrSourceColumnSpec {
1✔
3874
                format_specifics: Some(FormatSpecifics::Csv {
1✔
3875
                    header: CsvHeader::Auto,
1✔
3876
                }),
1✔
3877
                x: "x".to_owned(),
1✔
3878
                y: None,
1✔
3879
                int: vec![],
1✔
3880
                float: vec![],
1✔
3881
                text: vec![],
1✔
3882
                bool: vec![],
1✔
3883
                datetime: vec![],
1✔
3884
                rename: None,
1✔
3885
            }),
1✔
3886
            force_ogr_time_filter: false,
1✔
3887
            force_ogr_spatial_filter: false,
1✔
3888
            on_error: OgrSourceErrorSpec::Ignore,
1✔
3889
            sql_query: None,
1✔
3890
            attribute_query: None,
1✔
3891
            cache_ttl: CacheTtlSeconds::default(),
1✔
3892
        };
1✔
3893

1✔
3894
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
3895
            OgrSourceDataset,
1✔
3896
            VectorResultDescriptor,
1✔
3897
            VectorQueryRectangle,
1✔
3898
        > {
1✔
3899
            loading_info: loading_info.clone(),
1✔
3900
            result_descriptor: VectorResultDescriptor {
1✔
3901
                data_type: VectorDataType::MultiPoint,
1✔
3902
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3903
                columns: [(
1✔
3904
                    "foo".to_owned(),
1✔
3905
                    VectorColumnInfo {
1✔
3906
                        data_type: FeatureDataType::Float,
1✔
3907
                        measurement: Measurement::Unitless,
1✔
3908
                    },
1✔
3909
                )]
1✔
3910
                .into_iter()
1✔
3911
                .collect(),
1✔
3912
                time: None,
1✔
3913
                bbox: None,
1✔
3914
            },
1✔
3915
            phantom: Default::default(),
1✔
3916
        });
1✔
3917

3918
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
3919

1✔
3920
        let dataset_name = DatasetName::new(Some(session.user.id.to_string()), "my_dataset");
1✔
3921

1✔
3922
        let db = app_ctx.session_context(session.clone()).db();
1✔
3923
        let dataset_id = db
1✔
3924
            .add_dataset(
1✔
3925
                AddDataset {
1✔
3926
                    name: Some(dataset_name),
1✔
3927
                    display_name: "Ogr Test".to_owned(),
1✔
3928
                    description: "desc".to_owned(),
1✔
3929
                    source_operator: "OgrSource".to_owned(),
1✔
3930
                    symbology: None,
1✔
3931
                    provenance: Some(vec![Provenance {
1✔
3932
                        citation: "citation".to_owned(),
1✔
3933
                        license: "license".to_owned(),
1✔
3934
                        uri: "uri".to_owned(),
1✔
3935
                    }]),
1✔
3936
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
3937
                },
1✔
3938
                meta_data,
1✔
3939
            )
1✔
3940
            .await
1✔
3941
            .unwrap()
1✔
3942
            .id;
1✔
3943

1✔
3944
        assert!(db.load_dataset(&dataset_id).await.is_ok());
1✔
3945

3946
        db.delete_dataset(dataset_id).await.unwrap();
1✔
3947

1✔
3948
        assert!(db.load_dataset(&dataset_id).await.is_err());
1✔
3949
    }
1✔
3950

3951
    #[ge_context::test]
1✔
3952
    #[allow(clippy::too_many_lines)]
3953
    async fn it_deletes_admin_dataset(app_ctx: PostgresContext<NoTls>) {
1✔
3954
        let dataset_name = DatasetName::new(None, "my_dataset");
1✔
3955

1✔
3956
        let loading_info = OgrSourceDataset {
1✔
3957
            file_name: PathBuf::from("test.csv"),
1✔
3958
            layer_name: "test.csv".to_owned(),
1✔
3959
            data_type: Some(VectorDataType::MultiPoint),
1✔
3960
            time: OgrSourceDatasetTimeType::Start {
1✔
3961
                start_field: "start".to_owned(),
1✔
3962
                start_format: OgrSourceTimeFormat::Auto,
1✔
3963
                duration: OgrSourceDurationSpec::Zero,
1✔
3964
            },
1✔
3965
            default_geometry: None,
1✔
3966
            columns: Some(OgrSourceColumnSpec {
1✔
3967
                format_specifics: Some(FormatSpecifics::Csv {
1✔
3968
                    header: CsvHeader::Auto,
1✔
3969
                }),
1✔
3970
                x: "x".to_owned(),
1✔
3971
                y: None,
1✔
3972
                int: vec![],
1✔
3973
                float: vec![],
1✔
3974
                text: vec![],
1✔
3975
                bool: vec![],
1✔
3976
                datetime: vec![],
1✔
3977
                rename: None,
1✔
3978
            }),
1✔
3979
            force_ogr_time_filter: false,
1✔
3980
            force_ogr_spatial_filter: false,
1✔
3981
            on_error: OgrSourceErrorSpec::Ignore,
1✔
3982
            sql_query: None,
1✔
3983
            attribute_query: None,
1✔
3984
            cache_ttl: CacheTtlSeconds::default(),
1✔
3985
        };
1✔
3986

1✔
3987
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
3988
            OgrSourceDataset,
1✔
3989
            VectorResultDescriptor,
1✔
3990
            VectorQueryRectangle,
1✔
3991
        > {
1✔
3992
            loading_info: loading_info.clone(),
1✔
3993
            result_descriptor: VectorResultDescriptor {
1✔
3994
                data_type: VectorDataType::MultiPoint,
1✔
3995
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3996
                columns: [(
1✔
3997
                    "foo".to_owned(),
1✔
3998
                    VectorColumnInfo {
1✔
3999
                        data_type: FeatureDataType::Float,
1✔
4000
                        measurement: Measurement::Unitless,
1✔
4001
                    },
1✔
4002
                )]
1✔
4003
                .into_iter()
1✔
4004
                .collect(),
1✔
4005
                time: None,
1✔
4006
                bbox: None,
1✔
4007
            },
1✔
4008
            phantom: Default::default(),
1✔
4009
        });
1✔
4010

4011
        let session = admin_login(&app_ctx).await;
1✔
4012

4013
        let db = app_ctx.session_context(session).db();
1✔
4014
        let dataset_id = db
1✔
4015
            .add_dataset(
1✔
4016
                AddDataset {
1✔
4017
                    name: Some(dataset_name),
1✔
4018
                    display_name: "Ogr Test".to_owned(),
1✔
4019
                    description: "desc".to_owned(),
1✔
4020
                    source_operator: "OgrSource".to_owned(),
1✔
4021
                    symbology: None,
1✔
4022
                    provenance: Some(vec![Provenance {
1✔
4023
                        citation: "citation".to_owned(),
1✔
4024
                        license: "license".to_owned(),
1✔
4025
                        uri: "uri".to_owned(),
1✔
4026
                    }]),
1✔
4027
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
4028
                },
1✔
4029
                meta_data,
1✔
4030
            )
1✔
4031
            .await
1✔
4032
            .unwrap()
1✔
4033
            .id;
1✔
4034

1✔
4035
        assert!(db.load_dataset(&dataset_id).await.is_ok());
1✔
4036

4037
        db.delete_dataset(dataset_id).await.unwrap();
1✔
4038

1✔
4039
        assert!(db.load_dataset(&dataset_id).await.is_err());
1✔
4040
    }
1✔
4041

4042
    #[ge_context::test]
1✔
4043
    async fn test_missing_layer_dataset_in_collection_listing(app_ctx: PostgresContext<NoTls>) {
1✔
4044
        let session = admin_login(&app_ctx).await;
1✔
4045
        let db = app_ctx.session_context(session).db();
1✔
4046

4047
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
4048

4049
        let top_collection_id = db
1✔
4050
            .add_layer_collection(
1✔
4051
                AddLayerCollection {
1✔
4052
                    name: "top collection".to_string(),
1✔
4053
                    description: "description".to_string(),
1✔
4054
                    properties: Default::default(),
1✔
4055
                },
1✔
4056
                root_collection_id,
1✔
4057
            )
1✔
4058
            .await
1✔
4059
            .unwrap();
1✔
4060

1✔
4061
        let faux_layer = LayerId("faux".to_string());
1✔
4062

1✔
4063
        // this should fail
1✔
4064
        db.add_layer_to_collection(&faux_layer, &top_collection_id)
1✔
4065
            .await
1✔
4066
            .unwrap_err();
1✔
4067

4068
        let root_collection_layers = db
1✔
4069
            .load_layer_collection(
1✔
4070
                &top_collection_id,
1✔
4071
                LayerCollectionListOptions {
1✔
4072
                    offset: 0,
1✔
4073
                    limit: 20,
1✔
4074
                },
1✔
4075
            )
1✔
4076
            .await
1✔
4077
            .unwrap();
1✔
4078

1✔
4079
        assert_eq!(
1✔
4080
            root_collection_layers,
1✔
4081
            LayerCollection {
1✔
4082
                id: ProviderLayerCollectionId {
1✔
4083
                    provider_id: DataProviderId(
1✔
4084
                        "ce5e84db-cbf9-48a2-9a32-d4b7cc56ea74".try_into().unwrap()
1✔
4085
                    ),
1✔
4086
                    collection_id: top_collection_id.clone(),
1✔
4087
                },
1✔
4088
                name: "top collection".to_string(),
1✔
4089
                description: "description".to_string(),
1✔
4090
                items: vec![],
1✔
4091
                entry_label: None,
1✔
4092
                properties: vec![],
1✔
4093
            }
1✔
4094
        );
1✔
4095
    }
1✔
4096

4097
    #[allow(clippy::too_many_lines)]
4098
    #[ge_context::test]
1✔
4099
    async fn it_restricts_layer_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
4100
        let admin_session = admin_login(&app_ctx).await;
1✔
4101
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
4102

1✔
4103
        let admin_db = app_ctx.session_context(admin_session.clone()).db();
1✔
4104
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
4105

4106
        let root = admin_db.get_root_layer_collection_id().await.unwrap();
1✔
4107

4108
        // add new collection as admin
4109
        let new_collection_id = admin_db
1✔
4110
            .add_layer_collection(
1✔
4111
                AddLayerCollection {
1✔
4112
                    name: "admin collection".to_string(),
1✔
4113
                    description: String::new(),
1✔
4114
                    properties: Default::default(),
1✔
4115
                },
1✔
4116
                &root,
1✔
4117
            )
1✔
4118
            .await
1✔
4119
            .unwrap();
1✔
4120

4121
        // load as regular user, not visible
4122
        let collection = db1
1✔
4123
            .load_layer_collection(
1✔
4124
                &root,
1✔
4125
                LayerCollectionListOptions {
1✔
4126
                    offset: 0,
1✔
4127
                    limit: 10,
1✔
4128
                },
1✔
4129
            )
1✔
4130
            .await
1✔
4131
            .unwrap();
1✔
4132
        assert!(!collection.items.iter().any(|c| match c {
1✔
4133
            CollectionItem::Collection(c) => c.id.collection_id == new_collection_id,
1✔
4134
            CollectionItem::Layer(_) => false,
×
4135
        }));
1✔
4136

4137
        // give user read permission
4138
        admin_db
1✔
4139
            .add_permission(
1✔
4140
                session1.user.id.into(),
1✔
4141
                new_collection_id.clone(),
1✔
4142
                Permission::Read,
1✔
4143
            )
1✔
4144
            .await
1✔
4145
            .unwrap();
1✔
4146

4147
        // now visible
4148
        let collection = db1
1✔
4149
            .load_layer_collection(
1✔
4150
                &root,
1✔
4151
                LayerCollectionListOptions {
1✔
4152
                    offset: 0,
1✔
4153
                    limit: 10,
1✔
4154
                },
1✔
4155
            )
1✔
4156
            .await
1✔
4157
            .unwrap();
1✔
4158

1✔
4159
        assert!(collection.items.iter().any(|c| match c {
1✔
4160
            CollectionItem::Collection(c) => c.id.collection_id == new_collection_id,
1✔
4161
            CollectionItem::Layer(_) => false,
×
4162
        }));
1✔
4163
    }
1✔
4164

4165
    #[allow(clippy::too_many_lines)]
4166
    #[ge_context::test]
1✔
4167
    async fn it_handles_user_roles(app_ctx: PostgresContext<NoTls>) {
1✔
4168
        let admin_session = admin_login(&app_ctx).await;
1✔
4169
        let user_id = app_ctx
1✔
4170
            .register_user(UserRegistration {
1✔
4171
                email: "foo@example.com".to_string(),
1✔
4172
                password: "secret123".to_string(),
1✔
4173
                real_name: "Foo Bar".to_string(),
1✔
4174
            })
1✔
4175
            .await
1✔
4176
            .unwrap();
1✔
4177

1✔
4178
        let admin_db = app_ctx.session_context(admin_session.clone()).db();
1✔
4179

4180
        // create a new role
4181
        let role_id = admin_db.add_role("foo").await.unwrap();
1✔
4182

4183
        let user_session = app_ctx
1✔
4184
            .login(UserCredentials {
1✔
4185
                email: "foo@example.com".to_string(),
1✔
4186
                password: "secret123".to_string(),
1✔
4187
            })
1✔
4188
            .await
1✔
4189
            .unwrap();
1✔
4190

1✔
4191
        // user does not have the role yet
1✔
4192

1✔
4193
        assert!(!user_session.roles.contains(&role_id));
1✔
4194

4195
        //user can query their role descriptions (user role and registered user)
4196
        assert_eq!(user_session.roles.len(), 2);
1✔
4197

4198
        let expected_user_role_description = RoleDescription {
1✔
4199
            role: Role {
1✔
4200
                id: RoleId::from(user_id),
1✔
4201
                name: "foo@example.com".to_string(),
1✔
4202
            },
1✔
4203
            individual: true,
1✔
4204
        };
1✔
4205
        let expected_registered_role_description = RoleDescription {
1✔
4206
            role: Role {
1✔
4207
                id: Role::registered_user_role_id(),
1✔
4208
                name: "user".to_string(),
1✔
4209
            },
1✔
4210
            individual: false,
1✔
4211
        };
1✔
4212

4213
        let user_role_descriptions = app_ctx
1✔
4214
            .session_context(user_session.clone())
1✔
4215
            .db()
1✔
4216
            .get_role_descriptions(&user_id)
1✔
4217
            .await
1✔
4218
            .unwrap();
1✔
4219
        assert_eq!(
1✔
4220
            vec![
1✔
4221
                expected_user_role_description.clone(),
1✔
4222
                expected_registered_role_description.clone(),
1✔
4223
            ],
1✔
4224
            user_role_descriptions
1✔
4225
        );
1✔
4226

4227
        // we assign the role to the user
4228
        admin_db.assign_role(&role_id, &user_id).await.unwrap();
1✔
4229

4230
        let user_session = app_ctx
1✔
4231
            .login(UserCredentials {
1✔
4232
                email: "foo@example.com".to_string(),
1✔
4233
                password: "secret123".to_string(),
1✔
4234
            })
1✔
4235
            .await
1✔
4236
            .unwrap();
1✔
4237

1✔
4238
        // should be present now
1✔
4239
        assert!(user_session.roles.contains(&role_id));
1✔
4240

4241
        //user can query their role descriptions (now an additional foo role)
4242
        let expected_foo_role_description = RoleDescription {
1✔
4243
            role: Role {
1✔
4244
                id: role_id,
1✔
4245
                name: "foo".to_string(),
1✔
4246
            },
1✔
4247
            individual: false,
1✔
4248
        };
1✔
4249

4250
        let user_role_descriptions = app_ctx
1✔
4251
            .session_context(user_session.clone())
1✔
4252
            .db()
1✔
4253
            .get_role_descriptions(&user_id)
1✔
4254
            .await
1✔
4255
            .unwrap();
1✔
4256
        assert_eq!(
1✔
4257
            vec![
1✔
4258
                expected_foo_role_description,
1✔
4259
                expected_user_role_description.clone(),
1✔
4260
                expected_registered_role_description.clone(),
1✔
4261
            ],
1✔
4262
            user_role_descriptions
1✔
4263
        );
1✔
4264

4265
        // we revoke it
4266
        admin_db.revoke_role(&role_id, &user_id).await.unwrap();
1✔
4267

4268
        let user_session = app_ctx
1✔
4269
            .login(UserCredentials {
1✔
4270
                email: "foo@example.com".to_string(),
1✔
4271
                password: "secret123".to_string(),
1✔
4272
            })
1✔
4273
            .await
1✔
4274
            .unwrap();
1✔
4275

1✔
4276
        // the role is gone now
1✔
4277
        assert!(!user_session.roles.contains(&role_id));
1✔
4278

4279
        //user can query their role descriptions (user role and registered user)
4280
        let user_role_descriptions = app_ctx
1✔
4281
            .session_context(user_session.clone())
1✔
4282
            .db()
1✔
4283
            .get_role_descriptions(&user_id)
1✔
4284
            .await
1✔
4285
            .unwrap();
1✔
4286
        assert_eq!(
1✔
4287
            vec![
1✔
4288
                expected_user_role_description.clone(),
1✔
4289
                expected_registered_role_description.clone(),
1✔
4290
            ],
1✔
4291
            user_role_descriptions
1✔
4292
        );
1✔
4293

4294
        // assign it again and then delete the whole role, should not be present at user
4295

4296
        admin_db.assign_role(&role_id, &user_id).await.unwrap();
1✔
4297

1✔
4298
        admin_db.remove_role(&role_id).await.unwrap();
1✔
4299

4300
        let user_session = app_ctx
1✔
4301
            .login(UserCredentials {
1✔
4302
                email: "foo@example.com".to_string(),
1✔
4303
                password: "secret123".to_string(),
1✔
4304
            })
1✔
4305
            .await
1✔
4306
            .unwrap();
1✔
4307

1✔
4308
        assert!(!user_session.roles.contains(&role_id));
1✔
4309

4310
        //user can query their role descriptions (user role and registered user)
4311
        let user_role_descriptions = app_ctx
1✔
4312
            .session_context(user_session.clone())
1✔
4313
            .db()
1✔
4314
            .get_role_descriptions(&user_id)
1✔
4315
            .await
1✔
4316
            .unwrap();
1✔
4317
        assert_eq!(
1✔
4318
            vec![
1✔
4319
                expected_user_role_description,
1✔
4320
                expected_registered_role_description.clone(),
1✔
4321
            ],
1✔
4322
            user_role_descriptions
1✔
4323
        );
1✔
4324
    }
1✔
4325

4326
    #[allow(clippy::too_many_lines)]
4327
    #[ge_context::test]
1✔
4328
    async fn it_updates_project_layer_symbology(app_ctx: PostgresContext<NoTls>) {
1✔
4329
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
4330

4331
        let (_, workflow_id) = register_ndvi_workflow_helper(&app_ctx).await;
1✔
4332

4333
        let db = app_ctx.session_context(session.clone()).db();
1✔
4334

1✔
4335
        let create_project: CreateProject = serde_json::from_value(json!({
1✔
4336
            "name": "Default",
1✔
4337
            "description": "Default project",
1✔
4338
            "bounds": {
1✔
4339
                "boundingBox": {
1✔
4340
                    "lowerLeftCoordinate": {
1✔
4341
                        "x": -180,
1✔
4342
                        "y": -90
1✔
4343
                    },
1✔
4344
                    "upperRightCoordinate": {
1✔
4345
                        "x": 180,
1✔
4346
                        "y": 90
1✔
4347
                    }
1✔
4348
                },
1✔
4349
                "spatialReference": "EPSG:4326",
1✔
4350
                "timeInterval": {
1✔
4351
                    "start": 1_396_353_600_000i64,
1✔
4352
                    "end": 1_396_353_600_000i64
1✔
4353
                }
1✔
4354
            },
1✔
4355
            "timeStep": {
1✔
4356
                "step": 1,
1✔
4357
                "granularity": "months"
1✔
4358
            }
1✔
4359
        }))
1✔
4360
        .unwrap();
1✔
4361

4362
        let project_id = db.create_project(create_project).await.unwrap();
1✔
4363

1✔
4364
        let update: UpdateProject = serde_json::from_value(json!({
1✔
4365
            "id": project_id.to_string(),
1✔
4366
            "layers": [{
1✔
4367
                "name": "NDVI",
1✔
4368
                "workflow": workflow_id.to_string(),
1✔
4369
                "visibility": {
1✔
4370
                    "data": true,
1✔
4371
                    "legend": false
1✔
4372
                },
1✔
4373
                "symbology": {
1✔
4374
                    "type": "raster",
1✔
4375
                    "opacity": 1,
1✔
4376
                    "rasterColorizer": {
1✔
4377
                        "type": "singleBand",
1✔
4378
                        "band": 0,
1✔
4379
                        "bandColorizer": {
1✔
4380
                            "type": "linearGradient",
1✔
4381
                            "breakpoints": [{
1✔
4382
                                "value": 1,
1✔
4383
                                "color": [0, 0, 0, 255]
1✔
4384
                            }, {
1✔
4385
                                "value": 255,
1✔
4386
                                "color": [255, 255, 255, 255]
1✔
4387
                            }],
1✔
4388
                            "noDataColor": [0, 0, 0, 0],
1✔
4389
                            "overColor": [255, 255, 255, 127],
1✔
4390
                            "underColor": [255, 255, 255, 127]
1✔
4391
                        }
1✔
4392
                    }
1✔
4393
                }
1✔
4394
            }]
1✔
4395
        }))
1✔
4396
        .unwrap();
1✔
4397

1✔
4398
        db.update_project(update).await.unwrap();
1✔
4399

1✔
4400
        let update: UpdateProject = serde_json::from_value(json!({
1✔
4401
            "id": project_id.to_string(),
1✔
4402
            "layers": [{
1✔
4403
                "name": "NDVI",
1✔
4404
                "workflow": workflow_id.to_string(),
1✔
4405
                "visibility": {
1✔
4406
                    "data": true,
1✔
4407
                    "legend": false
1✔
4408
                },
1✔
4409
                "symbology": {
1✔
4410
                    "type": "raster",
1✔
4411
                    "opacity": 1,
1✔
4412
                    "rasterColorizer": {
1✔
4413
                        "type": "singleBand",
1✔
4414
                        "band": 0,
1✔
4415
                        "bandColorizer": {
1✔
4416
                        "type": "linearGradient",
1✔
4417
                            "breakpoints": [{
1✔
4418
                                "value": 1,
1✔
4419
                                "color": [0, 0, 4, 255]
1✔
4420
                            }, {
1✔
4421
                                "value": 17.866_666_666_666_667,
1✔
4422
                                "color": [11, 9, 36, 255]
1✔
4423
                            }, {
1✔
4424
                                "value": 34.733_333_333_333_334,
1✔
4425
                                "color": [32, 17, 75, 255]
1✔
4426
                            }, {
1✔
4427
                                "value": 51.6,
1✔
4428
                                "color": [59, 15, 112, 255]
1✔
4429
                            }, {
1✔
4430
                                "value": 68.466_666_666_666_67,
1✔
4431
                                "color": [87, 21, 126, 255]
1✔
4432
                            }, {
1✔
4433
                                "value": 85.333_333_333_333_33,
1✔
4434
                                "color": [114, 31, 129, 255]
1✔
4435
                            }, {
1✔
4436
                                "value": 102.199_999_999_999_99,
1✔
4437
                                "color": [140, 41, 129, 255]
1✔
4438
                            }, {
1✔
4439
                                "value": 119.066_666_666_666_65,
1✔
4440
                                "color": [168, 50, 125, 255]
1✔
4441
                            }, {
1✔
4442
                                "value": 135.933_333_333_333_34,
1✔
4443
                                "color": [196, 60, 117, 255]
1✔
4444
                            }, {
1✔
4445
                                "value": 152.799_999_999_999_98,
1✔
4446
                                "color": [222, 73, 104, 255]
1✔
4447
                            }, {
1✔
4448
                                "value": 169.666_666_666_666_66,
1✔
4449
                                "color": [241, 96, 93, 255]
1✔
4450
                            }, {
1✔
4451
                                "value": 186.533_333_333_333_33,
1✔
4452
                                "color": [250, 127, 94, 255]
1✔
4453
                            }, {
1✔
4454
                                "value": 203.399_999_999_999_98,
1✔
4455
                                "color": [254, 159, 109, 255]
1✔
4456
                            }, {
1✔
4457
                                "value": 220.266_666_666_666_65,
1✔
4458
                                "color": [254, 191, 132, 255]
1✔
4459
                            }, {
1✔
4460
                                "value": 237.133_333_333_333_3,
1✔
4461
                                "color": [253, 222, 160, 255]
1✔
4462
                            }, {
1✔
4463
                                "value": 254,
1✔
4464
                                "color": [252, 253, 191, 255]
1✔
4465
                            }],
1✔
4466
                            "noDataColor": [0, 0, 0, 0],
1✔
4467
                            "overColor": [255, 255, 255, 127],
1✔
4468
                            "underColor": [255, 255, 255, 127]
1✔
4469
                        }
1✔
4470
                    }
1✔
4471
                }
1✔
4472
            }]
1✔
4473
        }))
1✔
4474
        .unwrap();
1✔
4475

1✔
4476
        db.update_project(update).await.unwrap();
1✔
4477

1✔
4478
        let update: UpdateProject = serde_json::from_value(json!({
1✔
4479
            "id": project_id.to_string(),
1✔
4480
            "layers": [{
1✔
4481
                "name": "NDVI",
1✔
4482
                "workflow": workflow_id.to_string(),
1✔
4483
                "visibility": {
1✔
4484
                    "data": true,
1✔
4485
                    "legend": false
1✔
4486
                },
1✔
4487
                "symbology": {
1✔
4488
                    "type": "raster",
1✔
4489
                    "opacity": 1,
1✔
4490
                    "rasterColorizer": {
1✔
4491
                        "type": "singleBand",
1✔
4492
                        "band": 0,
1✔
4493
                        "bandColorizer": {
1✔
4494
                            "type": "linearGradient",
1✔
4495
                            "breakpoints": [{
1✔
4496
                                "value": 1,
1✔
4497
                                "color": [0, 0, 4, 255]
1✔
4498
                            }, {
1✔
4499
                                "value": 17.866_666_666_666_667,
1✔
4500
                                "color": [11, 9, 36, 255]
1✔
4501
                            }, {
1✔
4502
                                "value": 34.733_333_333_333_334,
1✔
4503
                                "color": [32, 17, 75, 255]
1✔
4504
                            }, {
1✔
4505
                                "value": 51.6,
1✔
4506
                                "color": [59, 15, 112, 255]
1✔
4507
                            }, {
1✔
4508
                                "value": 68.466_666_666_666_67,
1✔
4509
                                "color": [87, 21, 126, 255]
1✔
4510
                            }, {
1✔
4511
                                "value": 85.333_333_333_333_33,
1✔
4512
                                "color": [114, 31, 129, 255]
1✔
4513
                            }, {
1✔
4514
                                "value": 102.199_999_999_999_99,
1✔
4515
                                "color": [140, 41, 129, 255]
1✔
4516
                            }, {
1✔
4517
                                "value": 119.066_666_666_666_65,
1✔
4518
                                "color": [168, 50, 125, 255]
1✔
4519
                            }, {
1✔
4520
                                "value": 135.933_333_333_333_34,
1✔
4521
                                "color": [196, 60, 117, 255]
1✔
4522
                            }, {
1✔
4523
                                "value": 152.799_999_999_999_98,
1✔
4524
                                "color": [222, 73, 104, 255]
1✔
4525
                            }, {
1✔
4526
                                "value": 169.666_666_666_666_66,
1✔
4527
                                "color": [241, 96, 93, 255]
1✔
4528
                            }, {
1✔
4529
                                "value": 186.533_333_333_333_33,
1✔
4530
                                "color": [250, 127, 94, 255]
1✔
4531
                            }, {
1✔
4532
                                "value": 203.399_999_999_999_98,
1✔
4533
                                "color": [254, 159, 109, 255]
1✔
4534
                            }, {
1✔
4535
                                "value": 220.266_666_666_666_65,
1✔
4536
                                "color": [254, 191, 132, 255]
1✔
4537
                            }, {
1✔
4538
                                "value": 237.133_333_333_333_3,
1✔
4539
                                "color": [253, 222, 160, 255]
1✔
4540
                            }, {
1✔
4541
                                "value": 254,
1✔
4542
                                "color": [252, 253, 191, 255]
1✔
4543
                            }],
1✔
4544
                            "noDataColor": [0, 0, 0, 0],
1✔
4545
                            "overColor": [255, 255, 255, 127],
1✔
4546
                            "underColor": [255, 255, 255, 127]
1✔
4547
                        }
1✔
4548
                    }
1✔
4549
                }
1✔
4550
            }]
1✔
4551
        }))
1✔
4552
        .unwrap();
1✔
4553

1✔
4554
        db.update_project(update).await.unwrap();
1✔
4555

1✔
4556
        let update: UpdateProject = serde_json::from_value(json!({
1✔
4557
            "id": project_id.to_string(),
1✔
4558
            "layers": [{
1✔
4559
                "name": "NDVI",
1✔
4560
                "workflow": workflow_id.to_string(),
1✔
4561
                "visibility": {
1✔
4562
                    "data": true,
1✔
4563
                    "legend": false
1✔
4564
                },
1✔
4565
                "symbology": {
1✔
4566
                    "type": "raster",
1✔
4567
                    "opacity": 1,
1✔
4568
                    "rasterColorizer": {
1✔
4569
                        "type": "singleBand",
1✔
4570
                        "band": 0,
1✔
4571
                        "bandColorizer": {
1✔
4572
                            "type": "linearGradient",
1✔
4573
                            "breakpoints": [{
1✔
4574
                                "value": 1,
1✔
4575
                                "color": [0, 0, 4, 255]
1✔
4576
                            }, {
1✔
4577
                                "value": 17.933_333_333_333_334,
1✔
4578
                                "color": [11, 9, 36, 255]
1✔
4579
                            }, {
1✔
4580
                                "value": 34.866_666_666_666_67,
1✔
4581
                                "color": [32, 17, 75, 255]
1✔
4582
                            }, {
1✔
4583
                                "value": 51.800_000_000_000_004,
1✔
4584
                                "color": [59, 15, 112, 255]
1✔
4585
                            }, {
1✔
4586
                                "value": 68.733_333_333_333_33,
1✔
4587
                                "color": [87, 21, 126, 255]
1✔
4588
                            }, {
1✔
4589
                                "value": 85.666_666_666_666_66,
1✔
4590
                                "color": [114, 31, 129, 255]
1✔
4591
                            }, {
1✔
4592
                                "value": 102.6,
1✔
4593
                                "color": [140, 41, 129, 255]
1✔
4594
                            }, {
1✔
4595
                                "value": 119.533_333_333_333_32,
1✔
4596
                                "color": [168, 50, 125, 255]
1✔
4597
                            }, {
1✔
4598
                                "value": 136.466_666_666_666_67,
1✔
4599
                                "color": [196, 60, 117, 255]
1✔
4600
                            }, {
1✔
4601
                                "value": 153.4,
1✔
4602
                                "color": [222, 73, 104, 255]
1✔
4603
                            }, {
1✔
4604
                                "value": 170.333_333_333_333_31,
1✔
4605
                                "color": [241, 96, 93, 255]
1✔
4606
                            }, {
1✔
4607
                                "value": 187.266_666_666_666_65,
1✔
4608
                                "color": [250, 127, 94, 255]
1✔
4609
                            }, {
1✔
4610
                                "value": 204.2,
1✔
4611
                                "color": [254, 159, 109, 255]
1✔
4612
                            }, {
1✔
4613
                                "value": 221.133_333_333_333_33,
1✔
4614
                                "color": [254, 191, 132, 255]
1✔
4615
                            }, {
1✔
4616
                                "value": 238.066_666_666_666_63,
1✔
4617
                                "color": [253, 222, 160, 255]
1✔
4618
                            }, {
1✔
4619
                                "value": 255,
1✔
4620
                                "color": [252, 253, 191, 255]
1✔
4621
                            }],
1✔
4622
                            "noDataColor": [0, 0, 0, 0],
1✔
4623
                            "overColor": [255, 255, 255, 127],
1✔
4624
                            "underColor": [255, 255, 255, 127]
1✔
4625
                        }
1✔
4626
                    }
1✔
4627
                }
1✔
4628
            }]
1✔
4629
        }))
1✔
4630
        .unwrap();
1✔
4631

4632
        // run two updates concurrently
4633
        let (r0, r1) = join!(db.update_project(update.clone()), db.update_project(update));
1✔
4634

4635
        assert!(r0.is_ok());
1✔
4636
        assert!(r1.is_ok());
1✔
4637
    }
1✔
4638

4639
    #[ge_context::test]
1✔
4640
    #[allow(clippy::too_many_lines)]
4641
    async fn it_resolves_dataset_names_to_ids(app_ctx: PostgresContext<NoTls>) {
1✔
4642
        let admin_session = UserSession::admin_session();
1✔
4643
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
4644

1✔
4645
        let loading_info = OgrSourceDataset {
1✔
4646
            file_name: PathBuf::from("test.csv"),
1✔
4647
            layer_name: "test.csv".to_owned(),
1✔
4648
            data_type: Some(VectorDataType::MultiPoint),
1✔
4649
            time: OgrSourceDatasetTimeType::Start {
1✔
4650
                start_field: "start".to_owned(),
1✔
4651
                start_format: OgrSourceTimeFormat::Auto,
1✔
4652
                duration: OgrSourceDurationSpec::Zero,
1✔
4653
            },
1✔
4654
            default_geometry: None,
1✔
4655
            columns: Some(OgrSourceColumnSpec {
1✔
4656
                format_specifics: Some(FormatSpecifics::Csv {
1✔
4657
                    header: CsvHeader::Auto,
1✔
4658
                }),
1✔
4659
                x: "x".to_owned(),
1✔
4660
                y: None,
1✔
4661
                int: vec![],
1✔
4662
                float: vec![],
1✔
4663
                text: vec![],
1✔
4664
                bool: vec![],
1✔
4665
                datetime: vec![],
1✔
4666
                rename: None,
1✔
4667
            }),
1✔
4668
            force_ogr_time_filter: false,
1✔
4669
            force_ogr_spatial_filter: false,
1✔
4670
            on_error: OgrSourceErrorSpec::Ignore,
1✔
4671
            sql_query: None,
1✔
4672
            attribute_query: None,
1✔
4673
            cache_ttl: CacheTtlSeconds::default(),
1✔
4674
        };
1✔
4675

1✔
4676
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
4677
            OgrSourceDataset,
1✔
4678
            VectorResultDescriptor,
1✔
4679
            VectorQueryRectangle,
1✔
4680
        > {
1✔
4681
            loading_info: loading_info.clone(),
1✔
4682
            result_descriptor: VectorResultDescriptor {
1✔
4683
                data_type: VectorDataType::MultiPoint,
1✔
4684
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
4685
                columns: [(
1✔
4686
                    "foo".to_owned(),
1✔
4687
                    VectorColumnInfo {
1✔
4688
                        data_type: FeatureDataType::Float,
1✔
4689
                        measurement: Measurement::Unitless,
1✔
4690
                    },
1✔
4691
                )]
1✔
4692
                .into_iter()
1✔
4693
                .collect(),
1✔
4694
                time: None,
1✔
4695
                bbox: None,
1✔
4696
            },
1✔
4697
            phantom: Default::default(),
1✔
4698
        });
1✔
4699

4700
        let DatasetIdAndName {
4701
            id: dataset_id1,
1✔
4702
            name: dataset_name1,
1✔
4703
        } = db
1✔
4704
            .add_dataset(
1✔
4705
                AddDataset {
1✔
4706
                    name: Some(DatasetName::new(None, "my_dataset".to_owned())),
1✔
4707
                    display_name: "Ogr Test".to_owned(),
1✔
4708
                    description: "desc".to_owned(),
1✔
4709
                    source_operator: "OgrSource".to_owned(),
1✔
4710
                    symbology: None,
1✔
4711
                    provenance: Some(vec![Provenance {
1✔
4712
                        citation: "citation".to_owned(),
1✔
4713
                        license: "license".to_owned(),
1✔
4714
                        uri: "uri".to_owned(),
1✔
4715
                    }]),
1✔
4716
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
4717
                },
1✔
4718
                meta_data.clone(),
1✔
4719
            )
1✔
4720
            .await
1✔
4721
            .unwrap();
1✔
4722

4723
        let DatasetIdAndName {
4724
            id: dataset_id2,
1✔
4725
            name: dataset_name2,
1✔
4726
        } = db
1✔
4727
            .add_dataset(
1✔
4728
                AddDataset {
1✔
4729
                    name: Some(DatasetName::new(
1✔
4730
                        Some(admin_session.user.id.to_string()),
1✔
4731
                        "my_dataset".to_owned(),
1✔
4732
                    )),
1✔
4733
                    display_name: "Ogr Test".to_owned(),
1✔
4734
                    description: "desc".to_owned(),
1✔
4735
                    source_operator: "OgrSource".to_owned(),
1✔
4736
                    symbology: None,
1✔
4737
                    provenance: Some(vec![Provenance {
1✔
4738
                        citation: "citation".to_owned(),
1✔
4739
                        license: "license".to_owned(),
1✔
4740
                        uri: "uri".to_owned(),
1✔
4741
                    }]),
1✔
4742
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
4743
                },
1✔
4744
                meta_data,
1✔
4745
            )
1✔
4746
            .await
1✔
4747
            .unwrap();
1✔
4748

4749
        assert_eq!(
1✔
4750
            db.resolve_dataset_name_to_id(&dataset_name1)
1✔
4751
                .await
1✔
4752
                .unwrap()
1✔
4753
                .unwrap(),
1✔
4754
            dataset_id1
4755
        );
4756
        assert_eq!(
1✔
4757
            db.resolve_dataset_name_to_id(&dataset_name2)
1✔
4758
                .await
1✔
4759
                .unwrap()
1✔
4760
                .unwrap(),
1✔
4761
            dataset_id2
4762
        );
4763
    }
1✔
4764

4765
    #[ge_context::test]
1✔
4766
    #[allow(clippy::too_many_lines)]
4767
    async fn it_bulk_updates_quota(app_ctx: PostgresContext<NoTls>) {
1✔
4768
        let admin_session = UserSession::admin_session();
1✔
4769
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
4770

4771
        let user1 = app_ctx
1✔
4772
            .register_user(UserRegistration {
1✔
4773
                email: "user1@example.com".into(),
1✔
4774
                password: "12345678".into(),
1✔
4775
                real_name: "User1".into(),
1✔
4776
            })
1✔
4777
            .await
1✔
4778
            .unwrap();
1✔
4779

4780
        let user2 = app_ctx
1✔
4781
            .register_user(UserRegistration {
1✔
4782
                email: "user2@example.com".into(),
1✔
4783
                password: "12345678".into(),
1✔
4784
                real_name: "User2".into(),
1✔
4785
            })
1✔
4786
            .await
1✔
4787
            .unwrap();
1✔
4788

1✔
4789
        // single item in bulk
1✔
4790
        db.bulk_increment_quota_used([(user1, 1)]).await.unwrap();
1✔
4791

4792
        assert_eq!(db.quota_used_by_user(&user1).await.unwrap(), 1);
1✔
4793

4794
        // multiple items in bulk
4795
        db.bulk_increment_quota_used([(user1, 1), (user2, 3)])
1✔
4796
            .await
1✔
4797
            .unwrap();
1✔
4798

4799
        assert_eq!(db.quota_used_by_user(&user1).await.unwrap(), 2);
1✔
4800
        assert_eq!(db.quota_used_by_user(&user2).await.unwrap(), 3);
1✔
4801
    }
1✔
4802

4803
    async fn it_handles_oidc_tokens(app_ctx: PostgresContext<NoTls>) {
2✔
4804
        let external_user_claims = UserClaims {
2✔
4805
            external_id: SubjectIdentifier::new("Foo bar Id".into()),
2✔
4806
            email: "foo@bar.de".into(),
2✔
4807
            real_name: "Foo Bar".into(),
2✔
4808
        };
2✔
4809
        let tokens = OidcTokens {
2✔
4810
            access: AccessToken::new("FIRST_ACCESS_TOKEN".into()),
2✔
4811
            refresh: Some(RefreshToken::new("FIRST_REFRESH_TOKEN".into())),
2✔
4812
            expires_in: Duration::seconds(2),
2✔
4813
        };
2✔
4814

4815
        let login_result = app_ctx
2✔
4816
            .login_external(external_user_claims.clone(), tokens)
2✔
4817
            .await;
2✔
4818
        assert!(login_result.is_ok());
2✔
4819

4820
        let session_id = login_result.unwrap().id;
2✔
4821

4822
        let access_token = app_ctx.get_access_token(session_id).await.unwrap();
2✔
4823

2✔
4824
        assert_eq!(
2✔
4825
            "FIRST_ACCESS_TOKEN".to_string(),
2✔
4826
            access_token.secret().to_owned()
2✔
4827
        );
2✔
4828

4829
        //Access token duration oidc_login_refresh is 2 sec, i.e., session times out after 2 sec.
4830
        tokio::time::sleep(std::time::Duration::from_secs(5)).await;
2✔
4831

4832
        let access_token = app_ctx.get_access_token(session_id).await.unwrap();
2✔
4833

2✔
4834
        assert_eq!(
2✔
4835
            "SECOND_ACCESS_TOKEN".to_string(),
2✔
4836
            access_token.secret().to_owned()
2✔
4837
        );
2✔
4838
    }
2✔
4839

4840
    pub fn oidc_only_refresh() -> (Server, impl Fn() -> OidcManager) {
1✔
4841
        let mock_refresh_server_config = MockRefreshServerConfig {
1✔
4842
            expected_discoveries: 1,
1✔
4843
            token_duration: std::time::Duration::from_secs(2),
1✔
4844
            creates_first_token: false,
1✔
4845
            first_access_token: "FIRST_ACCESS_TOKEN".to_string(),
1✔
4846
            first_refresh_token: "FIRST_REFRESH_TOKEN".to_string(),
1✔
4847
            second_access_token: "SECOND_ACCESS_TOKEN".to_string(),
1✔
4848
            second_refresh_token: "SECOND_REFRESH_TOKEN".to_string(),
1✔
4849
            client_side_password: None,
1✔
4850
        };
1✔
4851

1✔
4852
        let (server, oidc_manager) = mock_refresh_server(mock_refresh_server_config);
1✔
4853

1✔
4854
        (server, move || {
1✔
4855
            OidcManager::from_oidc_with_static_tokens(oidc_manager.clone())
1✔
4856
        })
1✔
4857
    }
1✔
4858

4859
    #[ge_context::test(oidc_db = "oidc_only_refresh")]
1✔
4860
    async fn it_handles_oidc_tokens_without_encryption(app_ctx: PostgresContext<NoTls>) {
1✔
4861
        it_handles_oidc_tokens(app_ctx).await;
1✔
4862
    }
1✔
4863

4864
    pub fn oidc_only_refresh_with_encryption() -> (Server, impl Fn() -> OidcManager) {
1✔
4865
        let mock_refresh_server_config = MockRefreshServerConfig {
1✔
4866
            expected_discoveries: 1,
1✔
4867
            token_duration: std::time::Duration::from_secs(2),
1✔
4868
            creates_first_token: false,
1✔
4869
            first_access_token: "FIRST_ACCESS_TOKEN".to_string(),
1✔
4870
            first_refresh_token: "FIRST_REFRESH_TOKEN".to_string(),
1✔
4871
            second_access_token: "SECOND_ACCESS_TOKEN".to_string(),
1✔
4872
            second_refresh_token: "SECOND_REFRESH_TOKEN".to_string(),
1✔
4873
            client_side_password: Some("password123".to_string()),
1✔
4874
        };
1✔
4875

1✔
4876
        let (server, oidc_manager) = mock_refresh_server(mock_refresh_server_config);
1✔
4877

1✔
4878
        (server, move || {
1✔
4879
            OidcManager::from_oidc_with_static_tokens(oidc_manager.clone())
1✔
4880
        })
1✔
4881
    }
1✔
4882

4883
    #[ge_context::test(oidc_db = "oidc_only_refresh_with_encryption")]
1✔
4884
    async fn it_handles_oidc_tokens_with_encryption(app_ctx: PostgresContext<NoTls>) {
1✔
4885
        it_handles_oidc_tokens(app_ctx).await;
1✔
4886
    }
1✔
4887

4888
    #[ge_context::test]
1✔
4889
    #[allow(clippy::too_many_lines)]
4890
    async fn it_resolves_ml_model_names_to_ids(app_ctx: PostgresContext<NoTls>) {
1✔
4891
        let admin_session = UserSession::admin_session();
1✔
4892
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
4893

1✔
4894
        let upload_id = UploadId::new();
1✔
4895
        let upload = Upload {
1✔
4896
            id: upload_id,
1✔
4897
            files: vec![],
1✔
4898
        };
1✔
4899
        db.create_upload(upload).await.unwrap();
1✔
4900

1✔
4901
        let model = MlModel {
1✔
4902
            description: "No real model here".to_owned(),
1✔
4903
            display_name: "my unreal model".to_owned(),
1✔
4904
            metadata: MlModelMetadata {
1✔
4905
                file_name: "myUnrealmodel.onnx".to_owned(),
1✔
4906
                input_type: ApiRasterDataType::F32,
1✔
4907
                input_shape: TensorShape3D::new_single_pixel_bands(17),
1✔
4908
                output_shape: TensorShape3D::new_single_pixel_single_band(),
1✔
4909
                output_type: ApiRasterDataType::F64,
1✔
4910
            },
1✔
4911
            name: MlModelName::new(None, "myUnrealModel"),
1✔
4912
            upload: upload_id,
1✔
4913
        };
1✔
4914

4915
        let MlModelIdAndName {
4916
            id: model_id,
1✔
4917
            name: model_name,
1✔
4918
        } = db.add_model(model).await.unwrap();
1✔
4919

4920
        assert_eq!(
1✔
4921
            db.resolve_model_name_to_id(&model_name)
1✔
4922
                .await
1✔
4923
                .unwrap()
1✔
4924
                .unwrap(),
1✔
4925
            model_id
4926
        );
4927
    }
1✔
4928
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc