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

geo-engine / geoengine / 13719457298

07 Mar 2025 11:11AM UTC coverage: 90.08% (+0.004%) from 90.076%
13719457298

Pull #1026

github

web-flow
Merge d09cf15d1 into c96026921
Pull Request #1026: Ubuntu 24 LTS

2350 of 2476 new or added lines in 108 files covered. (94.91%)

6 existing lines in 4 files now uncovered.

126337 of 140250 relevant lines covered (90.08%)

57391.91 hits per line

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

96.88
/services/src/contexts/postgres.rs
1
use self::migrations::all_migrations;
2
use crate::api::model::services::Volume;
3
use crate::config::{Cache, Oidc, Quota, get_config_element};
4
use crate::contexts::{
5
    ApplicationContext, CurrentSchemaMigration, MigrationResult, QueryContextImpl, SessionId,
6
    initialize_database, migrations,
7
};
8
use crate::contexts::{ExecutionContextImpl, QuotaCheckerImpl};
9
use crate::contexts::{GeoEngineDb, SessionContext};
10
use crate::datasets::DatasetName;
11
use crate::datasets::upload::Volumes;
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::{QuotaTrackingFactory, initialize_quota_tracking};
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
    PostgresConnectionManager,
27
    bb8::Pool,
28
    bb8::PooledConnection,
29
    tokio_postgres::{Config, Socket, tls::MakeTlsConnect, tls::TlsConnect},
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 => {
×
NEW
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)
168✔
308
            .context(error::Unauthorized)
168✔
309
    }
342✔
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;
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
        INTERNAL_PROVIDER_ID, LayerDb, LayerProviderDb, LayerProviderListing,
485
        LayerProviderListingOptions,
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::{MockRefreshServerConfig, mock_refresh_server};
497
    use crate::util::tests::{MockQuotaTracking, admin_login, register_ndvi_workflow_helper};
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!(
2✔
663
            db.has_permission(project_id, Permission::Owner)
2✔
664
                .await
2✔
665
                .unwrap()
2✔
666
        );
667

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

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

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

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

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

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

711
        let project = db
2✔
712
            .load_project_version(project_id, LoadVersion::Latest)
2✔
713
            .await
2✔
714
            .unwrap();
2✔
715

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

2✔
730
        assert!(db.load_workflow(&layer_workflow_id).await.is_ok());
2✔
731

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

2✔
749
        assert!(db.load_workflow(&plot_workflow_id).await.is_ok());
2✔
750

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

771
        let versions = db.list_project_versions(project_id).await.unwrap();
2✔
772
        assert_eq!(versions.len(), 2);
2✔
773

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

800
        let versions = db.list_project_versions(project_id).await.unwrap();
2✔
801
        assert_eq!(versions.len(), 3);
2✔
802

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

815
        let versions = db.list_project_versions(project_id).await.unwrap();
2✔
816
        assert_eq!(versions.len(), 4);
2✔
817
    }
2✔
818

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

2✔
829
        let db = app_ctx.session_context(session.clone()).db();
2✔
830

831
        let projects = db.list_projects(options).await.unwrap();
2✔
832

2✔
833
        assert_eq!(projects.len(), 2);
2✔
834
        assert_eq!(projects[0].name, "Test9");
2✔
835
        assert_eq!(projects[1].name, "Test8");
2✔
836
        projects
2✔
837
    }
2✔
838

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

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

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

869
        let user_id = app_ctx.register_user(user_registration).await.unwrap();
1✔
870

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

876
        let session = app_ctx.login(credentials).await.unwrap();
1✔
877

1✔
878
        let db = app_ctx.session_context(session.clone()).db();
1✔
879

1✔
880
        app_ctx.session_by_id(session.id).await.unwrap();
1✔
881

1✔
882
        db.logout().await.unwrap();
1✔
883

1✔
884
        assert!(app_ctx.session_by_id(session.id).await.is_err());
1✔
885

886
        user_id
1✔
887
    }
1✔
888

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

897
        let login_result = app_ctx
1✔
898
            .login_external(external_user_claims.clone(), tokens_from_duration(duration))
1✔
899
            .await;
1✔
900
        assert!(login_result.is_ok());
1✔
901

902
        let session_1 = login_result.unwrap();
1✔
903
        let user_id = session_1.user.id; //TODO: Not a deterministic test.
1✔
904

1✔
905
        let db1 = app_ctx.session_context(session_1.clone()).db();
1✔
906

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

912
        let expected_duration = session_1.created + duration;
1✔
913
        assert_eq!(session_1.valid_until, expected_duration);
1✔
914

915
        assert!(app_ctx.session_by_id(session_1.id).await.is_ok());
1✔
916

917
        assert!(db1.logout().await.is_ok());
1✔
918

919
        assert!(app_ctx.session_by_id(session_1.id).await.is_err());
1✔
920

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

927
        let session_2 = login_result.unwrap();
1✔
928
        let result = session_2.clone();
1✔
929

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

936
        let expected_duration = session_2.created + duration;
1✔
937
        assert_eq!(session_2.valid_until, expected_duration);
1✔
938

939
        assert!(app_ctx.session_by_id(session_2.id).await.is_ok());
1✔
940

941
        result
1✔
942
    }
1✔
943

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

2✔
949
        assert!(session.created >= now && session.created <= then);
2✔
950
        assert!(session.valid_until > session.created);
2✔
951

952
        let session = app_ctx.session_by_id(session.id).await.unwrap();
2✔
953

2✔
954
        let db = app_ctx.session_context(session.clone()).db();
2✔
955

2✔
956
        db.logout().await.unwrap();
2✔
957

2✔
958
        assert!(app_ctx.session_by_id(session.id).await.is_err());
2✔
959
    }
2✔
960

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

974
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
975
        let ctx = app_ctx.session_context(session);
1✔
976

1✔
977
        let db = ctx.db();
1✔
978
        let id = db.register_workflow(workflow).await.unwrap();
1✔
979

1✔
980
        drop(ctx);
1✔
981

982
        let workflow = db.load_workflow(&id).await.unwrap();
1✔
983

1✔
984
        let json = serde_json::to_string(&workflow).unwrap();
1✔
985
        assert_eq!(
1✔
986
            json,
1✔
987
            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✔
988
        );
1✔
989
    }
1✔
990

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

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

1049
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
1050

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

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

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

1✔
1088
        assert_eq!(datasets.len(), 1);
1✔
1089

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

1118
        let provenance = db.load_provenance(&dataset_id).await.unwrap();
1✔
1119

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

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

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

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

1164
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
1165

1✔
1166
        let db = app_ctx.session_context(session.clone()).db();
1✔
1167

1✔
1168
        db.create_upload(input.clone()).await.unwrap();
1✔
1169

1170
        let upload = db.load_upload(id).await.unwrap();
1✔
1171

1✔
1172
        assert_eq!(upload, input);
1✔
1173
    }
1✔
1174

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

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

1189
        let provider_id = db.add_layer_provider(provider.into()).await.unwrap();
1✔
1190

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

1✔
1199
        assert_eq!(providers.len(), 1);
1✔
1200

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

1210
        let provider = db.load_layer_provider(provider_id).await.unwrap();
1✔
1211

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

1✔
1223
        assert_eq!(datasets.items.len(), 5);
1✔
1224
    }
1✔
1225

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

1✔
1231
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1232
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1233

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

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

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

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

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

1✔
1284
        assert_eq!(list1.len(), 1);
1✔
1285

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

1✔
1297
        assert_eq!(list2.len(), 0);
1✔
1298
    }
1✔
1299

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

1✔
1305
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1306
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1307

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

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

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

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

1✔
1347
        assert!(db1.load_provenance(&id).await.is_ok());
1✔
1348

1349
        assert!(db2.load_provenance(&id).await.is_err());
1✔
1350
    }
1✔
1351

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

1✔
1357
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1358
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1359

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

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

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

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

1✔
1399
        assert!(db1.load_dataset(&id).await.is_ok());
1✔
1400

1401
        assert!(db2.load_dataset(&id).await.is_err());
1✔
1402

1403
        db1.add_permission(session2.user.id.into(), id, Permission::Read)
1✔
1404
            .await
1✔
1405
            .unwrap();
1✔
1406

1✔
1407
        assert!(db2.load_dataset(&id).await.is_ok());
1✔
1408
    }
1✔
1409

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

1✔
1415
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1416
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1417

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

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

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

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

1✔
1457
        assert!(db1.load_dataset(&id).await.is_ok());
1✔
1458

1459
        assert!(db2.load_dataset(&id).await.is_err());
1✔
1460

1461
        db1.add_permission(session2.user.id.into(), id, Permission::Read)
1✔
1462
            .await
1✔
1463
            .unwrap();
1✔
1464

1✔
1465
        assert!(db2.load_dataset(&id).await.is_ok());
1✔
1466
    }
1✔
1467

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

1✔
1473
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1474
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1475

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

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

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

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

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

1519
        assert!(meta.is_ok());
1✔
1520

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

1525
        assert!(meta.is_err());
1✔
1526

1527
        db1.add_permission(session2.user.id.into(), id, Permission::Read)
1✔
1528
            .await
1✔
1529
            .unwrap();
1✔
1530

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

1535
        assert!(meta.is_ok());
1✔
1536
    }
1✔
1537

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

1✔
1543
        let db = app_ctx.session_context(session.clone()).db();
1✔
1544

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

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

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

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

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

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

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

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

1626
        assert!(meta.is_ok());
1✔
1627

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

1640
        let id = db
1✔
1641
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1642
            .await
1✔
1643
            .unwrap()
1✔
1644
            .id;
1645

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

1650
        assert!(meta.is_ok());
1✔
1651

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

1659
        let id = db
1✔
1660
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1661
            .await
1✔
1662
            .unwrap()
1✔
1663
            .id;
1664

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

1669
        assert!(meta.is_ok());
1✔
1670

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

1676
        let id = db
1✔
1677
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1678
            .await
1✔
1679
            .unwrap()
1✔
1680
            .id;
1681

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

1686
        assert!(meta.is_ok());
1✔
1687

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

1701
        let id = db
1✔
1702
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1703
            .await
1✔
1704
            .unwrap()
1✔
1705
            .id;
1706

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

1711
        assert!(meta.is_ok());
1✔
1712
    }
1✔
1713

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

1✔
1719
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1720
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1721

1✔
1722
        let upload_id = UploadId::new();
1✔
1723

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

1✔
1733
        db1.create_upload(upload).await.unwrap();
1✔
1734

1✔
1735
        assert!(db1.load_upload(upload_id).await.is_ok());
1✔
1736

1737
        assert!(db2.load_upload(upload_id).await.is_err());
1✔
1738
    }
1✔
1739

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

1745
        let layer_db = app_ctx.session_context(session).db();
1✔
1746

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

1758
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1759

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

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

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

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

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

1✔
1830
        layer_db
1✔
1831
            .add_collection_to_parent(&collection2_id, &collection1_id)
1✔
1832
            .await
1✔
1833
            .unwrap();
1✔
1834

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

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

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

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

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

1940
        let layer_db = app_ctx.session_context(session).db();
1✔
1941

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

1953
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1954

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2286
        let user_session = app_ctx.create_anonymous_session().await.unwrap();
1✔
2287
        let user_layer_db = app_ctx.session_context(user_session.clone()).db();
1✔
2288

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

2300
        let root_collection_id = admin_layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2301

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2791
        let layer_db = app_ctx.session_context(session).db();
1✔
2792

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

2804
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2805

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

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

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

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

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

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

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

1✔
2897
        assert_eq!(
1✔
2898
            root_collection_filtered,
1✔
2899
            vec!["Collection1".to_string(), "Collection2".to_string(),]
1✔
2900
        );
1✔
2901

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

1✔
2915
        assert_eq!(
1✔
2916
            collection1_all,
1✔
2917
            vec!["Collection2".to_string(), "Layer2".to_string(),]
1✔
2918
        );
1✔
2919

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

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

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

1✔
2948
        assert_eq!(collection1_filtered_prefix, Vec::<String>::new());
1✔
2949

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

1✔
2963
        assert_eq!(collection1_filtered_prefix2, vec!["Layer2".to_string(),]);
1✔
2964
    }
1✔
2965

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

2972
        let user_session = app_ctx.create_anonymous_session().await.unwrap();
1✔
2973
        let user_layer_db = app_ctx.session_context(user_session.clone()).db();
1✔
2974

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

2986
        let root_collection_id = admin_layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2987

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
3170
        assert_eq!(
1✔
3171
            root_collection_filtered,
1✔
3172
            vec!["Collection1".to_string(), "Collection2".to_string(),]
1✔
3173
        );
1✔
3174

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

1✔
3188
        assert_eq!(
1✔
3189
            collection1_all,
1✔
3190
            vec!["Collection2".to_string(), "Layer2".to_string(),]
1✔
3191
        );
1✔
3192

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

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

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

1✔
3221
        assert_eq!(collection1_filtered_prefix, Vec::<String>::new());
1✔
3222

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

1✔
3236
        assert_eq!(collection1_filtered_prefix2, vec!["Layer2".to_string(),]);
1✔
3237
    }
1✔
3238

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

3244
        let layer_db = app_ctx.session_context(session).db();
1✔
3245

1✔
3246
        let capabilities = layer_db.capabilities().search;
1✔
3247

3248
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
3249

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

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

3314
            if capabilities.autocomplete {
1✔
3315
                assert!(
1✔
3316
                    layer_db
1✔
3317
                        .autocomplete_search(
1✔
3318
                            &root_collection_id,
1✔
3319
                            SearchParameters {
1✔
3320
                                search_type: SearchType::Prefix,
1✔
3321
                                search_string: String::new(),
1✔
3322
                                limit: 10,
1✔
3323
                                offset: 0,
1✔
3324
                            },
1✔
3325
                        )
1✔
3326
                        .await
1✔
3327
                        .is_ok()
1✔
3328
                );
3329
            } else {
NEW
3330
                assert!(
×
NEW
3331
                    layer_db
×
NEW
3332
                        .autocomplete_search(
×
NEW
3333
                            &root_collection_id,
×
NEW
3334
                            SearchParameters {
×
NEW
3335
                                search_type: SearchType::Prefix,
×
NEW
3336
                                search_string: String::new(),
×
NEW
3337
                                limit: 10,
×
NEW
3338
                                offset: 0,
×
NEW
3339
                            },
×
NEW
3340
                        )
×
NEW
3341
                        .await
×
NEW
3342
                        .is_err()
×
3343
                );
3344
            }
3345
        }
×
3346
    }
1✔
3347

3348
    #[ge_context::test]
1✔
3349
    async fn it_tracks_used_quota_in_postgres(app_ctx: PostgresContext<NoTls>) {
1✔
3350
        let _user = app_ctx
1✔
3351
            .register_user(UserRegistration {
1✔
3352
                email: "foo@example.com".to_string(),
1✔
3353
                password: "secret1234".to_string(),
1✔
3354
                real_name: "Foo Bar".to_string(),
1✔
3355
            })
1✔
3356
            .await
1✔
3357
            .unwrap();
1✔
3358

3359
        let session = app_ctx
1✔
3360
            .login(UserCredentials {
1✔
3361
                email: "foo@example.com".to_string(),
1✔
3362
                password: "secret1234".to_string(),
1✔
3363
            })
1✔
3364
            .await
1✔
3365
            .unwrap();
1✔
3366

3367
        let admin_session = admin_login(&app_ctx).await;
1✔
3368

3369
        let quota = initialize_quota_tracking(
1✔
3370
            QuotaTrackingMode::Check,
1✔
3371
            app_ctx.session_context(admin_session).db(),
1✔
3372
            0,
1✔
3373
            60,
1✔
3374
        );
1✔
3375

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

1✔
3378
        tracking.mock_work_unit_done();
1✔
3379
        tracking.mock_work_unit_done();
1✔
3380

1✔
3381
        let db = app_ctx.session_context(session).db();
1✔
3382

1✔
3383
        // wait for quota to be recorded
1✔
3384
        let mut success = false;
1✔
3385
        for _ in 0..10 {
3✔
3386
            let used = db.quota_used().await.unwrap();
3✔
3387
            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
3✔
3388

3389
            if used == 2 {
3✔
3390
                success = true;
1✔
3391
                break;
1✔
3392
            }
2✔
3393
        }
3394

3395
        assert!(success);
1✔
3396
    }
1✔
3397

3398
    #[ge_context::test]
1✔
3399
    async fn it_tracks_available_quota(app_ctx: PostgresContext<NoTls>) {
1✔
3400
        let user = app_ctx
1✔
3401
            .register_user(UserRegistration {
1✔
3402
                email: "foo@example.com".to_string(),
1✔
3403
                password: "secret1234".to_string(),
1✔
3404
                real_name: "Foo Bar".to_string(),
1✔
3405
            })
1✔
3406
            .await
1✔
3407
            .unwrap();
1✔
3408

3409
        let session = app_ctx
1✔
3410
            .login(UserCredentials {
1✔
3411
                email: "foo@example.com".to_string(),
1✔
3412
                password: "secret1234".to_string(),
1✔
3413
            })
1✔
3414
            .await
1✔
3415
            .unwrap();
1✔
3416

3417
        let admin_session = admin_login(&app_ctx).await;
1✔
3418

3419
        app_ctx
1✔
3420
            .session_context(admin_session.clone())
1✔
3421
            .db()
1✔
3422
            .update_quota_available_by_user(&user, 1)
1✔
3423
            .await
1✔
3424
            .unwrap();
1✔
3425

1✔
3426
        let quota = initialize_quota_tracking(
1✔
3427
            QuotaTrackingMode::Check,
1✔
3428
            app_ctx.session_context(admin_session).db(),
1✔
3429
            0,
1✔
3430
            60,
1✔
3431
        );
1✔
3432

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

1✔
3435
        tracking.mock_work_unit_done();
1✔
3436
        tracking.mock_work_unit_done();
1✔
3437

1✔
3438
        let db = app_ctx.session_context(session).db();
1✔
3439

1✔
3440
        // wait for quota to be recorded
1✔
3441
        let mut success = false;
1✔
3442
        for _ in 0..10 {
3✔
3443
            let available = db.quota_available().await.unwrap();
3✔
3444
            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
3✔
3445

3446
            if available == -1 {
3✔
3447
                success = true;
1✔
3448
                break;
1✔
3449
            }
2✔
3450
        }
3451

3452
        assert!(success);
1✔
3453
    }
1✔
3454

3455
    #[ge_context::test]
1✔
3456
    async fn it_updates_quota_in_postgres(app_ctx: PostgresContext<NoTls>) {
1✔
3457
        let user = app_ctx
1✔
3458
            .register_user(UserRegistration {
1✔
3459
                email: "foo@example.com".to_string(),
1✔
3460
                password: "secret1234".to_string(),
1✔
3461
                real_name: "Foo Bar".to_string(),
1✔
3462
            })
1✔
3463
            .await
1✔
3464
            .unwrap();
1✔
3465

3466
        let session = app_ctx
1✔
3467
            .login(UserCredentials {
1✔
3468
                email: "foo@example.com".to_string(),
1✔
3469
                password: "secret1234".to_string(),
1✔
3470
            })
1✔
3471
            .await
1✔
3472
            .unwrap();
1✔
3473

1✔
3474
        let db = app_ctx.session_context(session.clone()).db();
1✔
3475
        let admin_db = app_ctx.session_context(UserSession::admin_session()).db();
1✔
3476

3477
        assert_eq!(
1✔
3478
            db.quota_available().await.unwrap(),
1✔
3479
            crate::config::get_config_element::<crate::config::Quota>()
1✔
3480
                .unwrap()
1✔
3481
                .initial_credits
3482
        );
3483

3484
        assert_eq!(
1✔
3485
            admin_db.quota_available_by_user(&user).await.unwrap(),
1✔
3486
            crate::config::get_config_element::<crate::config::Quota>()
1✔
3487
                .unwrap()
1✔
3488
                .initial_credits
3489
        );
3490

3491
        admin_db
1✔
3492
            .update_quota_available_by_user(&user, 123)
1✔
3493
            .await
1✔
3494
            .unwrap();
1✔
3495

3496
        assert_eq!(db.quota_available().await.unwrap(), 123);
1✔
3497

3498
        assert_eq!(admin_db.quota_available_by_user(&user).await.unwrap(), 123);
1✔
3499
    }
1✔
3500

3501
    #[allow(clippy::too_many_lines)]
3502
    #[ge_context::test]
1✔
3503
    async fn it_removes_layer_collections(app_ctx: PostgresContext<NoTls>) {
1✔
3504
        let session = admin_login(&app_ctx).await;
1✔
3505

3506
        let layer_db = app_ctx.session_context(session).db();
1✔
3507

1✔
3508
        let layer = AddLayer {
1✔
3509
            name: "layer".to_string(),
1✔
3510
            description: "description".to_string(),
1✔
3511
            workflow: Workflow {
1✔
3512
                operator: TypedOperator::Vector(
1✔
3513
                    MockPointSource {
1✔
3514
                        params: MockPointSourceParams {
1✔
3515
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3516
                        },
1✔
3517
                    }
1✔
3518
                    .boxed(),
1✔
3519
                ),
1✔
3520
            },
1✔
3521
            symbology: None,
1✔
3522
            metadata: Default::default(),
1✔
3523
            properties: Default::default(),
1✔
3524
        };
1✔
3525

3526
        let root_collection = &layer_db.get_root_layer_collection_id().await.unwrap();
1✔
3527

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

3534
        let top_c_id = layer_db
1✔
3535
            .add_layer_collection(collection, root_collection)
1✔
3536
            .await
1✔
3537
            .unwrap();
1✔
3538

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

1✔
3541
        let collection = AddLayerCollection {
1✔
3542
            name: "empty collection".to_string(),
1✔
3543
            description: "description".to_string(),
1✔
3544
            properties: Default::default(),
1✔
3545
        };
1✔
3546

3547
        let empty_c_id = layer_db
1✔
3548
            .add_layer_collection(collection, &top_c_id)
1✔
3549
            .await
1✔
3550
            .unwrap();
1✔
3551

3552
        let items = layer_db
1✔
3553
            .load_layer_collection(
1✔
3554
                &top_c_id,
1✔
3555
                LayerCollectionListOptions {
1✔
3556
                    offset: 0,
1✔
3557
                    limit: 20,
1✔
3558
                },
1✔
3559
            )
1✔
3560
            .await
1✔
3561
            .unwrap();
1✔
3562

1✔
3563
        assert_eq!(
1✔
3564
            items,
1✔
3565
            LayerCollection {
1✔
3566
                id: ProviderLayerCollectionId {
1✔
3567
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
3568
                    collection_id: top_c_id.clone(),
1✔
3569
                },
1✔
3570
                name: "top collection".to_string(),
1✔
3571
                description: "description".to_string(),
1✔
3572
                items: vec![
1✔
3573
                    CollectionItem::Collection(LayerCollectionListing {
1✔
3574
                        id: ProviderLayerCollectionId {
1✔
3575
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
3576
                            collection_id: empty_c_id.clone(),
1✔
3577
                        },
1✔
3578
                        name: "empty collection".to_string(),
1✔
3579
                        description: "description".to_string(),
1✔
3580
                        properties: Default::default(),
1✔
3581
                    }),
1✔
3582
                    CollectionItem::Layer(LayerListing {
1✔
3583
                        id: ProviderLayerId {
1✔
3584
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
3585
                            layer_id: l_id.clone(),
1✔
3586
                        },
1✔
3587
                        name: "layer".to_string(),
1✔
3588
                        description: "description".to_string(),
1✔
3589
                        properties: vec![],
1✔
3590
                    })
1✔
3591
                ],
1✔
3592
                entry_label: None,
1✔
3593
                properties: vec![],
1✔
3594
            }
1✔
3595
        );
1✔
3596

3597
        // remove empty collection
3598
        layer_db.remove_layer_collection(&empty_c_id).await.unwrap();
1✔
3599

3600
        let items = layer_db
1✔
3601
            .load_layer_collection(
1✔
3602
                &top_c_id,
1✔
3603
                LayerCollectionListOptions {
1✔
3604
                    offset: 0,
1✔
3605
                    limit: 20,
1✔
3606
                },
1✔
3607
            )
1✔
3608
            .await
1✔
3609
            .unwrap();
1✔
3610

1✔
3611
        assert_eq!(
1✔
3612
            items,
1✔
3613
            LayerCollection {
1✔
3614
                id: ProviderLayerCollectionId {
1✔
3615
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
3616
                    collection_id: top_c_id.clone(),
1✔
3617
                },
1✔
3618
                name: "top collection".to_string(),
1✔
3619
                description: "description".to_string(),
1✔
3620
                items: vec![CollectionItem::Layer(LayerListing {
1✔
3621
                    id: ProviderLayerId {
1✔
3622
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
3623
                        layer_id: l_id.clone(),
1✔
3624
                    },
1✔
3625
                    name: "layer".to_string(),
1✔
3626
                    description: "description".to_string(),
1✔
3627
                    properties: vec![],
1✔
3628
                })],
1✔
3629
                entry_label: None,
1✔
3630
                properties: vec![],
1✔
3631
            }
1✔
3632
        );
1✔
3633

3634
        // remove top (not root) collection
3635
        layer_db.remove_layer_collection(&top_c_id).await.unwrap();
1✔
3636

1✔
3637
        layer_db
1✔
3638
            .load_layer_collection(
1✔
3639
                &top_c_id,
1✔
3640
                LayerCollectionListOptions {
1✔
3641
                    offset: 0,
1✔
3642
                    limit: 20,
1✔
3643
                },
1✔
3644
            )
1✔
3645
            .await
1✔
3646
            .unwrap_err();
1✔
3647

1✔
3648
        // should be deleted automatically
1✔
3649
        layer_db.load_layer(&l_id).await.unwrap_err();
1✔
3650

1✔
3651
        // it is not allowed to remove the root collection
1✔
3652
        layer_db
1✔
3653
            .remove_layer_collection(root_collection)
1✔
3654
            .await
1✔
3655
            .unwrap_err();
1✔
3656
        layer_db
1✔
3657
            .load_layer_collection(
1✔
3658
                root_collection,
1✔
3659
                LayerCollectionListOptions {
1✔
3660
                    offset: 0,
1✔
3661
                    limit: 20,
1✔
3662
                },
1✔
3663
            )
1✔
3664
            .await
1✔
3665
            .unwrap();
1✔
3666
    }
1✔
3667

3668
    #[ge_context::test]
1✔
3669
    #[allow(clippy::too_many_lines)]
3670
    async fn it_removes_collections_from_collections(app_ctx: PostgresContext<NoTls>) {
1✔
3671
        let session = admin_login(&app_ctx).await;
1✔
3672

3673
        let db = app_ctx.session_context(session).db();
1✔
3674

3675
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
3676

3677
        let mid_collection_id = db
1✔
3678
            .add_layer_collection(
1✔
3679
                AddLayerCollection {
1✔
3680
                    name: "mid collection".to_string(),
1✔
3681
                    description: "description".to_string(),
1✔
3682
                    properties: Default::default(),
1✔
3683
                },
1✔
3684
                root_collection_id,
1✔
3685
            )
1✔
3686
            .await
1✔
3687
            .unwrap();
1✔
3688

3689
        let bottom_collection_id = db
1✔
3690
            .add_layer_collection(
1✔
3691
                AddLayerCollection {
1✔
3692
                    name: "bottom collection".to_string(),
1✔
3693
                    description: "description".to_string(),
1✔
3694
                    properties: Default::default(),
1✔
3695
                },
1✔
3696
                &mid_collection_id,
1✔
3697
            )
1✔
3698
            .await
1✔
3699
            .unwrap();
1✔
3700

3701
        let layer_id = db
1✔
3702
            .add_layer(
1✔
3703
                AddLayer {
1✔
3704
                    name: "layer".to_string(),
1✔
3705
                    description: "description".to_string(),
1✔
3706
                    workflow: Workflow {
1✔
3707
                        operator: TypedOperator::Vector(
1✔
3708
                            MockPointSource {
1✔
3709
                                params: MockPointSourceParams {
1✔
3710
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3711
                                },
1✔
3712
                            }
1✔
3713
                            .boxed(),
1✔
3714
                        ),
1✔
3715
                    },
1✔
3716
                    symbology: None,
1✔
3717
                    metadata: Default::default(),
1✔
3718
                    properties: Default::default(),
1✔
3719
                },
1✔
3720
                &mid_collection_id,
1✔
3721
            )
1✔
3722
            .await
1✔
3723
            .unwrap();
1✔
3724

1✔
3725
        // removing the mid collection…
1✔
3726
        db.remove_layer_collection_from_parent(&mid_collection_id, root_collection_id)
1✔
3727
            .await
1✔
3728
            .unwrap();
1✔
3729

1✔
3730
        // …should remove itself
1✔
3731
        db.load_layer_collection(&mid_collection_id, LayerCollectionListOptions::default())
1✔
3732
            .await
1✔
3733
            .unwrap_err();
1✔
3734

1✔
3735
        // …should remove the bottom collection
1✔
3736
        db.load_layer_collection(&bottom_collection_id, LayerCollectionListOptions::default())
1✔
3737
            .await
1✔
3738
            .unwrap_err();
1✔
3739

1✔
3740
        // … and should remove the layer of the bottom collection
1✔
3741
        db.load_layer(&layer_id).await.unwrap_err();
1✔
3742

1✔
3743
        // the root collection is still there
1✔
3744
        db.load_layer_collection(root_collection_id, LayerCollectionListOptions::default())
1✔
3745
            .await
1✔
3746
            .unwrap();
1✔
3747
    }
1✔
3748

3749
    #[ge_context::test]
1✔
3750
    #[allow(clippy::too_many_lines)]
3751
    async fn it_removes_layers_from_collections(app_ctx: PostgresContext<NoTls>) {
1✔
3752
        let session = admin_login(&app_ctx).await;
1✔
3753

3754
        let db = app_ctx.session_context(session).db();
1✔
3755

3756
        let root_collection = &db.get_root_layer_collection_id().await.unwrap();
1✔
3757

3758
        let another_collection = db
1✔
3759
            .add_layer_collection(
1✔
3760
                AddLayerCollection {
1✔
3761
                    name: "top collection".to_string(),
1✔
3762
                    description: "description".to_string(),
1✔
3763
                    properties: Default::default(),
1✔
3764
                },
1✔
3765
                root_collection,
1✔
3766
            )
1✔
3767
            .await
1✔
3768
            .unwrap();
1✔
3769

3770
        let layer_in_one_collection = db
1✔
3771
            .add_layer(
1✔
3772
                AddLayer {
1✔
3773
                    name: "layer 1".to_string(),
1✔
3774
                    description: "description".to_string(),
1✔
3775
                    workflow: Workflow {
1✔
3776
                        operator: TypedOperator::Vector(
1✔
3777
                            MockPointSource {
1✔
3778
                                params: MockPointSourceParams {
1✔
3779
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3780
                                },
1✔
3781
                            }
1✔
3782
                            .boxed(),
1✔
3783
                        ),
1✔
3784
                    },
1✔
3785
                    symbology: None,
1✔
3786
                    metadata: Default::default(),
1✔
3787
                    properties: Default::default(),
1✔
3788
                },
1✔
3789
                &another_collection,
1✔
3790
            )
1✔
3791
            .await
1✔
3792
            .unwrap();
1✔
3793

3794
        let layer_in_two_collections = db
1✔
3795
            .add_layer(
1✔
3796
                AddLayer {
1✔
3797
                    name: "layer 2".to_string(),
1✔
3798
                    description: "description".to_string(),
1✔
3799
                    workflow: Workflow {
1✔
3800
                        operator: TypedOperator::Vector(
1✔
3801
                            MockPointSource {
1✔
3802
                                params: MockPointSourceParams {
1✔
3803
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3804
                                },
1✔
3805
                            }
1✔
3806
                            .boxed(),
1✔
3807
                        ),
1✔
3808
                    },
1✔
3809
                    symbology: None,
1✔
3810
                    metadata: Default::default(),
1✔
3811
                    properties: Default::default(),
1✔
3812
                },
1✔
3813
                &another_collection,
1✔
3814
            )
1✔
3815
            .await
1✔
3816
            .unwrap();
1✔
3817

1✔
3818
        db.load_layer(&layer_in_two_collections).await.unwrap();
1✔
3819

1✔
3820
        db.add_layer_to_collection(&layer_in_two_collections, root_collection)
1✔
3821
            .await
1✔
3822
            .unwrap();
1✔
3823

1✔
3824
        // remove first layer --> should be deleted entirely
1✔
3825

1✔
3826
        db.remove_layer_from_collection(&layer_in_one_collection, &another_collection)
1✔
3827
            .await
1✔
3828
            .unwrap();
1✔
3829

3830
        let number_of_layer_in_collection = db
1✔
3831
            .load_layer_collection(
1✔
3832
                &another_collection,
1✔
3833
                LayerCollectionListOptions {
1✔
3834
                    offset: 0,
1✔
3835
                    limit: 20,
1✔
3836
                },
1✔
3837
            )
1✔
3838
            .await
1✔
3839
            .unwrap()
1✔
3840
            .items
1✔
3841
            .len();
1✔
3842
        assert_eq!(
1✔
3843
            number_of_layer_in_collection,
1✔
3844
            1 /* only the other collection should be here */
1✔
3845
        );
1✔
3846

3847
        db.load_layer(&layer_in_one_collection).await.unwrap_err();
1✔
3848

1✔
3849
        // remove second layer --> should only be gone in collection
1✔
3850

1✔
3851
        db.remove_layer_from_collection(&layer_in_two_collections, &another_collection)
1✔
3852
            .await
1✔
3853
            .unwrap();
1✔
3854

3855
        let number_of_layer_in_collection = db
1✔
3856
            .load_layer_collection(
1✔
3857
                &another_collection,
1✔
3858
                LayerCollectionListOptions {
1✔
3859
                    offset: 0,
1✔
3860
                    limit: 20,
1✔
3861
                },
1✔
3862
            )
1✔
3863
            .await
1✔
3864
            .unwrap()
1✔
3865
            .items
1✔
3866
            .len();
1✔
3867
        assert_eq!(
1✔
3868
            number_of_layer_in_collection,
1✔
3869
            0 /* both layers were deleted */
1✔
3870
        );
1✔
3871

3872
        db.load_layer(&layer_in_two_collections).await.unwrap();
1✔
3873
    }
1✔
3874

3875
    #[ge_context::test]
1✔
3876
    #[allow(clippy::too_many_lines)]
3877
    async fn it_deletes_dataset(app_ctx: PostgresContext<NoTls>) {
1✔
3878
        let loading_info = OgrSourceDataset {
1✔
3879
            file_name: PathBuf::from("test.csv"),
1✔
3880
            layer_name: "test.csv".to_owned(),
1✔
3881
            data_type: Some(VectorDataType::MultiPoint),
1✔
3882
            time: OgrSourceDatasetTimeType::Start {
1✔
3883
                start_field: "start".to_owned(),
1✔
3884
                start_format: OgrSourceTimeFormat::Auto,
1✔
3885
                duration: OgrSourceDurationSpec::Zero,
1✔
3886
            },
1✔
3887
            default_geometry: None,
1✔
3888
            columns: Some(OgrSourceColumnSpec {
1✔
3889
                format_specifics: Some(FormatSpecifics::Csv {
1✔
3890
                    header: CsvHeader::Auto,
1✔
3891
                }),
1✔
3892
                x: "x".to_owned(),
1✔
3893
                y: None,
1✔
3894
                int: vec![],
1✔
3895
                float: vec![],
1✔
3896
                text: vec![],
1✔
3897
                bool: vec![],
1✔
3898
                datetime: vec![],
1✔
3899
                rename: None,
1✔
3900
            }),
1✔
3901
            force_ogr_time_filter: false,
1✔
3902
            force_ogr_spatial_filter: false,
1✔
3903
            on_error: OgrSourceErrorSpec::Ignore,
1✔
3904
            sql_query: None,
1✔
3905
            attribute_query: None,
1✔
3906
            cache_ttl: CacheTtlSeconds::default(),
1✔
3907
        };
1✔
3908

1✔
3909
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
3910
            OgrSourceDataset,
1✔
3911
            VectorResultDescriptor,
1✔
3912
            VectorQueryRectangle,
1✔
3913
        > {
1✔
3914
            loading_info: loading_info.clone(),
1✔
3915
            result_descriptor: VectorResultDescriptor {
1✔
3916
                data_type: VectorDataType::MultiPoint,
1✔
3917
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3918
                columns: [(
1✔
3919
                    "foo".to_owned(),
1✔
3920
                    VectorColumnInfo {
1✔
3921
                        data_type: FeatureDataType::Float,
1✔
3922
                        measurement: Measurement::Unitless,
1✔
3923
                    },
1✔
3924
                )]
1✔
3925
                .into_iter()
1✔
3926
                .collect(),
1✔
3927
                time: None,
1✔
3928
                bbox: None,
1✔
3929
            },
1✔
3930
            phantom: Default::default(),
1✔
3931
        });
1✔
3932

3933
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
3934

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

1✔
3937
        let db = app_ctx.session_context(session.clone()).db();
1✔
3938
        let dataset_id = db
1✔
3939
            .add_dataset(
1✔
3940
                AddDataset {
1✔
3941
                    name: Some(dataset_name),
1✔
3942
                    display_name: "Ogr Test".to_owned(),
1✔
3943
                    description: "desc".to_owned(),
1✔
3944
                    source_operator: "OgrSource".to_owned(),
1✔
3945
                    symbology: None,
1✔
3946
                    provenance: Some(vec![Provenance {
1✔
3947
                        citation: "citation".to_owned(),
1✔
3948
                        license: "license".to_owned(),
1✔
3949
                        uri: "uri".to_owned(),
1✔
3950
                    }]),
1✔
3951
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
3952
                },
1✔
3953
                meta_data,
1✔
3954
            )
1✔
3955
            .await
1✔
3956
            .unwrap()
1✔
3957
            .id;
1✔
3958

1✔
3959
        assert!(db.load_dataset(&dataset_id).await.is_ok());
1✔
3960

3961
        db.delete_dataset(dataset_id).await.unwrap();
1✔
3962

1✔
3963
        assert!(db.load_dataset(&dataset_id).await.is_err());
1✔
3964
    }
1✔
3965

3966
    #[ge_context::test]
1✔
3967
    #[allow(clippy::too_many_lines)]
3968
    async fn it_deletes_admin_dataset(app_ctx: PostgresContext<NoTls>) {
1✔
3969
        let dataset_name = DatasetName::new(None, "my_dataset");
1✔
3970

1✔
3971
        let loading_info = OgrSourceDataset {
1✔
3972
            file_name: PathBuf::from("test.csv"),
1✔
3973
            layer_name: "test.csv".to_owned(),
1✔
3974
            data_type: Some(VectorDataType::MultiPoint),
1✔
3975
            time: OgrSourceDatasetTimeType::Start {
1✔
3976
                start_field: "start".to_owned(),
1✔
3977
                start_format: OgrSourceTimeFormat::Auto,
1✔
3978
                duration: OgrSourceDurationSpec::Zero,
1✔
3979
            },
1✔
3980
            default_geometry: None,
1✔
3981
            columns: Some(OgrSourceColumnSpec {
1✔
3982
                format_specifics: Some(FormatSpecifics::Csv {
1✔
3983
                    header: CsvHeader::Auto,
1✔
3984
                }),
1✔
3985
                x: "x".to_owned(),
1✔
3986
                y: None,
1✔
3987
                int: vec![],
1✔
3988
                float: vec![],
1✔
3989
                text: vec![],
1✔
3990
                bool: vec![],
1✔
3991
                datetime: vec![],
1✔
3992
                rename: None,
1✔
3993
            }),
1✔
3994
            force_ogr_time_filter: false,
1✔
3995
            force_ogr_spatial_filter: false,
1✔
3996
            on_error: OgrSourceErrorSpec::Ignore,
1✔
3997
            sql_query: None,
1✔
3998
            attribute_query: None,
1✔
3999
            cache_ttl: CacheTtlSeconds::default(),
1✔
4000
        };
1✔
4001

1✔
4002
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
4003
            OgrSourceDataset,
1✔
4004
            VectorResultDescriptor,
1✔
4005
            VectorQueryRectangle,
1✔
4006
        > {
1✔
4007
            loading_info: loading_info.clone(),
1✔
4008
            result_descriptor: VectorResultDescriptor {
1✔
4009
                data_type: VectorDataType::MultiPoint,
1✔
4010
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
4011
                columns: [(
1✔
4012
                    "foo".to_owned(),
1✔
4013
                    VectorColumnInfo {
1✔
4014
                        data_type: FeatureDataType::Float,
1✔
4015
                        measurement: Measurement::Unitless,
1✔
4016
                    },
1✔
4017
                )]
1✔
4018
                .into_iter()
1✔
4019
                .collect(),
1✔
4020
                time: None,
1✔
4021
                bbox: None,
1✔
4022
            },
1✔
4023
            phantom: Default::default(),
1✔
4024
        });
1✔
4025

4026
        let session = admin_login(&app_ctx).await;
1✔
4027

4028
        let db = app_ctx.session_context(session).db();
1✔
4029
        let dataset_id = db
1✔
4030
            .add_dataset(
1✔
4031
                AddDataset {
1✔
4032
                    name: Some(dataset_name),
1✔
4033
                    display_name: "Ogr Test".to_owned(),
1✔
4034
                    description: "desc".to_owned(),
1✔
4035
                    source_operator: "OgrSource".to_owned(),
1✔
4036
                    symbology: None,
1✔
4037
                    provenance: Some(vec![Provenance {
1✔
4038
                        citation: "citation".to_owned(),
1✔
4039
                        license: "license".to_owned(),
1✔
4040
                        uri: "uri".to_owned(),
1✔
4041
                    }]),
1✔
4042
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
4043
                },
1✔
4044
                meta_data,
1✔
4045
            )
1✔
4046
            .await
1✔
4047
            .unwrap()
1✔
4048
            .id;
1✔
4049

1✔
4050
        assert!(db.load_dataset(&dataset_id).await.is_ok());
1✔
4051

4052
        db.delete_dataset(dataset_id).await.unwrap();
1✔
4053

1✔
4054
        assert!(db.load_dataset(&dataset_id).await.is_err());
1✔
4055
    }
1✔
4056

4057
    #[ge_context::test]
1✔
4058
    async fn test_missing_layer_dataset_in_collection_listing(app_ctx: PostgresContext<NoTls>) {
1✔
4059
        let session = admin_login(&app_ctx).await;
1✔
4060
        let db = app_ctx.session_context(session).db();
1✔
4061

4062
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
4063

4064
        let top_collection_id = db
1✔
4065
            .add_layer_collection(
1✔
4066
                AddLayerCollection {
1✔
4067
                    name: "top collection".to_string(),
1✔
4068
                    description: "description".to_string(),
1✔
4069
                    properties: Default::default(),
1✔
4070
                },
1✔
4071
                root_collection_id,
1✔
4072
            )
1✔
4073
            .await
1✔
4074
            .unwrap();
1✔
4075

1✔
4076
        let faux_layer = LayerId("faux".to_string());
1✔
4077

1✔
4078
        // this should fail
1✔
4079
        db.add_layer_to_collection(&faux_layer, &top_collection_id)
1✔
4080
            .await
1✔
4081
            .unwrap_err();
1✔
4082

4083
        let root_collection_layers = db
1✔
4084
            .load_layer_collection(
1✔
4085
                &top_collection_id,
1✔
4086
                LayerCollectionListOptions {
1✔
4087
                    offset: 0,
1✔
4088
                    limit: 20,
1✔
4089
                },
1✔
4090
            )
1✔
4091
            .await
1✔
4092
            .unwrap();
1✔
4093

1✔
4094
        assert_eq!(
1✔
4095
            root_collection_layers,
1✔
4096
            LayerCollection {
1✔
4097
                id: ProviderLayerCollectionId {
1✔
4098
                    provider_id: DataProviderId(
1✔
4099
                        "ce5e84db-cbf9-48a2-9a32-d4b7cc56ea74".try_into().unwrap()
1✔
4100
                    ),
1✔
4101
                    collection_id: top_collection_id.clone(),
1✔
4102
                },
1✔
4103
                name: "top collection".to_string(),
1✔
4104
                description: "description".to_string(),
1✔
4105
                items: vec![],
1✔
4106
                entry_label: None,
1✔
4107
                properties: vec![],
1✔
4108
            }
1✔
4109
        );
1✔
4110
    }
1✔
4111

4112
    #[allow(clippy::too_many_lines)]
4113
    #[ge_context::test]
1✔
4114
    async fn it_restricts_layer_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
4115
        let admin_session = admin_login(&app_ctx).await;
1✔
4116
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
4117

1✔
4118
        let admin_db = app_ctx.session_context(admin_session.clone()).db();
1✔
4119
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
4120

4121
        let root = admin_db.get_root_layer_collection_id().await.unwrap();
1✔
4122

4123
        // add new collection as admin
4124
        let new_collection_id = admin_db
1✔
4125
            .add_layer_collection(
1✔
4126
                AddLayerCollection {
1✔
4127
                    name: "admin collection".to_string(),
1✔
4128
                    description: String::new(),
1✔
4129
                    properties: Default::default(),
1✔
4130
                },
1✔
4131
                &root,
1✔
4132
            )
1✔
4133
            .await
1✔
4134
            .unwrap();
1✔
4135

4136
        // load as regular user, not visible
4137
        let collection = db1
1✔
4138
            .load_layer_collection(
1✔
4139
                &root,
1✔
4140
                LayerCollectionListOptions {
1✔
4141
                    offset: 0,
1✔
4142
                    limit: 10,
1✔
4143
                },
1✔
4144
            )
1✔
4145
            .await
1✔
4146
            .unwrap();
1✔
4147
        assert!(!collection.items.iter().any(|c| match c {
1✔
4148
            CollectionItem::Collection(c) => c.id.collection_id == new_collection_id,
1✔
4149
            CollectionItem::Layer(_) => false,
×
4150
        }));
1✔
4151

4152
        // give user read permission
4153
        admin_db
1✔
4154
            .add_permission(
1✔
4155
                session1.user.id.into(),
1✔
4156
                new_collection_id.clone(),
1✔
4157
                Permission::Read,
1✔
4158
            )
1✔
4159
            .await
1✔
4160
            .unwrap();
1✔
4161

4162
        // now visible
4163
        let collection = db1
1✔
4164
            .load_layer_collection(
1✔
4165
                &root,
1✔
4166
                LayerCollectionListOptions {
1✔
4167
                    offset: 0,
1✔
4168
                    limit: 10,
1✔
4169
                },
1✔
4170
            )
1✔
4171
            .await
1✔
4172
            .unwrap();
1✔
4173

1✔
4174
        assert!(collection.items.iter().any(|c| match c {
2✔
4175
            CollectionItem::Collection(c) => c.id.collection_id == new_collection_id,
2✔
4176
            CollectionItem::Layer(_) => false,
×
4177
        }));
2✔
4178
    }
1✔
4179

4180
    #[allow(clippy::too_many_lines)]
4181
    #[ge_context::test]
1✔
4182
    async fn it_handles_user_roles(app_ctx: PostgresContext<NoTls>) {
1✔
4183
        let admin_session = admin_login(&app_ctx).await;
1✔
4184
        let user_id = app_ctx
1✔
4185
            .register_user(UserRegistration {
1✔
4186
                email: "foo@example.com".to_string(),
1✔
4187
                password: "secret123".to_string(),
1✔
4188
                real_name: "Foo Bar".to_string(),
1✔
4189
            })
1✔
4190
            .await
1✔
4191
            .unwrap();
1✔
4192

1✔
4193
        let admin_db = app_ctx.session_context(admin_session.clone()).db();
1✔
4194

4195
        // create a new role
4196
        let role_id = admin_db.add_role("foo").await.unwrap();
1✔
4197

4198
        let user_session = app_ctx
1✔
4199
            .login(UserCredentials {
1✔
4200
                email: "foo@example.com".to_string(),
1✔
4201
                password: "secret123".to_string(),
1✔
4202
            })
1✔
4203
            .await
1✔
4204
            .unwrap();
1✔
4205

1✔
4206
        // user does not have the role yet
1✔
4207

1✔
4208
        assert!(!user_session.roles.contains(&role_id));
1✔
4209

4210
        //user can query their role descriptions (user role and registered user)
4211
        assert_eq!(user_session.roles.len(), 2);
1✔
4212

4213
        let expected_user_role_description = RoleDescription {
1✔
4214
            role: Role {
1✔
4215
                id: RoleId::from(user_id),
1✔
4216
                name: "foo@example.com".to_string(),
1✔
4217
            },
1✔
4218
            individual: true,
1✔
4219
        };
1✔
4220
        let expected_registered_role_description = RoleDescription {
1✔
4221
            role: Role {
1✔
4222
                id: Role::registered_user_role_id(),
1✔
4223
                name: "user".to_string(),
1✔
4224
            },
1✔
4225
            individual: false,
1✔
4226
        };
1✔
4227

4228
        let user_role_descriptions = app_ctx
1✔
4229
            .session_context(user_session.clone())
1✔
4230
            .db()
1✔
4231
            .get_role_descriptions(&user_id)
1✔
4232
            .await
1✔
4233
            .unwrap();
1✔
4234
        assert_eq!(
1✔
4235
            vec![
1✔
4236
                expected_user_role_description.clone(),
1✔
4237
                expected_registered_role_description.clone(),
1✔
4238
            ],
1✔
4239
            user_role_descriptions
1✔
4240
        );
1✔
4241

4242
        // we assign the role to the user
4243
        admin_db.assign_role(&role_id, &user_id).await.unwrap();
1✔
4244

4245
        let user_session = app_ctx
1✔
4246
            .login(UserCredentials {
1✔
4247
                email: "foo@example.com".to_string(),
1✔
4248
                password: "secret123".to_string(),
1✔
4249
            })
1✔
4250
            .await
1✔
4251
            .unwrap();
1✔
4252

1✔
4253
        // should be present now
1✔
4254
        assert!(user_session.roles.contains(&role_id));
1✔
4255

4256
        //user can query their role descriptions (now an additional foo role)
4257
        let expected_foo_role_description = RoleDescription {
1✔
4258
            role: Role {
1✔
4259
                id: role_id,
1✔
4260
                name: "foo".to_string(),
1✔
4261
            },
1✔
4262
            individual: false,
1✔
4263
        };
1✔
4264

4265
        let user_role_descriptions = app_ctx
1✔
4266
            .session_context(user_session.clone())
1✔
4267
            .db()
1✔
4268
            .get_role_descriptions(&user_id)
1✔
4269
            .await
1✔
4270
            .unwrap();
1✔
4271
        assert_eq!(
1✔
4272
            vec![
1✔
4273
                expected_foo_role_description,
1✔
4274
                expected_user_role_description.clone(),
1✔
4275
                expected_registered_role_description.clone(),
1✔
4276
            ],
1✔
4277
            user_role_descriptions
1✔
4278
        );
1✔
4279

4280
        // we revoke it
4281
        admin_db.revoke_role(&role_id, &user_id).await.unwrap();
1✔
4282

4283
        let user_session = app_ctx
1✔
4284
            .login(UserCredentials {
1✔
4285
                email: "foo@example.com".to_string(),
1✔
4286
                password: "secret123".to_string(),
1✔
4287
            })
1✔
4288
            .await
1✔
4289
            .unwrap();
1✔
4290

1✔
4291
        // the role is gone now
1✔
4292
        assert!(!user_session.roles.contains(&role_id));
1✔
4293

4294
        //user can query their role descriptions (user role and registered user)
4295
        let user_role_descriptions = app_ctx
1✔
4296
            .session_context(user_session.clone())
1✔
4297
            .db()
1✔
4298
            .get_role_descriptions(&user_id)
1✔
4299
            .await
1✔
4300
            .unwrap();
1✔
4301
        assert_eq!(
1✔
4302
            vec![
1✔
4303
                expected_user_role_description.clone(),
1✔
4304
                expected_registered_role_description.clone(),
1✔
4305
            ],
1✔
4306
            user_role_descriptions
1✔
4307
        );
1✔
4308

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

4311
        admin_db.assign_role(&role_id, &user_id).await.unwrap();
1✔
4312

1✔
4313
        admin_db.remove_role(&role_id).await.unwrap();
1✔
4314

4315
        let user_session = app_ctx
1✔
4316
            .login(UserCredentials {
1✔
4317
                email: "foo@example.com".to_string(),
1✔
4318
                password: "secret123".to_string(),
1✔
4319
            })
1✔
4320
            .await
1✔
4321
            .unwrap();
1✔
4322

1✔
4323
        assert!(!user_session.roles.contains(&role_id));
1✔
4324

4325
        //user can query their role descriptions (user role and registered user)
4326
        let user_role_descriptions = app_ctx
1✔
4327
            .session_context(user_session.clone())
1✔
4328
            .db()
1✔
4329
            .get_role_descriptions(&user_id)
1✔
4330
            .await
1✔
4331
            .unwrap();
1✔
4332
        assert_eq!(
1✔
4333
            vec![
1✔
4334
                expected_user_role_description,
1✔
4335
                expected_registered_role_description.clone(),
1✔
4336
            ],
1✔
4337
            user_role_descriptions
1✔
4338
        );
1✔
4339
    }
1✔
4340

4341
    #[allow(clippy::too_many_lines)]
4342
    #[ge_context::test]
1✔
4343
    async fn it_updates_project_layer_symbology(app_ctx: PostgresContext<NoTls>) {
1✔
4344
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
4345

4346
        let (_, workflow_id) = register_ndvi_workflow_helper(&app_ctx).await;
1✔
4347

4348
        let db = app_ctx.session_context(session.clone()).db();
1✔
4349

1✔
4350
        let create_project: CreateProject = serde_json::from_value(json!({
1✔
4351
            "name": "Default",
1✔
4352
            "description": "Default project",
1✔
4353
            "bounds": {
1✔
4354
                "boundingBox": {
1✔
4355
                    "lowerLeftCoordinate": {
1✔
4356
                        "x": -180,
1✔
4357
                        "y": -90
1✔
4358
                    },
1✔
4359
                    "upperRightCoordinate": {
1✔
4360
                        "x": 180,
1✔
4361
                        "y": 90
1✔
4362
                    }
1✔
4363
                },
1✔
4364
                "spatialReference": "EPSG:4326",
1✔
4365
                "timeInterval": {
1✔
4366
                    "start": 1_396_353_600_000i64,
1✔
4367
                    "end": 1_396_353_600_000i64
1✔
4368
                }
1✔
4369
            },
1✔
4370
            "timeStep": {
1✔
4371
                "step": 1,
1✔
4372
                "granularity": "months"
1✔
4373
            }
1✔
4374
        }))
1✔
4375
        .unwrap();
1✔
4376

4377
        let project_id = db.create_project(create_project).await.unwrap();
1✔
4378

1✔
4379
        let update: UpdateProject = serde_json::from_value(json!({
1✔
4380
            "id": project_id.to_string(),
1✔
4381
            "layers": [{
1✔
4382
                "name": "NDVI",
1✔
4383
                "workflow": workflow_id.to_string(),
1✔
4384
                "visibility": {
1✔
4385
                    "data": true,
1✔
4386
                    "legend": false
1✔
4387
                },
1✔
4388
                "symbology": {
1✔
4389
                    "type": "raster",
1✔
4390
                    "opacity": 1,
1✔
4391
                    "rasterColorizer": {
1✔
4392
                        "type": "singleBand",
1✔
4393
                        "band": 0,
1✔
4394
                        "bandColorizer": {
1✔
4395
                            "type": "linearGradient",
1✔
4396
                            "breakpoints": [{
1✔
4397
                                "value": 1,
1✔
4398
                                "color": [0, 0, 0, 255]
1✔
4399
                            }, {
1✔
4400
                                "value": 255,
1✔
4401
                                "color": [255, 255, 255, 255]
1✔
4402
                            }],
1✔
4403
                            "noDataColor": [0, 0, 0, 0],
1✔
4404
                            "overColor": [255, 255, 255, 127],
1✔
4405
                            "underColor": [255, 255, 255, 127]
1✔
4406
                        }
1✔
4407
                    }
1✔
4408
                }
1✔
4409
            }]
1✔
4410
        }))
1✔
4411
        .unwrap();
1✔
4412

1✔
4413
        db.update_project(update).await.unwrap();
1✔
4414

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

1✔
4491
        db.update_project(update).await.unwrap();
1✔
4492

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

1✔
4569
        db.update_project(update).await.unwrap();
1✔
4570

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

4647
        // run two updates concurrently
4648
        let (r0, r1) = join!(db.update_project(update.clone()), db.update_project(update));
1✔
4649

4650
        assert!(r0.is_ok());
1✔
4651
        assert!(r1.is_ok());
1✔
4652
    }
1✔
4653

4654
    #[ge_context::test]
1✔
4655
    #[allow(clippy::too_many_lines)]
4656
    async fn it_resolves_dataset_names_to_ids(app_ctx: PostgresContext<NoTls>) {
1✔
4657
        let admin_session = UserSession::admin_session();
1✔
4658
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
4659

1✔
4660
        let loading_info = OgrSourceDataset {
1✔
4661
            file_name: PathBuf::from("test.csv"),
1✔
4662
            layer_name: "test.csv".to_owned(),
1✔
4663
            data_type: Some(VectorDataType::MultiPoint),
1✔
4664
            time: OgrSourceDatasetTimeType::Start {
1✔
4665
                start_field: "start".to_owned(),
1✔
4666
                start_format: OgrSourceTimeFormat::Auto,
1✔
4667
                duration: OgrSourceDurationSpec::Zero,
1✔
4668
            },
1✔
4669
            default_geometry: None,
1✔
4670
            columns: Some(OgrSourceColumnSpec {
1✔
4671
                format_specifics: Some(FormatSpecifics::Csv {
1✔
4672
                    header: CsvHeader::Auto,
1✔
4673
                }),
1✔
4674
                x: "x".to_owned(),
1✔
4675
                y: None,
1✔
4676
                int: vec![],
1✔
4677
                float: vec![],
1✔
4678
                text: vec![],
1✔
4679
                bool: vec![],
1✔
4680
                datetime: vec![],
1✔
4681
                rename: None,
1✔
4682
            }),
1✔
4683
            force_ogr_time_filter: false,
1✔
4684
            force_ogr_spatial_filter: false,
1✔
4685
            on_error: OgrSourceErrorSpec::Ignore,
1✔
4686
            sql_query: None,
1✔
4687
            attribute_query: None,
1✔
4688
            cache_ttl: CacheTtlSeconds::default(),
1✔
4689
        };
1✔
4690

1✔
4691
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
4692
            OgrSourceDataset,
1✔
4693
            VectorResultDescriptor,
1✔
4694
            VectorQueryRectangle,
1✔
4695
        > {
1✔
4696
            loading_info: loading_info.clone(),
1✔
4697
            result_descriptor: VectorResultDescriptor {
1✔
4698
                data_type: VectorDataType::MultiPoint,
1✔
4699
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
4700
                columns: [(
1✔
4701
                    "foo".to_owned(),
1✔
4702
                    VectorColumnInfo {
1✔
4703
                        data_type: FeatureDataType::Float,
1✔
4704
                        measurement: Measurement::Unitless,
1✔
4705
                    },
1✔
4706
                )]
1✔
4707
                .into_iter()
1✔
4708
                .collect(),
1✔
4709
                time: None,
1✔
4710
                bbox: None,
1✔
4711
            },
1✔
4712
            phantom: Default::default(),
1✔
4713
        });
1✔
4714

4715
        let DatasetIdAndName {
4716
            id: dataset_id1,
1✔
4717
            name: dataset_name1,
1✔
4718
        } = db
1✔
4719
            .add_dataset(
1✔
4720
                AddDataset {
1✔
4721
                    name: Some(DatasetName::new(None, "my_dataset".to_owned())),
1✔
4722
                    display_name: "Ogr Test".to_owned(),
1✔
4723
                    description: "desc".to_owned(),
1✔
4724
                    source_operator: "OgrSource".to_owned(),
1✔
4725
                    symbology: None,
1✔
4726
                    provenance: Some(vec![Provenance {
1✔
4727
                        citation: "citation".to_owned(),
1✔
4728
                        license: "license".to_owned(),
1✔
4729
                        uri: "uri".to_owned(),
1✔
4730
                    }]),
1✔
4731
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
4732
                },
1✔
4733
                meta_data.clone(),
1✔
4734
            )
1✔
4735
            .await
1✔
4736
            .unwrap();
1✔
4737

4738
        let DatasetIdAndName {
4739
            id: dataset_id2,
1✔
4740
            name: dataset_name2,
1✔
4741
        } = db
1✔
4742
            .add_dataset(
1✔
4743
                AddDataset {
1✔
4744
                    name: Some(DatasetName::new(
1✔
4745
                        Some(admin_session.user.id.to_string()),
1✔
4746
                        "my_dataset".to_owned(),
1✔
4747
                    )),
1✔
4748
                    display_name: "Ogr Test".to_owned(),
1✔
4749
                    description: "desc".to_owned(),
1✔
4750
                    source_operator: "OgrSource".to_owned(),
1✔
4751
                    symbology: None,
1✔
4752
                    provenance: Some(vec![Provenance {
1✔
4753
                        citation: "citation".to_owned(),
1✔
4754
                        license: "license".to_owned(),
1✔
4755
                        uri: "uri".to_owned(),
1✔
4756
                    }]),
1✔
4757
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
4758
                },
1✔
4759
                meta_data,
1✔
4760
            )
1✔
4761
            .await
1✔
4762
            .unwrap();
1✔
4763

4764
        assert_eq!(
1✔
4765
            db.resolve_dataset_name_to_id(&dataset_name1)
1✔
4766
                .await
1✔
4767
                .unwrap()
1✔
4768
                .unwrap(),
1✔
4769
            dataset_id1
4770
        );
4771
        assert_eq!(
1✔
4772
            db.resolve_dataset_name_to_id(&dataset_name2)
1✔
4773
                .await
1✔
4774
                .unwrap()
1✔
4775
                .unwrap(),
1✔
4776
            dataset_id2
4777
        );
4778
    }
1✔
4779

4780
    #[ge_context::test]
1✔
4781
    #[allow(clippy::too_many_lines)]
4782
    async fn it_bulk_updates_quota(app_ctx: PostgresContext<NoTls>) {
1✔
4783
        let admin_session = UserSession::admin_session();
1✔
4784
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
4785

4786
        let user1 = app_ctx
1✔
4787
            .register_user(UserRegistration {
1✔
4788
                email: "user1@example.com".into(),
1✔
4789
                password: "12345678".into(),
1✔
4790
                real_name: "User1".into(),
1✔
4791
            })
1✔
4792
            .await
1✔
4793
            .unwrap();
1✔
4794

4795
        let user2 = app_ctx
1✔
4796
            .register_user(UserRegistration {
1✔
4797
                email: "user2@example.com".into(),
1✔
4798
                password: "12345678".into(),
1✔
4799
                real_name: "User2".into(),
1✔
4800
            })
1✔
4801
            .await
1✔
4802
            .unwrap();
1✔
4803

1✔
4804
        // single item in bulk
1✔
4805
        db.bulk_increment_quota_used([(user1, 1)]).await.unwrap();
1✔
4806

4807
        assert_eq!(db.quota_used_by_user(&user1).await.unwrap(), 1);
1✔
4808

4809
        // multiple items in bulk
4810
        db.bulk_increment_quota_used([(user1, 1), (user2, 3)])
1✔
4811
            .await
1✔
4812
            .unwrap();
1✔
4813

4814
        assert_eq!(db.quota_used_by_user(&user1).await.unwrap(), 2);
1✔
4815
        assert_eq!(db.quota_used_by_user(&user2).await.unwrap(), 3);
1✔
4816
    }
1✔
4817

4818
    async fn it_handles_oidc_tokens(app_ctx: PostgresContext<NoTls>) {
2✔
4819
        let external_user_claims = UserClaims {
2✔
4820
            external_id: SubjectIdentifier::new("Foo bar Id".into()),
2✔
4821
            email: "foo@bar.de".into(),
2✔
4822
            real_name: "Foo Bar".into(),
2✔
4823
        };
2✔
4824
        let tokens = OidcTokens {
2✔
4825
            access: AccessToken::new("FIRST_ACCESS_TOKEN".into()),
2✔
4826
            refresh: Some(RefreshToken::new("FIRST_REFRESH_TOKEN".into())),
2✔
4827
            expires_in: Duration::seconds(2),
2✔
4828
        };
2✔
4829

4830
        let login_result = app_ctx
2✔
4831
            .login_external(external_user_claims.clone(), tokens)
2✔
4832
            .await;
2✔
4833
        assert!(login_result.is_ok());
2✔
4834

4835
        let session_id = login_result.unwrap().id;
2✔
4836

4837
        let access_token = app_ctx.get_access_token(session_id).await.unwrap();
2✔
4838

2✔
4839
        assert_eq!(
2✔
4840
            "FIRST_ACCESS_TOKEN".to_string(),
2✔
4841
            access_token.secret().to_owned()
2✔
4842
        );
2✔
4843

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

4847
        let access_token = app_ctx.get_access_token(session_id).await.unwrap();
2✔
4848

2✔
4849
        assert_eq!(
2✔
4850
            "SECOND_ACCESS_TOKEN".to_string(),
2✔
4851
            access_token.secret().to_owned()
2✔
4852
        );
2✔
4853
    }
2✔
4854

4855
    pub fn oidc_only_refresh() -> (Server, impl Fn() -> OidcManager) {
1✔
4856
        let mock_refresh_server_config = MockRefreshServerConfig {
1✔
4857
            expected_discoveries: 1,
1✔
4858
            token_duration: std::time::Duration::from_secs(2),
1✔
4859
            creates_first_token: false,
1✔
4860
            first_access_token: "FIRST_ACCESS_TOKEN".to_string(),
1✔
4861
            first_refresh_token: "FIRST_REFRESH_TOKEN".to_string(),
1✔
4862
            second_access_token: "SECOND_ACCESS_TOKEN".to_string(),
1✔
4863
            second_refresh_token: "SECOND_REFRESH_TOKEN".to_string(),
1✔
4864
            client_side_password: None,
1✔
4865
        };
1✔
4866

1✔
4867
        let (server, oidc_manager) = mock_refresh_server(mock_refresh_server_config);
1✔
4868

1✔
4869
        (server, move || {
1✔
4870
            OidcManager::from_oidc_with_static_tokens(oidc_manager.clone())
1✔
4871
        })
1✔
4872
    }
1✔
4873

4874
    #[ge_context::test(oidc_db = "oidc_only_refresh")]
1✔
4875
    async fn it_handles_oidc_tokens_without_encryption(app_ctx: PostgresContext<NoTls>) {
1✔
4876
        it_handles_oidc_tokens(app_ctx).await;
1✔
4877
    }
1✔
4878

4879
    pub fn oidc_only_refresh_with_encryption() -> (Server, impl Fn() -> OidcManager) {
1✔
4880
        let mock_refresh_server_config = MockRefreshServerConfig {
1✔
4881
            expected_discoveries: 1,
1✔
4882
            token_duration: std::time::Duration::from_secs(2),
1✔
4883
            creates_first_token: false,
1✔
4884
            first_access_token: "FIRST_ACCESS_TOKEN".to_string(),
1✔
4885
            first_refresh_token: "FIRST_REFRESH_TOKEN".to_string(),
1✔
4886
            second_access_token: "SECOND_ACCESS_TOKEN".to_string(),
1✔
4887
            second_refresh_token: "SECOND_REFRESH_TOKEN".to_string(),
1✔
4888
            client_side_password: Some("password123".to_string()),
1✔
4889
        };
1✔
4890

1✔
4891
        let (server, oidc_manager) = mock_refresh_server(mock_refresh_server_config);
1✔
4892

1✔
4893
        (server, move || {
1✔
4894
            OidcManager::from_oidc_with_static_tokens(oidc_manager.clone())
1✔
4895
        })
1✔
4896
    }
1✔
4897

4898
    #[ge_context::test(oidc_db = "oidc_only_refresh_with_encryption")]
1✔
4899
    async fn it_handles_oidc_tokens_with_encryption(app_ctx: PostgresContext<NoTls>) {
1✔
4900
        it_handles_oidc_tokens(app_ctx).await;
1✔
4901
    }
1✔
4902

4903
    #[ge_context::test]
1✔
4904
    #[allow(clippy::too_many_lines)]
4905
    async fn it_resolves_ml_model_names_to_ids(app_ctx: PostgresContext<NoTls>) {
1✔
4906
        let admin_session = UserSession::admin_session();
1✔
4907
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
4908

1✔
4909
        let upload_id = UploadId::new();
1✔
4910
        let upload = Upload {
1✔
4911
            id: upload_id,
1✔
4912
            files: vec![],
1✔
4913
        };
1✔
4914
        db.create_upload(upload).await.unwrap();
1✔
4915

1✔
4916
        let model = MlModel {
1✔
4917
            description: "No real model here".to_owned(),
1✔
4918
            display_name: "my unreal model".to_owned(),
1✔
4919
            metadata: MlModelMetadata {
1✔
4920
                file_name: "myUnrealmodel.onnx".to_owned(),
1✔
4921
                input_type: ApiRasterDataType::F32,
1✔
4922
                num_input_bands: 17,
1✔
4923
                output_type: ApiRasterDataType::F64,
1✔
4924
            },
1✔
4925
            name: MlModelName::new(None, "myUnrealModel"),
1✔
4926
            upload: upload_id,
1✔
4927
        };
1✔
4928

4929
        let MlModelIdAndName {
4930
            id: model_id,
1✔
4931
            name: model_name,
1✔
4932
        } = db.add_model(model).await.unwrap();
1✔
4933

4934
        assert_eq!(
1✔
4935
            db.resolve_model_name_to_id(&model_name)
1✔
4936
                .await
1✔
4937
                .unwrap()
1✔
4938
                .unwrap(),
1✔
4939
            model_id
4940
        );
4941
    }
1✔
4942
}
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

© 2026 Coveralls, Inc