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

geo-engine / geoengine / 12864321835

20 Jan 2025 08:53AM UTC coverage: 90.007% (-0.6%) from 90.64%
12864321835

Pull #1008

github

web-flow
Merge c723a3956 into de81b44f7
Pull Request #1008: user ctx in ge_test

3447 of 3549 new or added lines in 64 files covered. (97.13%)

785 existing lines in 18 files now uncovered.

127380 of 141522 relevant lines covered (90.01%)

56878.89 hits per line

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

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

47
// TODO: do not report postgres error details to user
48

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

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

86
        let pool = Pool::builder().build(pg_mgr).await?;
298✔
87

88
        Self::create_pro_database(pool.get().await?).await?;
298✔
89

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

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

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

121
        let pool = Pool::builder().build(pg_mgr).await?;
×
122

123
        Self::create_pro_database(pool.get().await?).await?;
×
124

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

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

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

166
        let pool = Pool::builder().build(pg_mgr).await?;
×
167

168
        let created_schema = Self::create_pro_database(pool.get().await?).await?;
×
169

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

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

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

196
            let mut db = app_ctx.session_context(UserSession::admin_session()).db();
×
197

×
198
            add_layers_from_directory(&mut db, layer_defs_path).await;
×
199
            add_layer_collections_from_directory(&mut db, layer_collection_defs_path).await;
×
200

201
            add_datasets_from_directory(&mut db, dataset_defs_path).await;
×
202

203
            add_providers_from_directory(&mut db, provider_defs_path.clone()).await;
×
204

205
            add_pro_providers_from_directory(&mut db, provider_defs_path.join("pro")).await;
×
206
        }
×
207

208
        Ok(app_ctx)
×
209
    }
×
210

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

218
        let migration = initialize_database(
298✔
219
            &mut conn,
298✔
220
            Box::new(ProMigrationImpl::from(CurrentSchemaMigration)),
298✔
221
            &pro_migrations(),
298✔
222
        )
298✔
223
        .await?;
298✔
224

225
        Ok(migration == MigrationResult::CreatedDatabase)
298✔
226
    }
298✔
227

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

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

252
        Ok(())
298✔
253
    }
298✔
254

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

NEW
274
        let row = conn.query_one(&stmt, &[]).await?;
×
275

NEW
276
        if row.get(0) {
×
NEW
277
            Ok(DatabaseStatus::InitializedClearDatabase)
×
278
        } else {
NEW
279
            Ok(DatabaseStatus::InitializedKeepDatabase)
×
280
        }
281
    }
298✔
282
}
283

284
enum DatabaseStatus {
285
    Unitialized,
286
    InitializedClearDatabase,
287
    InitializedKeepDatabase,
288
}
289

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

301
    fn session_context(&self, session: Self::Session) -> Self::SessionContext {
577✔
302
        PostgresSessionContext {
577✔
303
            session,
577✔
304
            context: self.clone(),
577✔
305
        }
577✔
306
    }
577✔
307

308
    async fn session_by_id(&self, session_id: SessionId) -> Result<Self::Session> {
171✔
309
        self.user_session_by_id(session_id)
171✔
310
            .await
171✔
311
            .map_err(Box::new)
165✔
312
            .context(error::Unauthorized)
165✔
313
    }
336✔
314
}
315

316
impl<Tls> ProApplicationContext for PostgresContext<Tls>
317
where
318
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
319
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
320
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
321
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
322
{
323
    fn oidc_manager(&self) -> &OidcManager {
30✔
324
        &self.oidc_manager
30✔
325
    }
30✔
326
}
327

328
#[derive(Clone)]
329
pub struct PostgresSessionContext<Tls>
330
where
331
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
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
    session: UserSession,
337
    context: PostgresContext<Tls>,
338
}
339

340
#[async_trait]
341
impl<Tls> SessionContext for PostgresSessionContext<Tls>
342
where
343
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
344
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
345
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
346
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
347
{
348
    type Session = UserSession;
349
    type GeoEngineDB = PostgresDb<Tls>;
350

351
    type TaskContext = SimpleTaskManagerContext;
352
    type TaskManager = UserTaskManager; // this does not persist across restarts
353
    type QueryContext = QueryContextImpl;
354
    type ExecutionContext = ExecutionContextImpl<Self::GeoEngineDB>;
355

356
    fn db(&self) -> Self::GeoEngineDB {
756✔
357
        PostgresDb::new(self.context.pool.clone(), self.session.clone())
756✔
358
    }
756✔
359

360
    fn tasks(&self) -> Self::TaskManager {
41✔
361
        UserTaskManager::new(self.context.task_manager.clone(), self.session.clone())
41✔
362
    }
41✔
363

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

32✔
367
        Ok(QueryContextImpl::new_with_extensions(
32✔
368
            self.context.query_ctx_chunk_size,
32✔
369
            self.context.thread_pool.clone(),
32✔
370
            Some(self.context.tile_cache.clone()),
32✔
371
            Some(
32✔
372
                self.context
32✔
373
                    .quota
32✔
374
                    .create_quota_tracking(&self.session, workflow, computation),
32✔
375
            ),
32✔
376
            Some(Box::new(QuotaCheckerImpl { user_db: self.db() }) as QuotaChecker),
32✔
377
        ))
32✔
378
    }
32✔
379

380
    fn execution_context(&self) -> Result<Self::ExecutionContext> {
57✔
381
        Ok(ExecutionContextImpl::<PostgresDb<Tls>>::new(
57✔
382
            self.db(),
57✔
383
            self.context.thread_pool.clone(),
57✔
384
            self.context.exe_ctx_tiling_spec,
57✔
385
        ))
57✔
386
    }
57✔
387

388
    fn volumes(&self) -> Result<Vec<Volume>> {
1✔
389
        Ok(self
1✔
390
            .context
1✔
391
            .volumes
1✔
392
            .volumes
1✔
393
            .iter()
1✔
394
            .map(|v| Volume {
1✔
395
                name: v.name.0.clone(),
1✔
396
                path: if self.session.is_admin() {
1✔
397
                    Some(v.path.to_string_lossy().to_string())
1✔
398
                } else {
399
                    None
×
400
                },
401
            })
1✔
402
            .collect())
1✔
403
    }
1✔
404

405
    fn session(&self) -> &Self::Session {
30✔
406
        &self.session
30✔
407
    }
30✔
408
}
409

410
#[derive(Debug)]
411
pub struct PostgresDb<Tls>
412
where
413
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
414
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
415
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
416
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
417
{
418
    pub(crate) conn_pool: Pool<PostgresConnectionManager<Tls>>,
419
    pub(crate) session: UserSession,
420
}
421

422
impl<Tls> PostgresDb<Tls>
423
where
424
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
425
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
426
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
427
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
428
{
429
    pub fn new(conn_pool: Pool<PostgresConnectionManager<Tls>>, session: UserSession) -> Self {
1,055✔
430
        Self { conn_pool, session }
1,055✔
431
    }
1,055✔
432

433
    /// Check whether the namepsace of the given dataset is allowed for insertion
434
    pub(crate) fn check_dataset_namespace(&self, id: &DatasetName) -> Result<()> {
97✔
435
        let is_ok = match &id.namespace {
97✔
436
            Some(namespace) => namespace.as_str() == self.session.user.id.to_string(),
32✔
437
            None => self.session.is_admin(),
65✔
438
        };
439

440
        if is_ok {
97✔
441
            Ok(())
97✔
442
        } else {
443
            Err(Error::InvalidDatasetIdNamespace)
×
444
        }
445
    }
97✔
446

447
    /// Check whether the namepsace of the given model is allowed for insertion
448
    pub(crate) fn check_ml_model_namespace(
1✔
449
        &self,
1✔
450
        name: &MlModelName,
1✔
451
    ) -> Result<(), MachineLearningError> {
1✔
452
        let is_ok = match &name.namespace {
1✔
453
            Some(namespace) => namespace.as_str() == self.session.user.id.to_string(),
1✔
454
            None => self.session.is_admin(),
×
455
        };
456

457
        if is_ok {
1✔
458
            Ok(())
1✔
459
        } else {
460
            Err(MachineLearningError::InvalidModelNamespace { name: name.clone() })
×
461
        }
462
    }
1✔
463
}
464

465
impl<Tls> GeoEngineDb for PostgresDb<Tls>
466
where
467
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
468
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
469
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
470
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
471
{
472
}
473

474
impl<Tls> ProGeoEngineDb for PostgresDb<Tls>
475
where
476
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
477
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
478
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
479
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
480
{
481
}
482

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

555
    #[ge_context::test]
3✔
556
    async fn test(app_ctx: PostgresContext<NoTls>) {
1✔
557
        anonymous(&app_ctx).await;
1✔
558

559
        let _user_id = user_reg_login(&app_ctx).await;
1✔
560

561
        let session = app_ctx
1✔
562
            .login(UserCredentials {
1✔
563
                email: "foo@example.com".into(),
1✔
564
                password: "secret123".into(),
1✔
565
            })
1✔
566
            .await
1✔
567
            .unwrap();
1✔
568

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

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

573
        set_session(&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
    #[ge_context::test]
3✔
585
    async fn test_external(app_ctx: PostgresContext<NoTls>) {
1✔
586
        anonymous(&app_ctx).await;
1✔
587

588
        let session = external_user_login_twice(&app_ctx).await;
1✔
589

590
        create_projects(&app_ctx, &session).await;
1✔
591

592
        let projects = list_projects(&app_ctx, &session).await;
1✔
593

594
        set_session_external(&app_ctx, &projects).await;
1✔
595

596
        let project_id = projects[0].id;
1✔
597

1✔
598
        update_projects(&app_ctx, &session, project_id).await;
1✔
599

600
        add_permission(&app_ctx, &session, project_id).await;
1✔
601

602
        delete_project(&app_ctx, &session, project_id).await;
1✔
603
    }
1✔
604

605
    fn tokens_from_duration(duration: Duration) -> OidcTokens {
3✔
606
        OidcTokens {
3✔
607
            access: AccessToken::new("AccessToken".to_string()),
3✔
608
            refresh: None,
3✔
609
            expires_in: duration,
3✔
610
        }
3✔
611
    }
3✔
612

613
    async fn set_session(app_ctx: &PostgresContext<NoTls>, projects: &[ProjectListing]) {
1✔
614
        let credentials = UserCredentials {
1✔
615
            email: "foo@example.com".into(),
1✔
616
            password: "secret123".into(),
1✔
617
        };
1✔
618

619
        let session = app_ctx.login(credentials).await.unwrap();
1✔
620

1✔
621
        set_session_in_database(app_ctx, projects, session).await;
1✔
622
    }
1✔
623

624
    async fn set_session_external(app_ctx: &PostgresContext<NoTls>, projects: &[ProjectListing]) {
1✔
625
        let external_user_claims = UserClaims {
1✔
626
            external_id: SubjectIdentifier::new("Foo bar Id".into()),
1✔
627
            email: "foo@bar.de".into(),
1✔
628
            real_name: "Foo Bar".into(),
1✔
629
        };
1✔
630

631
        let session = app_ctx
1✔
632
            .login_external(
1✔
633
                external_user_claims,
1✔
634
                tokens_from_duration(Duration::minutes(10)),
1✔
635
            )
1✔
636
            .await
1✔
637
            .unwrap();
1✔
638

1✔
639
        set_session_in_database(app_ctx, projects, session).await;
1✔
640
    }
1✔
641

642
    async fn set_session_in_database(
2✔
643
        app_ctx: &PostgresContext<NoTls>,
2✔
644
        projects: &[ProjectListing],
2✔
645
        session: UserSession,
2✔
646
    ) {
2✔
647
        let db = app_ctx.session_context(session.clone()).db();
2✔
648

2✔
649
        db.set_session_project(projects[0].id).await.unwrap();
2✔
650

651
        assert_eq!(
2✔
652
            app_ctx.session_by_id(session.id).await.unwrap().project,
2✔
653
            Some(projects[0].id)
2✔
654
        );
655

656
        let rect = STRectangle::new_unchecked(SpatialReference::epsg_4326(), 0., 1., 2., 3., 1, 2);
2✔
657
        db.set_session_view(rect.clone()).await.unwrap();
2✔
658
        assert_eq!(
2✔
659
            app_ctx.session_by_id(session.id).await.unwrap().view,
2✔
660
            Some(rect)
2✔
661
        );
662
    }
2✔
663

664
    async fn delete_project(
2✔
665
        app_ctx: &PostgresContext<NoTls>,
2✔
666
        session: &UserSession,
2✔
667
        project_id: ProjectId,
2✔
668
    ) {
2✔
669
        let db = app_ctx.session_context(session.clone()).db();
2✔
670

2✔
671
        db.delete_project(project_id).await.unwrap();
2✔
672

2✔
673
        assert!(db.load_project(project_id).await.is_err());
2✔
674
    }
2✔
675

676
    async fn add_permission(
2✔
677
        app_ctx: &PostgresContext<NoTls>,
2✔
678
        session: &UserSession,
2✔
679
        project_id: ProjectId,
2✔
680
    ) {
2✔
681
        let db = app_ctx.session_context(session.clone()).db();
2✔
682

2✔
683
        assert!(db
2✔
684
            .has_permission(project_id, Permission::Owner)
2✔
685
            .await
2✔
686
            .unwrap());
2✔
687

688
        let user2 = app_ctx
2✔
689
            .register_user(UserRegistration {
2✔
690
                email: "user2@example.com".into(),
2✔
691
                password: "12345678".into(),
2✔
692
                real_name: "User2".into(),
2✔
693
            })
2✔
694
            .await
2✔
695
            .unwrap();
2✔
696

697
        let session2 = app_ctx
2✔
698
            .login(UserCredentials {
2✔
699
                email: "user2@example.com".into(),
2✔
700
                password: "12345678".into(),
2✔
701
            })
2✔
702
            .await
2✔
703
            .unwrap();
2✔
704

2✔
705
        let db2 = app_ctx.session_context(session2.clone()).db();
2✔
706
        assert!(!db2
2✔
707
            .has_permission(project_id, Permission::Owner)
2✔
708
            .await
2✔
709
            .unwrap());
2✔
710

711
        db.add_permission(user2.into(), project_id, Permission::Read)
2✔
712
            .await
2✔
713
            .unwrap();
2✔
714

2✔
715
        assert!(db2
2✔
716
            .has_permission(project_id, Permission::Read)
2✔
717
            .await
2✔
718
            .unwrap());
2✔
719
    }
2✔
720

721
    #[allow(clippy::too_many_lines)]
722
    async fn update_projects(
2✔
723
        app_ctx: &PostgresContext<NoTls>,
2✔
724
        session: &UserSession,
2✔
725
        project_id: ProjectId,
2✔
726
    ) {
2✔
727
        let db = app_ctx.session_context(session.clone()).db();
2✔
728

729
        let project = db
2✔
730
            .load_project_version(project_id, LoadVersion::Latest)
2✔
731
            .await
2✔
732
            .unwrap();
2✔
733

734
        let layer_workflow_id = db
2✔
735
            .register_workflow(Workflow {
2✔
736
                operator: TypedOperator::Vector(
2✔
737
                    MockPointSource {
2✔
738
                        params: MockPointSourceParams {
2✔
739
                            points: vec![Coordinate2D::new(1., 2.); 3],
2✔
740
                        },
2✔
741
                    }
2✔
742
                    .boxed(),
2✔
743
                ),
2✔
744
            })
2✔
745
            .await
2✔
746
            .unwrap();
2✔
747

2✔
748
        assert!(db.load_workflow(&layer_workflow_id).await.is_ok());
2✔
749

750
        let plot_workflow_id = db
2✔
751
            .register_workflow(Workflow {
2✔
752
                operator: Statistics {
2✔
753
                    params: StatisticsParams {
2✔
754
                        column_names: vec![],
2✔
755
                        percentiles: vec![],
2✔
756
                    },
2✔
757
                    sources: MultipleRasterOrSingleVectorSource {
2✔
758
                        source: Raster(vec![]),
2✔
759
                    },
2✔
760
                }
2✔
761
                .boxed()
2✔
762
                .into(),
2✔
763
            })
2✔
764
            .await
2✔
765
            .unwrap();
2✔
766

2✔
767
        assert!(db.load_workflow(&plot_workflow_id).await.is_ok());
2✔
768

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

789
        let versions = db.list_project_versions(project_id).await.unwrap();
2✔
790
        assert_eq!(versions.len(), 2);
2✔
791

792
        // add second plot
793
        let update = UpdateProject {
2✔
794
            id: project.id,
2✔
795
            name: Some("Test9 Updated".into()),
2✔
796
            description: None,
2✔
797
            layers: Some(vec![LayerUpdate::UpdateOrInsert(ProjectLayer {
2✔
798
                workflow: layer_workflow_id,
2✔
799
                name: "TestLayer".into(),
2✔
800
                symbology: PointSymbology::default().into(),
2✔
801
                visibility: Default::default(),
2✔
802
            })]),
2✔
803
            plots: Some(vec![
2✔
804
                PlotUpdate::UpdateOrInsert(Plot {
2✔
805
                    workflow: plot_workflow_id,
2✔
806
                    name: "Test Plot".into(),
2✔
807
                }),
2✔
808
                PlotUpdate::UpdateOrInsert(Plot {
2✔
809
                    workflow: plot_workflow_id,
2✔
810
                    name: "Test Plot".into(),
2✔
811
                }),
2✔
812
            ]),
2✔
813
            bounds: None,
2✔
814
            time_step: None,
2✔
815
        };
2✔
816
        db.update_project(update).await.unwrap();
2✔
817

818
        let versions = db.list_project_versions(project_id).await.unwrap();
2✔
819
        assert_eq!(versions.len(), 3);
2✔
820

821
        // delete plots
822
        let update = UpdateProject {
2✔
823
            id: project.id,
2✔
824
            name: None,
2✔
825
            description: None,
2✔
826
            layers: None,
2✔
827
            plots: Some(vec![]),
2✔
828
            bounds: None,
2✔
829
            time_step: None,
2✔
830
        };
2✔
831
        db.update_project(update).await.unwrap();
2✔
832

833
        let versions = db.list_project_versions(project_id).await.unwrap();
2✔
834
        assert_eq!(versions.len(), 4);
2✔
835
    }
2✔
836

837
    async fn list_projects(
2✔
838
        app_ctx: &PostgresContext<NoTls>,
2✔
839
        session: &UserSession,
2✔
840
    ) -> Vec<ProjectListing> {
2✔
841
        let options = ProjectListOptions {
2✔
842
            order: OrderBy::NameDesc,
2✔
843
            offset: 0,
2✔
844
            limit: 2,
2✔
845
        };
2✔
846

2✔
847
        let db = app_ctx.session_context(session.clone()).db();
2✔
848

849
        let projects = db.list_projects(options).await.unwrap();
2✔
850

2✔
851
        assert_eq!(projects.len(), 2);
2✔
852
        assert_eq!(projects[0].name, "Test9");
2✔
853
        assert_eq!(projects[1].name, "Test8");
2✔
854
        projects
2✔
855
    }
2✔
856

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

860
        for i in 0..10 {
22✔
861
            let create = CreateProject {
20✔
862
                name: format!("Test{i}"),
20✔
863
                description: format!("Test{}", 10 - i),
20✔
864
                bounds: STRectangle::new(
20✔
865
                    SpatialReferenceOption::Unreferenced,
20✔
866
                    0.,
20✔
867
                    0.,
20✔
868
                    1.,
20✔
869
                    1.,
20✔
870
                    0,
20✔
871
                    1,
20✔
872
                )
20✔
873
                .unwrap(),
20✔
874
                time_step: None,
20✔
875
            };
20✔
876
            db.create_project(create).await.unwrap();
20✔
877
        }
878
    }
2✔
879

880
    async fn user_reg_login(app_ctx: &PostgresContext<NoTls>) -> UserId {
1✔
881
        let user_registration = UserRegistration {
1✔
882
            email: "foo@example.com".into(),
1✔
883
            password: "secret123".into(),
1✔
884
            real_name: "Foo Bar".into(),
1✔
885
        };
1✔
886

887
        let user_id = app_ctx.register_user(user_registration).await.unwrap();
1✔
888

1✔
889
        let credentials = UserCredentials {
1✔
890
            email: "foo@example.com".into(),
1✔
891
            password: "secret123".into(),
1✔
892
        };
1✔
893

894
        let session = app_ctx.login(credentials).await.unwrap();
1✔
895

1✔
896
        let db = app_ctx.session_context(session.clone()).db();
1✔
897

1✔
898
        app_ctx.session_by_id(session.id).await.unwrap();
1✔
899

1✔
900
        db.logout().await.unwrap();
1✔
901

1✔
902
        assert!(app_ctx.session_by_id(session.id).await.is_err());
1✔
903

904
        user_id
1✔
905
    }
1✔
906

907
    async fn external_user_login_twice(app_ctx: &PostgresContext<NoTls>) -> UserSession {
1✔
908
        let external_user_claims = UserClaims {
1✔
909
            external_id: SubjectIdentifier::new("Foo bar Id".into()),
1✔
910
            email: "foo@bar.de".into(),
1✔
911
            real_name: "Foo Bar".into(),
1✔
912
        };
1✔
913
        let duration = Duration::minutes(30);
1✔
914

915
        let login_result = app_ctx
1✔
916
            .login_external(external_user_claims.clone(), tokens_from_duration(duration))
1✔
917
            .await;
1✔
918
        assert!(login_result.is_ok());
1✔
919

920
        let session_1 = login_result.unwrap();
1✔
921
        let user_id = session_1.user.id; //TODO: Not a deterministic test.
1✔
922

1✔
923
        let db1 = app_ctx.session_context(session_1.clone()).db();
1✔
924

1✔
925
        assert!(session_1.user.email.is_some());
1✔
926
        assert_eq!(session_1.user.email.unwrap(), "foo@bar.de");
1✔
927
        assert!(session_1.user.real_name.is_some());
1✔
928
        assert_eq!(session_1.user.real_name.unwrap(), "Foo Bar");
1✔
929

930
        let expected_duration = session_1.created + duration;
1✔
931
        assert_eq!(session_1.valid_until, expected_duration);
1✔
932

933
        assert!(app_ctx.session_by_id(session_1.id).await.is_ok());
1✔
934

935
        assert!(db1.logout().await.is_ok());
1✔
936

937
        assert!(app_ctx.session_by_id(session_1.id).await.is_err());
1✔
938

939
        let duration = Duration::minutes(10);
1✔
940
        let login_result = app_ctx
1✔
941
            .login_external(external_user_claims.clone(), tokens_from_duration(duration))
1✔
942
            .await;
1✔
943
        assert!(login_result.is_ok());
1✔
944

945
        let session_2 = login_result.unwrap();
1✔
946
        let result = session_2.clone();
1✔
947

1✔
948
        assert!(session_2.user.email.is_some()); //TODO: Technically, user details could change for each login. For simplicity, this is not covered yet.
1✔
949
        assert_eq!(session_2.user.email.unwrap(), "foo@bar.de");
1✔
950
        assert!(session_2.user.real_name.is_some());
1✔
951
        assert_eq!(session_2.user.real_name.unwrap(), "Foo Bar");
1✔
952
        assert_eq!(session_2.user.id, user_id);
1✔
953

954
        let expected_duration = session_2.created + duration;
1✔
955
        assert_eq!(session_2.valid_until, expected_duration);
1✔
956

957
        assert!(app_ctx.session_by_id(session_2.id).await.is_ok());
1✔
958

959
        result
1✔
960
    }
1✔
961

962
    async fn anonymous(app_ctx: &PostgresContext<NoTls>) {
2✔
963
        let now: DateTime = chrono::offset::Utc::now().into();
2✔
964
        let session = app_ctx.create_anonymous_session().await.unwrap();
2✔
965
        let then: DateTime = chrono::offset::Utc::now().into();
2✔
966

2✔
967
        assert!(session.created >= now && session.created <= then);
2✔
968
        assert!(session.valid_until > session.created);
2✔
969

970
        let session = app_ctx.session_by_id(session.id).await.unwrap();
2✔
971

2✔
972
        let db = app_ctx.session_context(session.clone()).db();
2✔
973

2✔
974
        db.logout().await.unwrap();
2✔
975

2✔
976
        assert!(app_ctx.session_by_id(session.id).await.is_err());
2✔
977
    }
2✔
978

979
    #[ge_context::test]
3✔
980
    async fn it_persists_workflows(app_ctx: PostgresContext<NoTls>) {
1✔
981
        let workflow = Workflow {
1✔
982
            operator: TypedOperator::Vector(
1✔
983
                MockPointSource {
1✔
984
                    params: MockPointSourceParams {
1✔
985
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
986
                    },
1✔
987
                }
1✔
988
                .boxed(),
1✔
989
            ),
1✔
990
        };
1✔
991

992
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
993
        let ctx = app_ctx.session_context(session);
1✔
994

1✔
995
        let db = ctx.db();
1✔
996
        let id = db.register_workflow(workflow).await.unwrap();
1✔
997

1✔
998
        drop(ctx);
1✔
999

1000
        let workflow = db.load_workflow(&id).await.unwrap();
1✔
1001

1✔
1002
        let json = serde_json::to_string(&workflow).unwrap();
1✔
1003
        assert_eq!(
1✔
1004
            json,
1✔
1005
            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✔
1006
        );
1✔
1007
    }
1✔
1008

1009
    #[allow(clippy::too_many_lines)]
1010
    #[ge_context::test]
3✔
1011
    async fn it_persists_datasets(app_ctx: PostgresContext<NoTls>) {
1✔
1012
        let loading_info = OgrSourceDataset {
1✔
1013
            file_name: PathBuf::from("test.csv"),
1✔
1014
            layer_name: "test.csv".to_owned(),
1✔
1015
            data_type: Some(VectorDataType::MultiPoint),
1✔
1016
            time: OgrSourceDatasetTimeType::Start {
1✔
1017
                start_field: "start".to_owned(),
1✔
1018
                start_format: OgrSourceTimeFormat::Auto,
1✔
1019
                duration: OgrSourceDurationSpec::Zero,
1✔
1020
            },
1✔
1021
            default_geometry: None,
1✔
1022
            columns: Some(OgrSourceColumnSpec {
1✔
1023
                format_specifics: Some(FormatSpecifics::Csv {
1✔
1024
                    header: CsvHeader::Auto,
1✔
1025
                }),
1✔
1026
                x: "x".to_owned(),
1✔
1027
                y: None,
1✔
1028
                int: vec![],
1✔
1029
                float: vec![],
1✔
1030
                text: vec![],
1✔
1031
                bool: vec![],
1✔
1032
                datetime: vec![],
1✔
1033
                rename: None,
1✔
1034
            }),
1✔
1035
            force_ogr_time_filter: false,
1✔
1036
            force_ogr_spatial_filter: false,
1✔
1037
            on_error: OgrSourceErrorSpec::Ignore,
1✔
1038
            sql_query: None,
1✔
1039
            attribute_query: None,
1✔
1040
            cache_ttl: CacheTtlSeconds::default(),
1✔
1041
        };
1✔
1042

1✔
1043
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
1044
            OgrSourceDataset,
1✔
1045
            VectorResultDescriptor,
1✔
1046
            VectorQueryRectangle,
1✔
1047
        > {
1✔
1048
            loading_info: loading_info.clone(),
1✔
1049
            result_descriptor: VectorResultDescriptor {
1✔
1050
                data_type: VectorDataType::MultiPoint,
1✔
1051
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1052
                columns: [(
1✔
1053
                    "foo".to_owned(),
1✔
1054
                    VectorColumnInfo {
1✔
1055
                        data_type: FeatureDataType::Float,
1✔
1056
                        measurement: Measurement::Unitless,
1✔
1057
                    },
1✔
1058
                )]
1✔
1059
                .into_iter()
1✔
1060
                .collect(),
1✔
1061
                time: None,
1✔
1062
                bbox: None,
1✔
1063
            },
1✔
1064
            phantom: Default::default(),
1✔
1065
        });
1✔
1066

1067
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
1068

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

1✔
1071
        let db = app_ctx.session_context(session.clone()).db();
1✔
1072
        let DatasetIdAndName {
1073
            id: dataset_id,
1✔
1074
            name: dataset_name,
1✔
1075
        } = db
1✔
1076
            .add_dataset(
1✔
1077
                AddDataset {
1✔
1078
                    name: Some(dataset_name.clone()),
1✔
1079
                    display_name: "Ogr Test".to_owned(),
1✔
1080
                    description: "desc".to_owned(),
1✔
1081
                    source_operator: "OgrSource".to_owned(),
1✔
1082
                    symbology: None,
1✔
1083
                    provenance: Some(vec![Provenance {
1✔
1084
                        citation: "citation".to_owned(),
1✔
1085
                        license: "license".to_owned(),
1✔
1086
                        uri: "uri".to_owned(),
1✔
1087
                    }]),
1✔
1088
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1089
                },
1✔
1090
                meta_data,
1✔
1091
            )
1✔
1092
            .await
1✔
1093
            .unwrap();
1✔
1094

1095
        let datasets = db
1✔
1096
            .list_datasets(DatasetListOptions {
1✔
1097
                filter: None,
1✔
1098
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
1099
                offset: 0,
1✔
1100
                limit: 10,
1✔
1101
                tags: None,
1✔
1102
            })
1✔
1103
            .await
1✔
1104
            .unwrap();
1✔
1105

1✔
1106
        assert_eq!(datasets.len(), 1);
1✔
1107

1108
        assert_eq!(
1✔
1109
            datasets[0],
1✔
1110
            DatasetListing {
1✔
1111
                id: dataset_id,
1✔
1112
                name: dataset_name,
1✔
1113
                display_name: "Ogr Test".to_owned(),
1✔
1114
                description: "desc".to_owned(),
1✔
1115
                source_operator: "OgrSource".to_owned(),
1✔
1116
                symbology: None,
1✔
1117
                tags: vec!["upload".to_owned(), "test".to_owned()],
1✔
1118
                result_descriptor: TypedResultDescriptor::Vector(VectorResultDescriptor {
1✔
1119
                    data_type: VectorDataType::MultiPoint,
1✔
1120
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1121
                    columns: [(
1✔
1122
                        "foo".to_owned(),
1✔
1123
                        VectorColumnInfo {
1✔
1124
                            data_type: FeatureDataType::Float,
1✔
1125
                            measurement: Measurement::Unitless
1✔
1126
                        }
1✔
1127
                    )]
1✔
1128
                    .into_iter()
1✔
1129
                    .collect(),
1✔
1130
                    time: None,
1✔
1131
                    bbox: None,
1✔
1132
                })
1✔
1133
            },
1✔
1134
        );
1✔
1135

1136
        let provenance = db.load_provenance(&dataset_id).await.unwrap();
1✔
1137

1✔
1138
        assert_eq!(
1✔
1139
            provenance,
1✔
1140
            ProvenanceOutput {
1✔
1141
                data: dataset_id.into(),
1✔
1142
                provenance: Some(vec![Provenance {
1✔
1143
                    citation: "citation".to_owned(),
1✔
1144
                    license: "license".to_owned(),
1✔
1145
                    uri: "uri".to_owned(),
1✔
1146
                }])
1✔
1147
            }
1✔
1148
        );
1✔
1149

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

1153
        assert_eq!(
1✔
1154
            meta_data
1✔
1155
                .loading_info(VectorQueryRectangle {
1✔
1156
                    spatial_bounds: BoundingBox2D::new_unchecked(
1✔
1157
                        (-180., -90.).into(),
1✔
1158
                        (180., 90.).into()
1✔
1159
                    ),
1✔
1160
                    time_interval: TimeInterval::default(),
1✔
1161
                    spatial_resolution: SpatialResolution::zero_point_one(),
1✔
1162
                    attributes: ColumnSelection::all()
1✔
1163
                })
1✔
1164
                .await
1✔
1165
                .unwrap(),
1✔
1166
            loading_info
1167
        );
1168
    }
1✔
1169

1170
    #[ge_context::test]
3✔
1171
    async fn it_persists_uploads(app_ctx: PostgresContext<NoTls>) {
1✔
1172
        let id = UploadId::from_str("2de18cd8-4a38-4111-a445-e3734bc18a80").unwrap();
1✔
1173
        let input = Upload {
1✔
1174
            id,
1✔
1175
            files: vec![FileUpload {
1✔
1176
                id: FileId::from_str("e80afab0-831d-4d40-95d6-1e4dfd277e72").unwrap(),
1✔
1177
                name: "test.csv".to_owned(),
1✔
1178
                byte_size: 1337,
1✔
1179
            }],
1✔
1180
        };
1✔
1181

1182
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
1183

1✔
1184
        let db = app_ctx.session_context(session.clone()).db();
1✔
1185

1✔
1186
        db.create_upload(input.clone()).await.unwrap();
1✔
1187

1188
        let upload = db.load_upload(id).await.unwrap();
1✔
1189

1✔
1190
        assert_eq!(upload, input);
1✔
1191
    }
1✔
1192

1193
    #[allow(clippy::too_many_lines)]
1194
    #[ge_context::test]
3✔
1195
    async fn it_persists_layer_providers(app_ctx: PostgresContext<NoTls>) {
1✔
1196
        let db = app_ctx.session_context(UserSession::admin_session()).db();
1✔
1197

1✔
1198
        let provider = NetCdfCfDataProviderDefinition {
1✔
1199
            name: "netcdfcf".to_string(),
1✔
1200
            description: "NetCdfCfProviderDefinition".to_string(),
1✔
1201
            priority: Some(33),
1✔
1202
            data: test_data!("netcdf4d/").into(),
1✔
1203
            overviews: test_data!("netcdf4d/overviews/").into(),
1✔
1204
            cache_ttl: CacheTtlSeconds::new(0),
1✔
1205
        };
1✔
1206

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

1209
        let providers = db
1✔
1210
            .list_layer_providers(LayerProviderListingOptions {
1✔
1211
                offset: 0,
1✔
1212
                limit: 10,
1✔
1213
            })
1✔
1214
            .await
1✔
1215
            .unwrap();
1✔
1216

1✔
1217
        assert_eq!(providers.len(), 1);
1✔
1218

1219
        assert_eq!(
1✔
1220
            providers[0],
1✔
1221
            LayerProviderListing {
1✔
1222
                id: provider_id,
1✔
1223
                name: "netcdfcf".to_owned(),
1✔
1224
                priority: 33,
1✔
1225
            }
1✔
1226
        );
1✔
1227

1228
        let provider = db.load_layer_provider(provider_id).await.unwrap();
1✔
1229

1230
        let datasets = provider
1✔
1231
            .load_layer_collection(
1✔
1232
                &provider.get_root_layer_collection_id().await.unwrap(),
1✔
1233
                LayerCollectionListOptions {
1✔
1234
                    offset: 0,
1✔
1235
                    limit: 10,
1✔
1236
                },
1✔
1237
            )
1✔
1238
            .await
1✔
1239
            .unwrap();
1✔
1240

1✔
1241
        assert_eq!(datasets.items.len(), 5);
1✔
1242
    }
1✔
1243

1244
    #[ge_context::test]
3✔
1245
    async fn it_lists_only_permitted_datasets(app_ctx: PostgresContext<NoTls>) {
1✔
1246
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1247
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1248

1✔
1249
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1250
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1251

1✔
1252
        let descriptor = VectorResultDescriptor {
1✔
1253
            data_type: VectorDataType::Data,
1✔
1254
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1255
            columns: Default::default(),
1✔
1256
            time: None,
1✔
1257
            bbox: None,
1✔
1258
        };
1✔
1259

1✔
1260
        let ds = AddDataset {
1✔
1261
            name: None,
1✔
1262
            display_name: "OgrDataset".to_string(),
1✔
1263
            description: "My Ogr dataset".to_string(),
1✔
1264
            source_operator: "OgrSource".to_string(),
1✔
1265
            symbology: None,
1✔
1266
            provenance: None,
1✔
1267
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1268
        };
1✔
1269

1✔
1270
        let meta = StaticMetaData {
1✔
1271
            loading_info: OgrSourceDataset {
1✔
1272
                file_name: Default::default(),
1✔
1273
                layer_name: String::new(),
1✔
1274
                data_type: None,
1✔
1275
                time: Default::default(),
1✔
1276
                default_geometry: None,
1✔
1277
                columns: None,
1✔
1278
                force_ogr_time_filter: false,
1✔
1279
                force_ogr_spatial_filter: false,
1✔
1280
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1281
                sql_query: None,
1✔
1282
                attribute_query: None,
1✔
1283
                cache_ttl: CacheTtlSeconds::default(),
1✔
1284
            },
1✔
1285
            result_descriptor: descriptor.clone(),
1✔
1286
            phantom: Default::default(),
1✔
1287
        };
1✔
1288

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

1291
        let list1 = db1
1✔
1292
            .list_datasets(DatasetListOptions {
1✔
1293
                filter: None,
1✔
1294
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
1295
                offset: 0,
1✔
1296
                limit: 1,
1✔
1297
                tags: None,
1✔
1298
            })
1✔
1299
            .await
1✔
1300
            .unwrap();
1✔
1301

1✔
1302
        assert_eq!(list1.len(), 1);
1✔
1303

1304
        let list2 = db2
1✔
1305
            .list_datasets(DatasetListOptions {
1✔
1306
                filter: None,
1✔
1307
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
1308
                offset: 0,
1✔
1309
                limit: 1,
1✔
1310
                tags: None,
1✔
1311
            })
1✔
1312
            .await
1✔
1313
            .unwrap();
1✔
1314

1✔
1315
        assert_eq!(list2.len(), 0);
1✔
1316
    }
1✔
1317

1318
    #[ge_context::test]
3✔
1319
    async fn it_shows_only_permitted_provenance(app_ctx: PostgresContext<NoTls>) {
1✔
1320
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1321
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1322

1✔
1323
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1324
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1325

1✔
1326
        let descriptor = VectorResultDescriptor {
1✔
1327
            data_type: VectorDataType::Data,
1✔
1328
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1329
            columns: Default::default(),
1✔
1330
            time: None,
1✔
1331
            bbox: None,
1✔
1332
        };
1✔
1333

1✔
1334
        let ds = AddDataset {
1✔
1335
            name: None,
1✔
1336
            display_name: "OgrDataset".to_string(),
1✔
1337
            description: "My Ogr dataset".to_string(),
1✔
1338
            source_operator: "OgrSource".to_string(),
1✔
1339
            symbology: None,
1✔
1340
            provenance: None,
1✔
1341
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1342
        };
1✔
1343

1✔
1344
        let meta = StaticMetaData {
1✔
1345
            loading_info: OgrSourceDataset {
1✔
1346
                file_name: Default::default(),
1✔
1347
                layer_name: String::new(),
1✔
1348
                data_type: None,
1✔
1349
                time: Default::default(),
1✔
1350
                default_geometry: None,
1✔
1351
                columns: None,
1✔
1352
                force_ogr_time_filter: false,
1✔
1353
                force_ogr_spatial_filter: false,
1✔
1354
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1355
                sql_query: None,
1✔
1356
                attribute_query: None,
1✔
1357
                cache_ttl: CacheTtlSeconds::default(),
1✔
1358
            },
1✔
1359
            result_descriptor: descriptor.clone(),
1✔
1360
            phantom: Default::default(),
1✔
1361
        };
1✔
1362

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

1✔
1365
        assert!(db1.load_provenance(&id).await.is_ok());
1✔
1366

1367
        assert!(db2.load_provenance(&id).await.is_err());
1✔
1368
    }
1✔
1369

1370
    #[ge_context::test]
3✔
1371
    async fn it_updates_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
1372
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1373
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1374

1✔
1375
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1376
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1377

1✔
1378
        let descriptor = VectorResultDescriptor {
1✔
1379
            data_type: VectorDataType::Data,
1✔
1380
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1381
            columns: Default::default(),
1✔
1382
            time: None,
1✔
1383
            bbox: None,
1✔
1384
        };
1✔
1385

1✔
1386
        let ds = AddDataset {
1✔
1387
            name: None,
1✔
1388
            display_name: "OgrDataset".to_string(),
1✔
1389
            description: "My Ogr dataset".to_string(),
1✔
1390
            source_operator: "OgrSource".to_string(),
1✔
1391
            symbology: None,
1✔
1392
            provenance: None,
1✔
1393
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1394
        };
1✔
1395

1✔
1396
        let meta = StaticMetaData {
1✔
1397
            loading_info: OgrSourceDataset {
1✔
1398
                file_name: Default::default(),
1✔
1399
                layer_name: String::new(),
1✔
1400
                data_type: None,
1✔
1401
                time: Default::default(),
1✔
1402
                default_geometry: None,
1✔
1403
                columns: None,
1✔
1404
                force_ogr_time_filter: false,
1✔
1405
                force_ogr_spatial_filter: false,
1✔
1406
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1407
                sql_query: None,
1✔
1408
                attribute_query: None,
1✔
1409
                cache_ttl: CacheTtlSeconds::default(),
1✔
1410
            },
1✔
1411
            result_descriptor: descriptor.clone(),
1✔
1412
            phantom: Default::default(),
1✔
1413
        };
1✔
1414

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

1✔
1417
        assert!(db1.load_dataset(&id).await.is_ok());
1✔
1418

1419
        assert!(db2.load_dataset(&id).await.is_err());
1✔
1420

1421
        db1.add_permission(session2.user.id.into(), id, Permission::Read)
1✔
1422
            .await
1✔
1423
            .unwrap();
1✔
1424

1✔
1425
        assert!(db2.load_dataset(&id).await.is_ok());
1✔
1426
    }
1✔
1427

1428
    #[ge_context::test]
3✔
1429
    async fn it_uses_roles_for_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
1430
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1431
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1432

1✔
1433
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1434
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1435

1✔
1436
        let descriptor = VectorResultDescriptor {
1✔
1437
            data_type: VectorDataType::Data,
1✔
1438
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1439
            columns: Default::default(),
1✔
1440
            time: None,
1✔
1441
            bbox: None,
1✔
1442
        };
1✔
1443

1✔
1444
        let ds = AddDataset {
1✔
1445
            name: None,
1✔
1446
            display_name: "OgrDataset".to_string(),
1✔
1447
            description: "My Ogr dataset".to_string(),
1✔
1448
            source_operator: "OgrSource".to_string(),
1✔
1449
            symbology: None,
1✔
1450
            provenance: None,
1✔
1451
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1452
        };
1✔
1453

1✔
1454
        let meta = StaticMetaData {
1✔
1455
            loading_info: OgrSourceDataset {
1✔
1456
                file_name: Default::default(),
1✔
1457
                layer_name: String::new(),
1✔
1458
                data_type: None,
1✔
1459
                time: Default::default(),
1✔
1460
                default_geometry: None,
1✔
1461
                columns: None,
1✔
1462
                force_ogr_time_filter: false,
1✔
1463
                force_ogr_spatial_filter: false,
1✔
1464
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1465
                sql_query: None,
1✔
1466
                attribute_query: None,
1✔
1467
                cache_ttl: CacheTtlSeconds::default(),
1✔
1468
            },
1✔
1469
            result_descriptor: descriptor.clone(),
1✔
1470
            phantom: Default::default(),
1✔
1471
        };
1✔
1472

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

1✔
1475
        assert!(db1.load_dataset(&id).await.is_ok());
1✔
1476

1477
        assert!(db2.load_dataset(&id).await.is_err());
1✔
1478

1479
        db1.add_permission(session2.user.id.into(), id, Permission::Read)
1✔
1480
            .await
1✔
1481
            .unwrap();
1✔
1482

1✔
1483
        assert!(db2.load_dataset(&id).await.is_ok());
1✔
1484
    }
1✔
1485

1486
    #[ge_context::test]
3✔
1487
    async fn it_secures_meta_data(app_ctx: PostgresContext<NoTls>) {
1✔
1488
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1489
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1490

1✔
1491
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1492
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1493

1✔
1494
        let descriptor = VectorResultDescriptor {
1✔
1495
            data_type: VectorDataType::Data,
1✔
1496
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1497
            columns: Default::default(),
1✔
1498
            time: None,
1✔
1499
            bbox: None,
1✔
1500
        };
1✔
1501

1✔
1502
        let ds = AddDataset {
1✔
1503
            name: None,
1✔
1504
            display_name: "OgrDataset".to_string(),
1✔
1505
            description: "My Ogr dataset".to_string(),
1✔
1506
            source_operator: "OgrSource".to_string(),
1✔
1507
            symbology: None,
1✔
1508
            provenance: None,
1✔
1509
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1510
        };
1✔
1511

1✔
1512
        let meta = StaticMetaData {
1✔
1513
            loading_info: OgrSourceDataset {
1✔
1514
                file_name: Default::default(),
1✔
1515
                layer_name: String::new(),
1✔
1516
                data_type: None,
1✔
1517
                time: Default::default(),
1✔
1518
                default_geometry: None,
1✔
1519
                columns: None,
1✔
1520
                force_ogr_time_filter: false,
1✔
1521
                force_ogr_spatial_filter: false,
1✔
1522
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1523
                sql_query: None,
1✔
1524
                attribute_query: None,
1✔
1525
                cache_ttl: CacheTtlSeconds::default(),
1✔
1526
            },
1✔
1527
            result_descriptor: descriptor.clone(),
1✔
1528
            phantom: Default::default(),
1✔
1529
        };
1✔
1530

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

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

1537
        assert!(meta.is_ok());
1✔
1538

1539
        let meta: geoengine_operators::util::Result<
1✔
1540
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1541
        > = db2.meta_data(&id.into()).await;
1✔
1542

1543
        assert!(meta.is_err());
1✔
1544

1545
        db1.add_permission(session2.user.id.into(), id, Permission::Read)
1✔
1546
            .await
1✔
1547
            .unwrap();
1✔
1548

1549
        let meta: geoengine_operators::util::Result<
1✔
1550
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1551
        > = db2.meta_data(&id.into()).await;
1✔
1552

1553
        assert!(meta.is_ok());
1✔
1554
    }
1✔
1555

1556
    #[allow(clippy::too_many_lines)]
1557
    #[ge_context::test]
3✔
1558
    async fn it_loads_all_meta_data_types(app_ctx: PostgresContext<NoTls>) {
1✔
1559
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
1560

1✔
1561
        let db = app_ctx.session_context(session.clone()).db();
1✔
1562

1✔
1563
        let vector_descriptor = VectorResultDescriptor {
1✔
1564
            data_type: VectorDataType::Data,
1✔
1565
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1566
            columns: Default::default(),
1✔
1567
            time: None,
1✔
1568
            bbox: None,
1✔
1569
        };
1✔
1570

1✔
1571
        let raster_descriptor = RasterResultDescriptor {
1✔
1572
            data_type: RasterDataType::U8,
1✔
1573
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1574
            time: None,
1✔
1575
            bbox: None,
1✔
1576
            resolution: None,
1✔
1577
            bands: RasterBandDescriptors::new_single_band(),
1✔
1578
        };
1✔
1579

1✔
1580
        let vector_ds = AddDataset {
1✔
1581
            name: None,
1✔
1582
            display_name: "OgrDataset".to_string(),
1✔
1583
            description: "My Ogr dataset".to_string(),
1✔
1584
            source_operator: "OgrSource".to_string(),
1✔
1585
            symbology: None,
1✔
1586
            provenance: None,
1✔
1587
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1588
        };
1✔
1589

1✔
1590
        let raster_ds = AddDataset {
1✔
1591
            name: None,
1✔
1592
            display_name: "GdalDataset".to_string(),
1✔
1593
            description: "My Gdal dataset".to_string(),
1✔
1594
            source_operator: "GdalSource".to_string(),
1✔
1595
            symbology: None,
1✔
1596
            provenance: None,
1✔
1597
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1598
        };
1✔
1599

1✔
1600
        let gdal_params = GdalDatasetParameters {
1✔
1601
            file_path: Default::default(),
1✔
1602
            rasterband_channel: 0,
1✔
1603
            geo_transform: GdalDatasetGeoTransform {
1✔
1604
                origin_coordinate: Default::default(),
1✔
1605
                x_pixel_size: 0.0,
1✔
1606
                y_pixel_size: 0.0,
1✔
1607
            },
1✔
1608
            width: 0,
1✔
1609
            height: 0,
1✔
1610
            file_not_found_handling: FileNotFoundHandling::NoData,
1✔
1611
            no_data_value: None,
1✔
1612
            properties_mapping: None,
1✔
1613
            gdal_open_options: None,
1✔
1614
            gdal_config_options: None,
1✔
1615
            allow_alphaband_as_mask: false,
1✔
1616
            retry: None,
1✔
1617
        };
1✔
1618

1✔
1619
        let meta = StaticMetaData {
1✔
1620
            loading_info: OgrSourceDataset {
1✔
1621
                file_name: Default::default(),
1✔
1622
                layer_name: String::new(),
1✔
1623
                data_type: None,
1✔
1624
                time: Default::default(),
1✔
1625
                default_geometry: None,
1✔
1626
                columns: None,
1✔
1627
                force_ogr_time_filter: false,
1✔
1628
                force_ogr_spatial_filter: false,
1✔
1629
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1630
                sql_query: None,
1✔
1631
                attribute_query: None,
1✔
1632
                cache_ttl: CacheTtlSeconds::default(),
1✔
1633
            },
1✔
1634
            result_descriptor: vector_descriptor.clone(),
1✔
1635
            phantom: Default::default(),
1✔
1636
        };
1✔
1637

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

1640
        let meta: geoengine_operators::util::Result<
1✔
1641
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1642
        > = db.meta_data(&id.into()).await;
1✔
1643

1644
        assert!(meta.is_ok());
1✔
1645

1646
        let meta = GdalMetaDataRegular {
1✔
1647
            result_descriptor: raster_descriptor.clone(),
1✔
1648
            params: gdal_params.clone(),
1✔
1649
            time_placeholders: Default::default(),
1✔
1650
            data_time: Default::default(),
1✔
1651
            step: TimeStep {
1✔
1652
                granularity: TimeGranularity::Millis,
1✔
1653
                step: 0,
1✔
1654
            },
1✔
1655
            cache_ttl: CacheTtlSeconds::default(),
1✔
1656
        };
1✔
1657

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

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

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

1670
        let meta = GdalMetaDataStatic {
1✔
1671
            time: None,
1✔
1672
            params: gdal_params.clone(),
1✔
1673
            result_descriptor: raster_descriptor.clone(),
1✔
1674
            cache_ttl: CacheTtlSeconds::default(),
1✔
1675
        };
1✔
1676

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

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

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

1689
        let meta = GdalMetaDataList {
1✔
1690
            result_descriptor: raster_descriptor.clone(),
1✔
1691
            params: vec![],
1✔
1692
        };
1✔
1693

1694
        let id = db
1✔
1695
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1696
            .await
1✔
1697
            .unwrap()
1✔
1698
            .id;
1699

1700
        let meta: geoengine_operators::util::Result<
1✔
1701
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1702
        > = db.meta_data(&id.into()).await;
1✔
1703

1704
        assert!(meta.is_ok());
1✔
1705

1706
        let meta = GdalMetadataNetCdfCf {
1✔
1707
            result_descriptor: raster_descriptor.clone(),
1✔
1708
            params: gdal_params.clone(),
1✔
1709
            start: TimeInstance::MIN,
1✔
1710
            end: TimeInstance::MAX,
1✔
1711
            step: TimeStep {
1✔
1712
                granularity: TimeGranularity::Millis,
1✔
1713
                step: 0,
1✔
1714
            },
1✔
1715
            band_offset: 0,
1✔
1716
            cache_ttl: CacheTtlSeconds::default(),
1✔
1717
        };
1✔
1718

1719
        let id = db
1✔
1720
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1721
            .await
1✔
1722
            .unwrap()
1✔
1723
            .id;
1724

1725
        let meta: geoengine_operators::util::Result<
1✔
1726
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1727
        > = db.meta_data(&id.into()).await;
1✔
1728

1729
        assert!(meta.is_ok());
1✔
1730
    }
1✔
1731

1732
    #[ge_context::test]
3✔
1733
    async fn it_secures_uploads(app_ctx: PostgresContext<NoTls>) {
1✔
1734
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1735
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1736

1✔
1737
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1738
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1739

1✔
1740
        let upload_id = UploadId::new();
1✔
1741

1✔
1742
        let upload = Upload {
1✔
1743
            id: upload_id,
1✔
1744
            files: vec![FileUpload {
1✔
1745
                id: FileId::new(),
1✔
1746
                name: "test.bin".to_owned(),
1✔
1747
                byte_size: 1024,
1✔
1748
            }],
1✔
1749
        };
1✔
1750

1✔
1751
        db1.create_upload(upload).await.unwrap();
1✔
1752

1✔
1753
        assert!(db1.load_upload(upload_id).await.is_ok());
1✔
1754

1755
        assert!(db2.load_upload(upload_id).await.is_err());
1✔
1756
    }
1✔
1757

1758
    #[allow(clippy::too_many_lines)]
1759
    #[ge_context::test]
3✔
1760
    async fn it_collects_layers(app_ctx: PostgresContext<NoTls>) {
1✔
1761
        let session = admin_login(&app_ctx).await;
1✔
1762

1763
        let layer_db = app_ctx.session_context(session).db();
1✔
1764

1✔
1765
        let workflow = Workflow {
1✔
1766
            operator: TypedOperator::Vector(
1✔
1767
                MockPointSource {
1✔
1768
                    params: MockPointSourceParams {
1✔
1769
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1770
                    },
1✔
1771
                }
1✔
1772
                .boxed(),
1✔
1773
            ),
1✔
1774
        };
1✔
1775

1776
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1777

1778
        let layer1 = layer_db
1✔
1779
            .add_layer(
1✔
1780
                AddLayer {
1✔
1781
                    name: "Layer1".to_string(),
1✔
1782
                    description: "Layer 1".to_string(),
1✔
1783
                    symbology: None,
1✔
1784
                    workflow: workflow.clone(),
1✔
1785
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1786
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1787
                },
1✔
1788
                &root_collection_id,
1✔
1789
            )
1✔
1790
            .await
1✔
1791
            .unwrap();
1✔
1792

1793
        assert_eq!(
1✔
1794
            layer_db.load_layer(&layer1).await.unwrap(),
1✔
1795
            crate::layers::layer::Layer {
1✔
1796
                id: ProviderLayerId {
1✔
1797
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1798
                    layer_id: layer1.clone(),
1✔
1799
                },
1✔
1800
                name: "Layer1".to_string(),
1✔
1801
                description: "Layer 1".to_string(),
1✔
1802
                symbology: None,
1✔
1803
                workflow: workflow.clone(),
1✔
1804
                metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1805
                properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1806
            }
1✔
1807
        );
1808

1809
        let collection1_id = layer_db
1✔
1810
            .add_layer_collection(
1✔
1811
                AddLayerCollection {
1✔
1812
                    name: "Collection1".to_string(),
1✔
1813
                    description: "Collection 1".to_string(),
1✔
1814
                    properties: Default::default(),
1✔
1815
                },
1✔
1816
                &root_collection_id,
1✔
1817
            )
1✔
1818
            .await
1✔
1819
            .unwrap();
1✔
1820

1821
        let layer2 = layer_db
1✔
1822
            .add_layer(
1✔
1823
                AddLayer {
1✔
1824
                    name: "Layer2".to_string(),
1✔
1825
                    description: "Layer 2".to_string(),
1✔
1826
                    symbology: None,
1✔
1827
                    workflow: workflow.clone(),
1✔
1828
                    metadata: Default::default(),
1✔
1829
                    properties: Default::default(),
1✔
1830
                },
1✔
1831
                &collection1_id,
1✔
1832
            )
1✔
1833
            .await
1✔
1834
            .unwrap();
1✔
1835

1836
        let collection2_id = layer_db
1✔
1837
            .add_layer_collection(
1✔
1838
                AddLayerCollection {
1✔
1839
                    name: "Collection2".to_string(),
1✔
1840
                    description: "Collection 2".to_string(),
1✔
1841
                    properties: Default::default(),
1✔
1842
                },
1✔
1843
                &collection1_id,
1✔
1844
            )
1✔
1845
            .await
1✔
1846
            .unwrap();
1✔
1847

1✔
1848
        layer_db
1✔
1849
            .add_collection_to_parent(&collection2_id, &collection1_id)
1✔
1850
            .await
1✔
1851
            .unwrap();
1✔
1852

1853
        let root_collection = layer_db
1✔
1854
            .load_layer_collection(
1✔
1855
                &root_collection_id,
1✔
1856
                LayerCollectionListOptions {
1✔
1857
                    offset: 0,
1✔
1858
                    limit: 20,
1✔
1859
                },
1✔
1860
            )
1✔
1861
            .await
1✔
1862
            .unwrap();
1✔
1863

1✔
1864
        assert_eq!(
1✔
1865
            root_collection,
1✔
1866
            LayerCollection {
1✔
1867
                id: ProviderLayerCollectionId {
1✔
1868
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1869
                    collection_id: root_collection_id,
1✔
1870
                },
1✔
1871
                name: "Layers".to_string(),
1✔
1872
                description: "All available Geo Engine layers".to_string(),
1✔
1873
                items: vec![
1✔
1874
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1875
                        id: ProviderLayerCollectionId {
1✔
1876
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1877
                            collection_id: collection1_id.clone(),
1✔
1878
                        },
1✔
1879
                        name: "Collection1".to_string(),
1✔
1880
                        description: "Collection 1".to_string(),
1✔
1881
                        properties: Default::default(),
1✔
1882
                    }),
1✔
1883
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1884
                        id: ProviderLayerCollectionId {
1✔
1885
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1886
                            collection_id: LayerCollectionId(UNSORTED_COLLECTION_ID.to_string()),
1✔
1887
                        },
1✔
1888
                        name: "Unsorted".to_string(),
1✔
1889
                        description: "Unsorted Layers".to_string(),
1✔
1890
                        properties: Default::default(),
1✔
1891
                    }),
1✔
1892
                    CollectionItem::Layer(LayerListing {
1✔
1893
                        id: ProviderLayerId {
1✔
1894
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1895
                            layer_id: layer1,
1✔
1896
                        },
1✔
1897
                        name: "Layer1".to_string(),
1✔
1898
                        description: "Layer 1".to_string(),
1✔
1899
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1900
                    })
1✔
1901
                ],
1✔
1902
                entry_label: None,
1✔
1903
                properties: vec![],
1✔
1904
            }
1✔
1905
        );
1✔
1906

1907
        let collection1 = layer_db
1✔
1908
            .load_layer_collection(
1✔
1909
                &collection1_id,
1✔
1910
                LayerCollectionListOptions {
1✔
1911
                    offset: 0,
1✔
1912
                    limit: 20,
1✔
1913
                },
1✔
1914
            )
1✔
1915
            .await
1✔
1916
            .unwrap();
1✔
1917

1✔
1918
        assert_eq!(
1✔
1919
            collection1,
1✔
1920
            LayerCollection {
1✔
1921
                id: ProviderLayerCollectionId {
1✔
1922
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1923
                    collection_id: collection1_id,
1✔
1924
                },
1✔
1925
                name: "Collection1".to_string(),
1✔
1926
                description: "Collection 1".to_string(),
1✔
1927
                items: vec![
1✔
1928
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1929
                        id: ProviderLayerCollectionId {
1✔
1930
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1931
                            collection_id: collection2_id,
1✔
1932
                        },
1✔
1933
                        name: "Collection2".to_string(),
1✔
1934
                        description: "Collection 2".to_string(),
1✔
1935
                        properties: Default::default(),
1✔
1936
                    }),
1✔
1937
                    CollectionItem::Layer(LayerListing {
1✔
1938
                        id: ProviderLayerId {
1✔
1939
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1940
                            layer_id: layer2,
1✔
1941
                        },
1✔
1942
                        name: "Layer2".to_string(),
1✔
1943
                        description: "Layer 2".to_string(),
1✔
1944
                        properties: vec![],
1✔
1945
                    })
1✔
1946
                ],
1✔
1947
                entry_label: None,
1✔
1948
                properties: vec![],
1✔
1949
            }
1✔
1950
        );
1✔
1951
    }
1✔
1952

1953
    #[allow(clippy::too_many_lines)]
1954
    #[ge_context::test]
3✔
1955
    async fn it_searches_layers(app_ctx: PostgresContext<NoTls>) {
1✔
1956
        let session = admin_login(&app_ctx).await;
1✔
1957

1958
        let layer_db = app_ctx.session_context(session).db();
1✔
1959

1✔
1960
        let workflow = Workflow {
1✔
1961
            operator: TypedOperator::Vector(
1✔
1962
                MockPointSource {
1✔
1963
                    params: MockPointSourceParams {
1✔
1964
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1965
                    },
1✔
1966
                }
1✔
1967
                .boxed(),
1✔
1968
            ),
1✔
1969
        };
1✔
1970

1971
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1972

1973
        let layer1 = layer_db
1✔
1974
            .add_layer(
1✔
1975
                AddLayer {
1✔
1976
                    name: "Layer1".to_string(),
1✔
1977
                    description: "Layer 1".to_string(),
1✔
1978
                    symbology: None,
1✔
1979
                    workflow: workflow.clone(),
1✔
1980
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1981
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1982
                },
1✔
1983
                &root_collection_id,
1✔
1984
            )
1✔
1985
            .await
1✔
1986
            .unwrap();
1✔
1987

1988
        let collection1_id = layer_db
1✔
1989
            .add_layer_collection(
1✔
1990
                AddLayerCollection {
1✔
1991
                    name: "Collection1".to_string(),
1✔
1992
                    description: "Collection 1".to_string(),
1✔
1993
                    properties: Default::default(),
1✔
1994
                },
1✔
1995
                &root_collection_id,
1✔
1996
            )
1✔
1997
            .await
1✔
1998
            .unwrap();
1✔
1999

2000
        let layer2 = layer_db
1✔
2001
            .add_layer(
1✔
2002
                AddLayer {
1✔
2003
                    name: "Layer2".to_string(),
1✔
2004
                    description: "Layer 2".to_string(),
1✔
2005
                    symbology: None,
1✔
2006
                    workflow: workflow.clone(),
1✔
2007
                    metadata: Default::default(),
1✔
2008
                    properties: Default::default(),
1✔
2009
                },
1✔
2010
                &collection1_id,
1✔
2011
            )
1✔
2012
            .await
1✔
2013
            .unwrap();
1✔
2014

2015
        let collection2_id = layer_db
1✔
2016
            .add_layer_collection(
1✔
2017
                AddLayerCollection {
1✔
2018
                    name: "Collection2".to_string(),
1✔
2019
                    description: "Collection 2".to_string(),
1✔
2020
                    properties: Default::default(),
1✔
2021
                },
1✔
2022
                &collection1_id,
1✔
2023
            )
1✔
2024
            .await
1✔
2025
            .unwrap();
1✔
2026

2027
        let root_collection_all = layer_db
1✔
2028
            .search(
1✔
2029
                &root_collection_id,
1✔
2030
                SearchParameters {
1✔
2031
                    search_type: SearchType::Fulltext,
1✔
2032
                    search_string: String::new(),
1✔
2033
                    limit: 10,
1✔
2034
                    offset: 0,
1✔
2035
                },
1✔
2036
            )
1✔
2037
            .await
1✔
2038
            .unwrap();
1✔
2039

1✔
2040
        assert_eq!(
1✔
2041
            root_collection_all,
1✔
2042
            LayerCollection {
1✔
2043
                id: ProviderLayerCollectionId {
1✔
2044
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2045
                    collection_id: root_collection_id.clone(),
1✔
2046
                },
1✔
2047
                name: "Layers".to_string(),
1✔
2048
                description: "All available Geo Engine layers".to_string(),
1✔
2049
                items: vec![
1✔
2050
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2051
                        id: ProviderLayerCollectionId {
1✔
2052
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2053
                            collection_id: collection1_id.clone(),
1✔
2054
                        },
1✔
2055
                        name: "Collection1".to_string(),
1✔
2056
                        description: "Collection 1".to_string(),
1✔
2057
                        properties: Default::default(),
1✔
2058
                    }),
1✔
2059
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2060
                        id: ProviderLayerCollectionId {
1✔
2061
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2062
                            collection_id: collection2_id.clone(),
1✔
2063
                        },
1✔
2064
                        name: "Collection2".to_string(),
1✔
2065
                        description: "Collection 2".to_string(),
1✔
2066
                        properties: Default::default(),
1✔
2067
                    }),
1✔
2068
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2069
                        id: ProviderLayerCollectionId {
1✔
2070
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2071
                            collection_id: LayerCollectionId(
1✔
2072
                                "ffb2dd9e-f5ad-427c-b7f1-c9a0c7a0ae3f".to_string()
1✔
2073
                            ),
1✔
2074
                        },
1✔
2075
                        name: "Unsorted".to_string(),
1✔
2076
                        description: "Unsorted Layers".to_string(),
1✔
2077
                        properties: Default::default(),
1✔
2078
                    }),
1✔
2079
                    CollectionItem::Layer(LayerListing {
1✔
2080
                        id: ProviderLayerId {
1✔
2081
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2082
                            layer_id: layer1.clone(),
1✔
2083
                        },
1✔
2084
                        name: "Layer1".to_string(),
1✔
2085
                        description: "Layer 1".to_string(),
1✔
2086
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2087
                    }),
1✔
2088
                    CollectionItem::Layer(LayerListing {
1✔
2089
                        id: ProviderLayerId {
1✔
2090
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2091
                            layer_id: layer2.clone(),
1✔
2092
                        },
1✔
2093
                        name: "Layer2".to_string(),
1✔
2094
                        description: "Layer 2".to_string(),
1✔
2095
                        properties: vec![],
1✔
2096
                    }),
1✔
2097
                ],
1✔
2098
                entry_label: None,
1✔
2099
                properties: vec![],
1✔
2100
            }
1✔
2101
        );
1✔
2102

2103
        let root_collection_filtered = layer_db
1✔
2104
            .search(
1✔
2105
                &root_collection_id,
1✔
2106
                SearchParameters {
1✔
2107
                    search_type: SearchType::Fulltext,
1✔
2108
                    search_string: "lection".to_string(),
1✔
2109
                    limit: 10,
1✔
2110
                    offset: 0,
1✔
2111
                },
1✔
2112
            )
1✔
2113
            .await
1✔
2114
            .unwrap();
1✔
2115

1✔
2116
        assert_eq!(
1✔
2117
            root_collection_filtered,
1✔
2118
            LayerCollection {
1✔
2119
                id: ProviderLayerCollectionId {
1✔
2120
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2121
                    collection_id: root_collection_id.clone(),
1✔
2122
                },
1✔
2123
                name: "Layers".to_string(),
1✔
2124
                description: "All available Geo Engine layers".to_string(),
1✔
2125
                items: vec![
1✔
2126
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2127
                        id: ProviderLayerCollectionId {
1✔
2128
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2129
                            collection_id: collection1_id.clone(),
1✔
2130
                        },
1✔
2131
                        name: "Collection1".to_string(),
1✔
2132
                        description: "Collection 1".to_string(),
1✔
2133
                        properties: Default::default(),
1✔
2134
                    }),
1✔
2135
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2136
                        id: ProviderLayerCollectionId {
1✔
2137
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2138
                            collection_id: collection2_id.clone(),
1✔
2139
                        },
1✔
2140
                        name: "Collection2".to_string(),
1✔
2141
                        description: "Collection 2".to_string(),
1✔
2142
                        properties: Default::default(),
1✔
2143
                    }),
1✔
2144
                ],
1✔
2145
                entry_label: None,
1✔
2146
                properties: vec![],
1✔
2147
            }
1✔
2148
        );
1✔
2149

2150
        let collection1_all = layer_db
1✔
2151
            .search(
1✔
2152
                &collection1_id,
1✔
2153
                SearchParameters {
1✔
2154
                    search_type: SearchType::Fulltext,
1✔
2155
                    search_string: String::new(),
1✔
2156
                    limit: 10,
1✔
2157
                    offset: 0,
1✔
2158
                },
1✔
2159
            )
1✔
2160
            .await
1✔
2161
            .unwrap();
1✔
2162

1✔
2163
        assert_eq!(
1✔
2164
            collection1_all,
1✔
2165
            LayerCollection {
1✔
2166
                id: ProviderLayerCollectionId {
1✔
2167
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2168
                    collection_id: collection1_id.clone(),
1✔
2169
                },
1✔
2170
                name: "Collection1".to_string(),
1✔
2171
                description: "Collection 1".to_string(),
1✔
2172
                items: vec![
1✔
2173
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2174
                        id: ProviderLayerCollectionId {
1✔
2175
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2176
                            collection_id: collection2_id.clone(),
1✔
2177
                        },
1✔
2178
                        name: "Collection2".to_string(),
1✔
2179
                        description: "Collection 2".to_string(),
1✔
2180
                        properties: Default::default(),
1✔
2181
                    }),
1✔
2182
                    CollectionItem::Layer(LayerListing {
1✔
2183
                        id: ProviderLayerId {
1✔
2184
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2185
                            layer_id: layer2.clone(),
1✔
2186
                        },
1✔
2187
                        name: "Layer2".to_string(),
1✔
2188
                        description: "Layer 2".to_string(),
1✔
2189
                        properties: vec![],
1✔
2190
                    }),
1✔
2191
                ],
1✔
2192
                entry_label: None,
1✔
2193
                properties: vec![],
1✔
2194
            }
1✔
2195
        );
1✔
2196

2197
        let collection1_filtered_fulltext = layer_db
1✔
2198
            .search(
1✔
2199
                &collection1_id,
1✔
2200
                SearchParameters {
1✔
2201
                    search_type: SearchType::Fulltext,
1✔
2202
                    search_string: "ay".to_string(),
1✔
2203
                    limit: 10,
1✔
2204
                    offset: 0,
1✔
2205
                },
1✔
2206
            )
1✔
2207
            .await
1✔
2208
            .unwrap();
1✔
2209

1✔
2210
        assert_eq!(
1✔
2211
            collection1_filtered_fulltext,
1✔
2212
            LayerCollection {
1✔
2213
                id: ProviderLayerCollectionId {
1✔
2214
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2215
                    collection_id: collection1_id.clone(),
1✔
2216
                },
1✔
2217
                name: "Collection1".to_string(),
1✔
2218
                description: "Collection 1".to_string(),
1✔
2219
                items: vec![CollectionItem::Layer(LayerListing {
1✔
2220
                    id: ProviderLayerId {
1✔
2221
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
2222
                        layer_id: layer2.clone(),
1✔
2223
                    },
1✔
2224
                    name: "Layer2".to_string(),
1✔
2225
                    description: "Layer 2".to_string(),
1✔
2226
                    properties: vec![],
1✔
2227
                }),],
1✔
2228
                entry_label: None,
1✔
2229
                properties: vec![],
1✔
2230
            }
1✔
2231
        );
1✔
2232

2233
        let collection1_filtered_prefix = layer_db
1✔
2234
            .search(
1✔
2235
                &collection1_id,
1✔
2236
                SearchParameters {
1✔
2237
                    search_type: SearchType::Prefix,
1✔
2238
                    search_string: "ay".to_string(),
1✔
2239
                    limit: 10,
1✔
2240
                    offset: 0,
1✔
2241
                },
1✔
2242
            )
1✔
2243
            .await
1✔
2244
            .unwrap();
1✔
2245

1✔
2246
        assert_eq!(
1✔
2247
            collection1_filtered_prefix,
1✔
2248
            LayerCollection {
1✔
2249
                id: ProviderLayerCollectionId {
1✔
2250
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2251
                    collection_id: collection1_id.clone(),
1✔
2252
                },
1✔
2253
                name: "Collection1".to_string(),
1✔
2254
                description: "Collection 1".to_string(),
1✔
2255
                items: vec![],
1✔
2256
                entry_label: None,
1✔
2257
                properties: vec![],
1✔
2258
            }
1✔
2259
        );
1✔
2260

2261
        let collection1_filtered_prefix2 = layer_db
1✔
2262
            .search(
1✔
2263
                &collection1_id,
1✔
2264
                SearchParameters {
1✔
2265
                    search_type: SearchType::Prefix,
1✔
2266
                    search_string: "Lay".to_string(),
1✔
2267
                    limit: 10,
1✔
2268
                    offset: 0,
1✔
2269
                },
1✔
2270
            )
1✔
2271
            .await
1✔
2272
            .unwrap();
1✔
2273

1✔
2274
        assert_eq!(
1✔
2275
            collection1_filtered_prefix2,
1✔
2276
            LayerCollection {
1✔
2277
                id: ProviderLayerCollectionId {
1✔
2278
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2279
                    collection_id: collection1_id.clone(),
1✔
2280
                },
1✔
2281
                name: "Collection1".to_string(),
1✔
2282
                description: "Collection 1".to_string(),
1✔
2283
                items: vec![CollectionItem::Layer(LayerListing {
1✔
2284
                    id: ProviderLayerId {
1✔
2285
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
2286
                        layer_id: layer2.clone(),
1✔
2287
                    },
1✔
2288
                    name: "Layer2".to_string(),
1✔
2289
                    description: "Layer 2".to_string(),
1✔
2290
                    properties: vec![],
1✔
2291
                }),],
1✔
2292
                entry_label: None,
1✔
2293
                properties: vec![],
1✔
2294
            }
1✔
2295
        );
1✔
2296
    }
1✔
2297

2298
    #[allow(clippy::too_many_lines)]
2299
    #[ge_context::test]
3✔
2300
    async fn it_searches_layers_with_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
2301
        let admin_session = admin_login(&app_ctx).await;
1✔
2302
        let admin_layer_db = app_ctx.session_context(admin_session).db();
1✔
2303

2304
        let user_session = app_ctx.create_anonymous_session().await.unwrap();
1✔
2305
        let user_layer_db = app_ctx.session_context(user_session.clone()).db();
1✔
2306

1✔
2307
        let workflow = Workflow {
1✔
2308
            operator: TypedOperator::Vector(
1✔
2309
                MockPointSource {
1✔
2310
                    params: MockPointSourceParams {
1✔
2311
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2312
                    },
1✔
2313
                }
1✔
2314
                .boxed(),
1✔
2315
            ),
1✔
2316
        };
1✔
2317

2318
        let root_collection_id = admin_layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2319

2320
        let layer1 = admin_layer_db
1✔
2321
            .add_layer(
1✔
2322
                AddLayer {
1✔
2323
                    name: "Layer1".to_string(),
1✔
2324
                    description: "Layer 1".to_string(),
1✔
2325
                    symbology: None,
1✔
2326
                    workflow: workflow.clone(),
1✔
2327
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
2328
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2329
                },
1✔
2330
                &root_collection_id,
1✔
2331
            )
1✔
2332
            .await
1✔
2333
            .unwrap();
1✔
2334

2335
        let collection1_id = admin_layer_db
1✔
2336
            .add_layer_collection(
1✔
2337
                AddLayerCollection {
1✔
2338
                    name: "Collection1".to_string(),
1✔
2339
                    description: "Collection 1".to_string(),
1✔
2340
                    properties: Default::default(),
1✔
2341
                },
1✔
2342
                &root_collection_id,
1✔
2343
            )
1✔
2344
            .await
1✔
2345
            .unwrap();
1✔
2346

2347
        let layer2 = admin_layer_db
1✔
2348
            .add_layer(
1✔
2349
                AddLayer {
1✔
2350
                    name: "Layer2".to_string(),
1✔
2351
                    description: "Layer 2".to_string(),
1✔
2352
                    symbology: None,
1✔
2353
                    workflow: workflow.clone(),
1✔
2354
                    metadata: Default::default(),
1✔
2355
                    properties: Default::default(),
1✔
2356
                },
1✔
2357
                &collection1_id,
1✔
2358
            )
1✔
2359
            .await
1✔
2360
            .unwrap();
1✔
2361

2362
        let collection2_id = admin_layer_db
1✔
2363
            .add_layer_collection(
1✔
2364
                AddLayerCollection {
1✔
2365
                    name: "Collection2".to_string(),
1✔
2366
                    description: "Collection 2".to_string(),
1✔
2367
                    properties: Default::default(),
1✔
2368
                },
1✔
2369
                &collection1_id,
1✔
2370
            )
1✔
2371
            .await
1✔
2372
            .unwrap();
1✔
2373

2374
        let collection3_id = admin_layer_db
1✔
2375
            .add_layer_collection(
1✔
2376
                AddLayerCollection {
1✔
2377
                    name: "Collection3".to_string(),
1✔
2378
                    description: "Collection 3".to_string(),
1✔
2379
                    properties: Default::default(),
1✔
2380
                },
1✔
2381
                &collection1_id,
1✔
2382
            )
1✔
2383
            .await
1✔
2384
            .unwrap();
1✔
2385

2386
        let layer3 = admin_layer_db
1✔
2387
            .add_layer(
1✔
2388
                AddLayer {
1✔
2389
                    name: "Layer3".to_string(),
1✔
2390
                    description: "Layer 3".to_string(),
1✔
2391
                    symbology: None,
1✔
2392
                    workflow: workflow.clone(),
1✔
2393
                    metadata: Default::default(),
1✔
2394
                    properties: Default::default(),
1✔
2395
                },
1✔
2396
                &collection2_id,
1✔
2397
            )
1✔
2398
            .await
1✔
2399
            .unwrap();
1✔
2400

1✔
2401
        // Grant user permissions for collection1, collection2, layer1 and layer2
1✔
2402
        admin_layer_db
1✔
2403
            .add_permission(
1✔
2404
                user_session.user.id.into(),
1✔
2405
                collection1_id.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
                collection2_id.clone(),
1✔
2415
                Permission::Read,
1✔
2416
            )
1✔
2417
            .await
1✔
2418
            .unwrap();
1✔
2419

1✔
2420
        admin_layer_db
1✔
2421
            .add_permission(
1✔
2422
                user_session.user.id.into(),
1✔
2423
                layer1.clone(),
1✔
2424
                Permission::Read,
1✔
2425
            )
1✔
2426
            .await
1✔
2427
            .unwrap();
1✔
2428

1✔
2429
        admin_layer_db
1✔
2430
            .add_permission(
1✔
2431
                user_session.user.id.into(),
1✔
2432
                layer2.clone(),
1✔
2433
                Permission::Read,
1✔
2434
            )
1✔
2435
            .await
1✔
2436
            .unwrap();
1✔
2437

2438
        // Ensure admin sees everything we added
2439
        let admin_root_collection_all = admin_layer_db
1✔
2440
            .search(
1✔
2441
                &root_collection_id,
1✔
2442
                SearchParameters {
1✔
2443
                    search_type: SearchType::Fulltext,
1✔
2444
                    search_string: String::new(),
1✔
2445
                    limit: 10,
1✔
2446
                    offset: 0,
1✔
2447
                },
1✔
2448
            )
1✔
2449
            .await
1✔
2450
            .unwrap();
1✔
2451

1✔
2452
        assert_eq!(
1✔
2453
            admin_root_collection_all,
1✔
2454
            LayerCollection {
1✔
2455
                id: ProviderLayerCollectionId {
1✔
2456
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2457
                    collection_id: root_collection_id.clone(),
1✔
2458
                },
1✔
2459
                name: "Layers".to_string(),
1✔
2460
                description: "All available Geo Engine layers".to_string(),
1✔
2461
                items: vec![
1✔
2462
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2463
                        id: ProviderLayerCollectionId {
1✔
2464
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2465
                            collection_id: collection1_id.clone(),
1✔
2466
                        },
1✔
2467
                        name: "Collection1".to_string(),
1✔
2468
                        description: "Collection 1".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: collection2_id.clone(),
1✔
2475
                        },
1✔
2476
                        name: "Collection2".to_string(),
1✔
2477
                        description: "Collection 2".to_string(),
1✔
2478
                        properties: Default::default(),
1✔
2479
                    }),
1✔
2480
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2481
                        id: ProviderLayerCollectionId {
1✔
2482
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2483
                            collection_id: collection3_id.clone(),
1✔
2484
                        },
1✔
2485
                        name: "Collection3".to_string(),
1✔
2486
                        description: "Collection 3".to_string(),
1✔
2487
                        properties: Default::default(),
1✔
2488
                    }),
1✔
2489
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2490
                        id: ProviderLayerCollectionId {
1✔
2491
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2492
                            collection_id: LayerCollectionId(
1✔
2493
                                "ffb2dd9e-f5ad-427c-b7f1-c9a0c7a0ae3f".to_string()
1✔
2494
                            ),
1✔
2495
                        },
1✔
2496
                        name: "Unsorted".to_string(),
1✔
2497
                        description: "Unsorted Layers".to_string(),
1✔
2498
                        properties: Default::default(),
1✔
2499
                    }),
1✔
2500
                    CollectionItem::Layer(LayerListing {
1✔
2501
                        id: ProviderLayerId {
1✔
2502
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2503
                            layer_id: layer1.clone(),
1✔
2504
                        },
1✔
2505
                        name: "Layer1".to_string(),
1✔
2506
                        description: "Layer 1".to_string(),
1✔
2507
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2508
                    }),
1✔
2509
                    CollectionItem::Layer(LayerListing {
1✔
2510
                        id: ProviderLayerId {
1✔
2511
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2512
                            layer_id: layer2.clone(),
1✔
2513
                        },
1✔
2514
                        name: "Layer2".to_string(),
1✔
2515
                        description: "Layer 2".to_string(),
1✔
2516
                        properties: vec![],
1✔
2517
                    }),
1✔
2518
                    CollectionItem::Layer(LayerListing {
1✔
2519
                        id: ProviderLayerId {
1✔
2520
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2521
                            layer_id: layer3.clone(),
1✔
2522
                        },
1✔
2523
                        name: "Layer3".to_string(),
1✔
2524
                        description: "Layer 3".to_string(),
1✔
2525
                        properties: vec![],
1✔
2526
                    }),
1✔
2527
                ],
1✔
2528
                entry_label: None,
1✔
2529
                properties: vec![],
1✔
2530
            }
1✔
2531
        );
1✔
2532

2533
        let root_collection_all = user_layer_db
1✔
2534
            .search(
1✔
2535
                &root_collection_id,
1✔
2536
                SearchParameters {
1✔
2537
                    search_type: SearchType::Fulltext,
1✔
2538
                    search_string: String::new(),
1✔
2539
                    limit: 10,
1✔
2540
                    offset: 0,
1✔
2541
                },
1✔
2542
            )
1✔
2543
            .await
1✔
2544
            .unwrap();
1✔
2545

1✔
2546
        assert_eq!(
1✔
2547
            root_collection_all,
1✔
2548
            LayerCollection {
1✔
2549
                id: ProviderLayerCollectionId {
1✔
2550
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2551
                    collection_id: root_collection_id.clone(),
1✔
2552
                },
1✔
2553
                name: "Layers".to_string(),
1✔
2554
                description: "All available Geo Engine layers".to_string(),
1✔
2555
                items: vec![
1✔
2556
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2557
                        id: ProviderLayerCollectionId {
1✔
2558
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2559
                            collection_id: collection1_id.clone(),
1✔
2560
                        },
1✔
2561
                        name: "Collection1".to_string(),
1✔
2562
                        description: "Collection 1".to_string(),
1✔
2563
                        properties: Default::default(),
1✔
2564
                    }),
1✔
2565
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2566
                        id: ProviderLayerCollectionId {
1✔
2567
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2568
                            collection_id: collection2_id.clone(),
1✔
2569
                        },
1✔
2570
                        name: "Collection2".to_string(),
1✔
2571
                        description: "Collection 2".to_string(),
1✔
2572
                        properties: Default::default(),
1✔
2573
                    }),
1✔
2574
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2575
                        id: ProviderLayerCollectionId {
1✔
2576
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2577
                            collection_id: LayerCollectionId(
1✔
2578
                                "ffb2dd9e-f5ad-427c-b7f1-c9a0c7a0ae3f".to_string()
1✔
2579
                            ),
1✔
2580
                        },
1✔
2581
                        name: "Unsorted".to_string(),
1✔
2582
                        description: "Unsorted Layers".to_string(),
1✔
2583
                        properties: Default::default(),
1✔
2584
                    }),
1✔
2585
                    CollectionItem::Layer(LayerListing {
1✔
2586
                        id: ProviderLayerId {
1✔
2587
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2588
                            layer_id: layer1.clone(),
1✔
2589
                        },
1✔
2590
                        name: "Layer1".to_string(),
1✔
2591
                        description: "Layer 1".to_string(),
1✔
2592
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2593
                    }),
1✔
2594
                    CollectionItem::Layer(LayerListing {
1✔
2595
                        id: ProviderLayerId {
1✔
2596
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2597
                            layer_id: layer2.clone(),
1✔
2598
                        },
1✔
2599
                        name: "Layer2".to_string(),
1✔
2600
                        description: "Layer 2".to_string(),
1✔
2601
                        properties: vec![],
1✔
2602
                    }),
1✔
2603
                ],
1✔
2604
                entry_label: None,
1✔
2605
                properties: vec![],
1✔
2606
            }
1✔
2607
        );
1✔
2608

2609
        let root_collection_filtered = user_layer_db
1✔
2610
            .search(
1✔
2611
                &root_collection_id,
1✔
2612
                SearchParameters {
1✔
2613
                    search_type: SearchType::Fulltext,
1✔
2614
                    search_string: "lection".to_string(),
1✔
2615
                    limit: 10,
1✔
2616
                    offset: 0,
1✔
2617
                },
1✔
2618
            )
1✔
2619
            .await
1✔
2620
            .unwrap();
1✔
2621

1✔
2622
        assert_eq!(
1✔
2623
            root_collection_filtered,
1✔
2624
            LayerCollection {
1✔
2625
                id: ProviderLayerCollectionId {
1✔
2626
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2627
                    collection_id: root_collection_id.clone(),
1✔
2628
                },
1✔
2629
                name: "Layers".to_string(),
1✔
2630
                description: "All available Geo Engine layers".to_string(),
1✔
2631
                items: vec![
1✔
2632
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2633
                        id: ProviderLayerCollectionId {
1✔
2634
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2635
                            collection_id: collection1_id.clone(),
1✔
2636
                        },
1✔
2637
                        name: "Collection1".to_string(),
1✔
2638
                        description: "Collection 1".to_string(),
1✔
2639
                        properties: Default::default(),
1✔
2640
                    }),
1✔
2641
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2642
                        id: ProviderLayerCollectionId {
1✔
2643
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2644
                            collection_id: collection2_id.clone(),
1✔
2645
                        },
1✔
2646
                        name: "Collection2".to_string(),
1✔
2647
                        description: "Collection 2".to_string(),
1✔
2648
                        properties: Default::default(),
1✔
2649
                    }),
1✔
2650
                ],
1✔
2651
                entry_label: None,
1✔
2652
                properties: vec![],
1✔
2653
            }
1✔
2654
        );
1✔
2655

2656
        let collection1_all = user_layer_db
1✔
2657
            .search(
1✔
2658
                &collection1_id,
1✔
2659
                SearchParameters {
1✔
2660
                    search_type: SearchType::Fulltext,
1✔
2661
                    search_string: String::new(),
1✔
2662
                    limit: 10,
1✔
2663
                    offset: 0,
1✔
2664
                },
1✔
2665
            )
1✔
2666
            .await
1✔
2667
            .unwrap();
1✔
2668

1✔
2669
        assert_eq!(
1✔
2670
            collection1_all,
1✔
2671
            LayerCollection {
1✔
2672
                id: ProviderLayerCollectionId {
1✔
2673
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2674
                    collection_id: collection1_id.clone(),
1✔
2675
                },
1✔
2676
                name: "Collection1".to_string(),
1✔
2677
                description: "Collection 1".to_string(),
1✔
2678
                items: vec![
1✔
2679
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2680
                        id: ProviderLayerCollectionId {
1✔
2681
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2682
                            collection_id: collection2_id.clone(),
1✔
2683
                        },
1✔
2684
                        name: "Collection2".to_string(),
1✔
2685
                        description: "Collection 2".to_string(),
1✔
2686
                        properties: Default::default(),
1✔
2687
                    }),
1✔
2688
                    CollectionItem::Layer(LayerListing {
1✔
2689
                        id: ProviderLayerId {
1✔
2690
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2691
                            layer_id: layer2.clone(),
1✔
2692
                        },
1✔
2693
                        name: "Layer2".to_string(),
1✔
2694
                        description: "Layer 2".to_string(),
1✔
2695
                        properties: vec![],
1✔
2696
                    }),
1✔
2697
                ],
1✔
2698
                entry_label: None,
1✔
2699
                properties: vec![],
1✔
2700
            }
1✔
2701
        );
1✔
2702

2703
        let collection1_filtered_fulltext = user_layer_db
1✔
2704
            .search(
1✔
2705
                &collection1_id,
1✔
2706
                SearchParameters {
1✔
2707
                    search_type: SearchType::Fulltext,
1✔
2708
                    search_string: "ay".to_string(),
1✔
2709
                    limit: 10,
1✔
2710
                    offset: 0,
1✔
2711
                },
1✔
2712
            )
1✔
2713
            .await
1✔
2714
            .unwrap();
1✔
2715

1✔
2716
        assert_eq!(
1✔
2717
            collection1_filtered_fulltext,
1✔
2718
            LayerCollection {
1✔
2719
                id: ProviderLayerCollectionId {
1✔
2720
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2721
                    collection_id: collection1_id.clone(),
1✔
2722
                },
1✔
2723
                name: "Collection1".to_string(),
1✔
2724
                description: "Collection 1".to_string(),
1✔
2725
                items: vec![CollectionItem::Layer(LayerListing {
1✔
2726
                    id: ProviderLayerId {
1✔
2727
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
2728
                        layer_id: layer2.clone(),
1✔
2729
                    },
1✔
2730
                    name: "Layer2".to_string(),
1✔
2731
                    description: "Layer 2".to_string(),
1✔
2732
                    properties: vec![],
1✔
2733
                }),],
1✔
2734
                entry_label: None,
1✔
2735
                properties: vec![],
1✔
2736
            }
1✔
2737
        );
1✔
2738

2739
        let collection1_filtered_prefix = user_layer_db
1✔
2740
            .search(
1✔
2741
                &collection1_id,
1✔
2742
                SearchParameters {
1✔
2743
                    search_type: SearchType::Prefix,
1✔
2744
                    search_string: "ay".to_string(),
1✔
2745
                    limit: 10,
1✔
2746
                    offset: 0,
1✔
2747
                },
1✔
2748
            )
1✔
2749
            .await
1✔
2750
            .unwrap();
1✔
2751

1✔
2752
        assert_eq!(
1✔
2753
            collection1_filtered_prefix,
1✔
2754
            LayerCollection {
1✔
2755
                id: ProviderLayerCollectionId {
1✔
2756
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2757
                    collection_id: collection1_id.clone(),
1✔
2758
                },
1✔
2759
                name: "Collection1".to_string(),
1✔
2760
                description: "Collection 1".to_string(),
1✔
2761
                items: vec![],
1✔
2762
                entry_label: None,
1✔
2763
                properties: vec![],
1✔
2764
            }
1✔
2765
        );
1✔
2766

2767
        let collection1_filtered_prefix2 = user_layer_db
1✔
2768
            .search(
1✔
2769
                &collection1_id,
1✔
2770
                SearchParameters {
1✔
2771
                    search_type: SearchType::Prefix,
1✔
2772
                    search_string: "Lay".to_string(),
1✔
2773
                    limit: 10,
1✔
2774
                    offset: 0,
1✔
2775
                },
1✔
2776
            )
1✔
2777
            .await
1✔
2778
            .unwrap();
1✔
2779

1✔
2780
        assert_eq!(
1✔
2781
            collection1_filtered_prefix2,
1✔
2782
            LayerCollection {
1✔
2783
                id: ProviderLayerCollectionId {
1✔
2784
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2785
                    collection_id: collection1_id.clone(),
1✔
2786
                },
1✔
2787
                name: "Collection1".to_string(),
1✔
2788
                description: "Collection 1".to_string(),
1✔
2789
                items: vec![CollectionItem::Layer(LayerListing {
1✔
2790
                    id: ProviderLayerId {
1✔
2791
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
2792
                        layer_id: layer2.clone(),
1✔
2793
                    },
1✔
2794
                    name: "Layer2".to_string(),
1✔
2795
                    description: "Layer 2".to_string(),
1✔
2796
                    properties: vec![],
1✔
2797
                }),],
1✔
2798
                entry_label: None,
1✔
2799
                properties: vec![],
1✔
2800
            }
1✔
2801
        );
1✔
2802
    }
1✔
2803

2804
    #[allow(clippy::too_many_lines)]
2805
    #[ge_context::test]
3✔
2806
    async fn it_autocompletes_layers(app_ctx: PostgresContext<NoTls>) {
1✔
2807
        let session = admin_login(&app_ctx).await;
1✔
2808

2809
        let layer_db = app_ctx.session_context(session).db();
1✔
2810

1✔
2811
        let workflow = Workflow {
1✔
2812
            operator: TypedOperator::Vector(
1✔
2813
                MockPointSource {
1✔
2814
                    params: MockPointSourceParams {
1✔
2815
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2816
                    },
1✔
2817
                }
1✔
2818
                .boxed(),
1✔
2819
            ),
1✔
2820
        };
1✔
2821

2822
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2823

2824
        let _layer1 = layer_db
1✔
2825
            .add_layer(
1✔
2826
                AddLayer {
1✔
2827
                    name: "Layer1".to_string(),
1✔
2828
                    description: "Layer 1".to_string(),
1✔
2829
                    symbology: None,
1✔
2830
                    workflow: workflow.clone(),
1✔
2831
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
2832
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2833
                },
1✔
2834
                &root_collection_id,
1✔
2835
            )
1✔
2836
            .await
1✔
2837
            .unwrap();
1✔
2838

2839
        let collection1_id = layer_db
1✔
2840
            .add_layer_collection(
1✔
2841
                AddLayerCollection {
1✔
2842
                    name: "Collection1".to_string(),
1✔
2843
                    description: "Collection 1".to_string(),
1✔
2844
                    properties: Default::default(),
1✔
2845
                },
1✔
2846
                &root_collection_id,
1✔
2847
            )
1✔
2848
            .await
1✔
2849
            .unwrap();
1✔
2850

2851
        let _layer2 = layer_db
1✔
2852
            .add_layer(
1✔
2853
                AddLayer {
1✔
2854
                    name: "Layer2".to_string(),
1✔
2855
                    description: "Layer 2".to_string(),
1✔
2856
                    symbology: None,
1✔
2857
                    workflow: workflow.clone(),
1✔
2858
                    metadata: Default::default(),
1✔
2859
                    properties: Default::default(),
1✔
2860
                },
1✔
2861
                &collection1_id,
1✔
2862
            )
1✔
2863
            .await
1✔
2864
            .unwrap();
1✔
2865

2866
        let _collection2_id = layer_db
1✔
2867
            .add_layer_collection(
1✔
2868
                AddLayerCollection {
1✔
2869
                    name: "Collection2".to_string(),
1✔
2870
                    description: "Collection 2".to_string(),
1✔
2871
                    properties: Default::default(),
1✔
2872
                },
1✔
2873
                &collection1_id,
1✔
2874
            )
1✔
2875
            .await
1✔
2876
            .unwrap();
1✔
2877

2878
        let root_collection_all = layer_db
1✔
2879
            .autocomplete_search(
1✔
2880
                &root_collection_id,
1✔
2881
                SearchParameters {
1✔
2882
                    search_type: SearchType::Fulltext,
1✔
2883
                    search_string: String::new(),
1✔
2884
                    limit: 10,
1✔
2885
                    offset: 0,
1✔
2886
                },
1✔
2887
            )
1✔
2888
            .await
1✔
2889
            .unwrap();
1✔
2890

1✔
2891
        assert_eq!(
1✔
2892
            root_collection_all,
1✔
2893
            vec![
1✔
2894
                "Collection1".to_string(),
1✔
2895
                "Collection2".to_string(),
1✔
2896
                "Layer1".to_string(),
1✔
2897
                "Layer2".to_string(),
1✔
2898
                "Unsorted".to_string(),
1✔
2899
            ]
1✔
2900
        );
1✔
2901

2902
        let root_collection_filtered = layer_db
1✔
2903
            .autocomplete_search(
1✔
2904
                &root_collection_id,
1✔
2905
                SearchParameters {
1✔
2906
                    search_type: SearchType::Fulltext,
1✔
2907
                    search_string: "lection".to_string(),
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
            root_collection_filtered,
1✔
2917
            vec!["Collection1".to_string(), "Collection2".to_string(),]
1✔
2918
        );
1✔
2919

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

1✔
2933
        assert_eq!(
1✔
2934
            collection1_all,
1✔
2935
            vec!["Collection2".to_string(), "Layer2".to_string(),]
1✔
2936
        );
1✔
2937

2938
        let collection1_filtered_fulltext = layer_db
1✔
2939
            .autocomplete_search(
1✔
2940
                &collection1_id,
1✔
2941
                SearchParameters {
1✔
2942
                    search_type: SearchType::Fulltext,
1✔
2943
                    search_string: "ay".to_string(),
1✔
2944
                    limit: 10,
1✔
2945
                    offset: 0,
1✔
2946
                },
1✔
2947
            )
1✔
2948
            .await
1✔
2949
            .unwrap();
1✔
2950

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

2953
        let collection1_filtered_prefix = layer_db
1✔
2954
            .autocomplete_search(
1✔
2955
                &collection1_id,
1✔
2956
                SearchParameters {
1✔
2957
                    search_type: SearchType::Prefix,
1✔
2958
                    search_string: "ay".to_string(),
1✔
2959
                    limit: 10,
1✔
2960
                    offset: 0,
1✔
2961
                },
1✔
2962
            )
1✔
2963
            .await
1✔
2964
            .unwrap();
1✔
2965

1✔
2966
        assert_eq!(collection1_filtered_prefix, Vec::<String>::new());
1✔
2967

2968
        let collection1_filtered_prefix2 = layer_db
1✔
2969
            .autocomplete_search(
1✔
2970
                &collection1_id,
1✔
2971
                SearchParameters {
1✔
2972
                    search_type: SearchType::Prefix,
1✔
2973
                    search_string: "Lay".to_string(),
1✔
2974
                    limit: 10,
1✔
2975
                    offset: 0,
1✔
2976
                },
1✔
2977
            )
1✔
2978
            .await
1✔
2979
            .unwrap();
1✔
2980

1✔
2981
        assert_eq!(collection1_filtered_prefix2, vec!["Layer2".to_string(),]);
1✔
2982
    }
1✔
2983

2984
    #[allow(clippy::too_many_lines)]
2985
    #[ge_context::test]
3✔
2986
    async fn it_autocompletes_layers_with_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
2987
        let admin_session = admin_login(&app_ctx).await;
1✔
2988
        let admin_layer_db = app_ctx.session_context(admin_session).db();
1✔
2989

2990
        let user_session = app_ctx.create_anonymous_session().await.unwrap();
1✔
2991
        let user_layer_db = app_ctx.session_context(user_session.clone()).db();
1✔
2992

1✔
2993
        let workflow = Workflow {
1✔
2994
            operator: TypedOperator::Vector(
1✔
2995
                MockPointSource {
1✔
2996
                    params: MockPointSourceParams {
1✔
2997
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2998
                    },
1✔
2999
                }
1✔
3000
                .boxed(),
1✔
3001
            ),
1✔
3002
        };
1✔
3003

3004
        let root_collection_id = admin_layer_db.get_root_layer_collection_id().await.unwrap();
1✔
3005

3006
        let layer1 = admin_layer_db
1✔
3007
            .add_layer(
1✔
3008
                AddLayer {
1✔
3009
                    name: "Layer1".to_string(),
1✔
3010
                    description: "Layer 1".to_string(),
1✔
3011
                    symbology: None,
1✔
3012
                    workflow: workflow.clone(),
1✔
3013
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
3014
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
3015
                },
1✔
3016
                &root_collection_id,
1✔
3017
            )
1✔
3018
            .await
1✔
3019
            .unwrap();
1✔
3020

3021
        let collection1_id = admin_layer_db
1✔
3022
            .add_layer_collection(
1✔
3023
                AddLayerCollection {
1✔
3024
                    name: "Collection1".to_string(),
1✔
3025
                    description: "Collection 1".to_string(),
1✔
3026
                    properties: Default::default(),
1✔
3027
                },
1✔
3028
                &root_collection_id,
1✔
3029
            )
1✔
3030
            .await
1✔
3031
            .unwrap();
1✔
3032

3033
        let layer2 = admin_layer_db
1✔
3034
            .add_layer(
1✔
3035
                AddLayer {
1✔
3036
                    name: "Layer2".to_string(),
1✔
3037
                    description: "Layer 2".to_string(),
1✔
3038
                    symbology: None,
1✔
3039
                    workflow: workflow.clone(),
1✔
3040
                    metadata: Default::default(),
1✔
3041
                    properties: Default::default(),
1✔
3042
                },
1✔
3043
                &collection1_id,
1✔
3044
            )
1✔
3045
            .await
1✔
3046
            .unwrap();
1✔
3047

3048
        let collection2_id = admin_layer_db
1✔
3049
            .add_layer_collection(
1✔
3050
                AddLayerCollection {
1✔
3051
                    name: "Collection2".to_string(),
1✔
3052
                    description: "Collection 2".to_string(),
1✔
3053
                    properties: Default::default(),
1✔
3054
                },
1✔
3055
                &collection1_id,
1✔
3056
            )
1✔
3057
            .await
1✔
3058
            .unwrap();
1✔
3059

3060
        let _collection3_id = admin_layer_db
1✔
3061
            .add_layer_collection(
1✔
3062
                AddLayerCollection {
1✔
3063
                    name: "Collection3".to_string(),
1✔
3064
                    description: "Collection 3".to_string(),
1✔
3065
                    properties: Default::default(),
1✔
3066
                },
1✔
3067
                &collection1_id,
1✔
3068
            )
1✔
3069
            .await
1✔
3070
            .unwrap();
1✔
3071

3072
        let _layer3 = admin_layer_db
1✔
3073
            .add_layer(
1✔
3074
                AddLayer {
1✔
3075
                    name: "Layer3".to_string(),
1✔
3076
                    description: "Layer 3".to_string(),
1✔
3077
                    symbology: None,
1✔
3078
                    workflow: workflow.clone(),
1✔
3079
                    metadata: Default::default(),
1✔
3080
                    properties: Default::default(),
1✔
3081
                },
1✔
3082
                &collection2_id,
1✔
3083
            )
1✔
3084
            .await
1✔
3085
            .unwrap();
1✔
3086

1✔
3087
        // Grant user permissions for collection1, collection2, layer1 and layer2
1✔
3088
        admin_layer_db
1✔
3089
            .add_permission(
1✔
3090
                user_session.user.id.into(),
1✔
3091
                collection1_id.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
                collection2_id.clone(),
1✔
3101
                Permission::Read,
1✔
3102
            )
1✔
3103
            .await
1✔
3104
            .unwrap();
1✔
3105

1✔
3106
        admin_layer_db
1✔
3107
            .add_permission(
1✔
3108
                user_session.user.id.into(),
1✔
3109
                layer1.clone(),
1✔
3110
                Permission::Read,
1✔
3111
            )
1✔
3112
            .await
1✔
3113
            .unwrap();
1✔
3114

1✔
3115
        admin_layer_db
1✔
3116
            .add_permission(
1✔
3117
                user_session.user.id.into(),
1✔
3118
                layer2.clone(),
1✔
3119
                Permission::Read,
1✔
3120
            )
1✔
3121
            .await
1✔
3122
            .unwrap();
1✔
3123

3124
        // Ensure admin sees everything we added
3125
        let admin_root_collection_all = admin_layer_db
1✔
3126
            .autocomplete_search(
1✔
3127
                &root_collection_id,
1✔
3128
                SearchParameters {
1✔
3129
                    search_type: SearchType::Fulltext,
1✔
3130
                    search_string: String::new(),
1✔
3131
                    limit: 10,
1✔
3132
                    offset: 0,
1✔
3133
                },
1✔
3134
            )
1✔
3135
            .await
1✔
3136
            .unwrap();
1✔
3137

1✔
3138
        assert_eq!(
1✔
3139
            admin_root_collection_all,
1✔
3140
            vec![
1✔
3141
                "Collection1".to_string(),
1✔
3142
                "Collection2".to_string(),
1✔
3143
                "Collection3".to_string(),
1✔
3144
                "Layer1".to_string(),
1✔
3145
                "Layer2".to_string(),
1✔
3146
                "Layer3".to_string(),
1✔
3147
                "Unsorted".to_string(),
1✔
3148
            ]
1✔
3149
        );
1✔
3150

3151
        let root_collection_all = user_layer_db
1✔
3152
            .autocomplete_search(
1✔
3153
                &root_collection_id,
1✔
3154
                SearchParameters {
1✔
3155
                    search_type: SearchType::Fulltext,
1✔
3156
                    search_string: String::new(),
1✔
3157
                    limit: 10,
1✔
3158
                    offset: 0,
1✔
3159
                },
1✔
3160
            )
1✔
3161
            .await
1✔
3162
            .unwrap();
1✔
3163

1✔
3164
        assert_eq!(
1✔
3165
            root_collection_all,
1✔
3166
            vec![
1✔
3167
                "Collection1".to_string(),
1✔
3168
                "Collection2".to_string(),
1✔
3169
                "Layer1".to_string(),
1✔
3170
                "Layer2".to_string(),
1✔
3171
                "Unsorted".to_string(),
1✔
3172
            ]
1✔
3173
        );
1✔
3174

3175
        let root_collection_filtered = user_layer_db
1✔
3176
            .autocomplete_search(
1✔
3177
                &root_collection_id,
1✔
3178
                SearchParameters {
1✔
3179
                    search_type: SearchType::Fulltext,
1✔
3180
                    search_string: "lection".to_string(),
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
            root_collection_filtered,
1✔
3190
            vec!["Collection1".to_string(), "Collection2".to_string(),]
1✔
3191
        );
1✔
3192

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

1✔
3206
        assert_eq!(
1✔
3207
            collection1_all,
1✔
3208
            vec!["Collection2".to_string(), "Layer2".to_string(),]
1✔
3209
        );
1✔
3210

3211
        let collection1_filtered_fulltext = user_layer_db
1✔
3212
            .autocomplete_search(
1✔
3213
                &collection1_id,
1✔
3214
                SearchParameters {
1✔
3215
                    search_type: SearchType::Fulltext,
1✔
3216
                    search_string: "ay".to_string(),
1✔
3217
                    limit: 10,
1✔
3218
                    offset: 0,
1✔
3219
                },
1✔
3220
            )
1✔
3221
            .await
1✔
3222
            .unwrap();
1✔
3223

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

3226
        let collection1_filtered_prefix = user_layer_db
1✔
3227
            .autocomplete_search(
1✔
3228
                &collection1_id,
1✔
3229
                SearchParameters {
1✔
3230
                    search_type: SearchType::Prefix,
1✔
3231
                    search_string: "ay".to_string(),
1✔
3232
                    limit: 10,
1✔
3233
                    offset: 0,
1✔
3234
                },
1✔
3235
            )
1✔
3236
            .await
1✔
3237
            .unwrap();
1✔
3238

1✔
3239
        assert_eq!(collection1_filtered_prefix, Vec::<String>::new());
1✔
3240

3241
        let collection1_filtered_prefix2 = user_layer_db
1✔
3242
            .autocomplete_search(
1✔
3243
                &collection1_id,
1✔
3244
                SearchParameters {
1✔
3245
                    search_type: SearchType::Prefix,
1✔
3246
                    search_string: "Lay".to_string(),
1✔
3247
                    limit: 10,
1✔
3248
                    offset: 0,
1✔
3249
                },
1✔
3250
            )
1✔
3251
            .await
1✔
3252
            .unwrap();
1✔
3253

1✔
3254
        assert_eq!(collection1_filtered_prefix2, vec!["Layer2".to_string(),]);
1✔
3255
    }
1✔
3256

3257
    #[allow(clippy::too_many_lines)]
3258
    #[ge_context::test]
3✔
3259
    async fn it_reports_search_capabilities(app_ctx: PostgresContext<NoTls>) {
1✔
3260
        let session = admin_login(&app_ctx).await;
1✔
3261

3262
        let layer_db = app_ctx.session_context(session).db();
1✔
3263

1✔
3264
        let capabilities = layer_db.capabilities().search;
1✔
3265

3266
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
3267

1✔
3268
        if capabilities.search_types.fulltext {
1✔
3269
            assert!(layer_db
1✔
3270
                .search(
1✔
3271
                    &root_collection_id,
1✔
3272
                    SearchParameters {
1✔
3273
                        search_type: SearchType::Fulltext,
1✔
3274
                        search_string: String::new(),
1✔
3275
                        limit: 10,
1✔
3276
                        offset: 0,
1✔
3277
                    },
1✔
3278
                )
1✔
3279
                .await
1✔
3280
                .is_ok());
1✔
3281

3282
            if capabilities.autocomplete {
1✔
3283
                assert!(layer_db
1✔
3284
                    .autocomplete_search(
1✔
3285
                        &root_collection_id,
1✔
3286
                        SearchParameters {
1✔
3287
                            search_type: SearchType::Fulltext,
1✔
3288
                            search_string: String::new(),
1✔
3289
                            limit: 10,
1✔
3290
                            offset: 0,
1✔
3291
                        },
1✔
3292
                    )
1✔
3293
                    .await
1✔
3294
                    .is_ok());
1✔
3295
            } else {
3296
                assert!(layer_db
×
3297
                    .autocomplete_search(
×
3298
                        &root_collection_id,
×
3299
                        SearchParameters {
×
3300
                            search_type: SearchType::Fulltext,
×
3301
                            search_string: String::new(),
×
3302
                            limit: 10,
×
3303
                            offset: 0,
×
3304
                        },
×
3305
                    )
×
3306
                    .await
×
3307
                    .is_err());
×
3308
            }
3309
        }
×
3310
        if capabilities.search_types.prefix {
1✔
3311
            assert!(layer_db
1✔
3312
                .search(
1✔
3313
                    &root_collection_id,
1✔
3314
                    SearchParameters {
1✔
3315
                        search_type: SearchType::Prefix,
1✔
3316
                        search_string: String::new(),
1✔
3317
                        limit: 10,
1✔
3318
                        offset: 0,
1✔
3319
                    },
1✔
3320
                )
1✔
3321
                .await
1✔
3322
                .is_ok());
1✔
3323

3324
            if capabilities.autocomplete {
1✔
3325
                assert!(layer_db
1✔
3326
                    .autocomplete_search(
1✔
3327
                        &root_collection_id,
1✔
3328
                        SearchParameters {
1✔
3329
                            search_type: SearchType::Prefix,
1✔
3330
                            search_string: String::new(),
1✔
3331
                            limit: 10,
1✔
3332
                            offset: 0,
1✔
3333
                        },
1✔
3334
                    )
1✔
3335
                    .await
1✔
3336
                    .is_ok());
1✔
3337
            } else {
3338
                assert!(layer_db
×
3339
                    .autocomplete_search(
×
3340
                        &root_collection_id,
×
3341
                        SearchParameters {
×
3342
                            search_type: SearchType::Prefix,
×
3343
                            search_string: String::new(),
×
3344
                            limit: 10,
×
3345
                            offset: 0,
×
3346
                        },
×
3347
                    )
×
3348
                    .await
×
3349
                    .is_err());
×
3350
            }
3351
        }
×
3352
    }
1✔
3353

3354
    #[ge_context::test]
3✔
3355
    async fn it_tracks_used_quota_in_postgres(app_ctx: PostgresContext<NoTls>) {
1✔
3356
        let _user = app_ctx
1✔
3357
            .register_user(UserRegistration {
1✔
3358
                email: "foo@example.com".to_string(),
1✔
3359
                password: "secret1234".to_string(),
1✔
3360
                real_name: "Foo Bar".to_string(),
1✔
3361
            })
1✔
3362
            .await
1✔
3363
            .unwrap();
1✔
3364

3365
        let session = app_ctx
1✔
3366
            .login(UserCredentials {
1✔
3367
                email: "foo@example.com".to_string(),
1✔
3368
                password: "secret1234".to_string(),
1✔
3369
            })
1✔
3370
            .await
1✔
3371
            .unwrap();
1✔
3372

3373
        let admin_session = admin_login(&app_ctx).await;
1✔
3374

3375
        let quota = initialize_quota_tracking(
1✔
3376
            QuotaTrackingMode::Check,
1✔
3377
            app_ctx.session_context(admin_session).db(),
1✔
3378
            0,
1✔
3379
            60,
1✔
3380
        );
1✔
3381

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

1✔
3384
        tracking.mock_work_unit_done();
1✔
3385
        tracking.mock_work_unit_done();
1✔
3386

1✔
3387
        let db = app_ctx.session_context(session).db();
1✔
3388

1✔
3389
        // wait for quota to be recorded
1✔
3390
        let mut success = false;
1✔
3391
        for _ in 0..10 {
2✔
3392
            let used = db.quota_used().await.unwrap();
2✔
3393
            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
2✔
3394

3395
            if used == 2 {
2✔
3396
                success = true;
1✔
3397
                break;
1✔
3398
            }
1✔
3399
        }
3400

3401
        assert!(success);
1✔
3402
    }
1✔
3403

3404
    #[ge_context::test]
3✔
3405
    async fn it_tracks_available_quota(app_ctx: PostgresContext<NoTls>) {
1✔
3406
        let user = app_ctx
1✔
3407
            .register_user(UserRegistration {
1✔
3408
                email: "foo@example.com".to_string(),
1✔
3409
                password: "secret1234".to_string(),
1✔
3410
                real_name: "Foo Bar".to_string(),
1✔
3411
            })
1✔
3412
            .await
1✔
3413
            .unwrap();
1✔
3414

3415
        let session = app_ctx
1✔
3416
            .login(UserCredentials {
1✔
3417
                email: "foo@example.com".to_string(),
1✔
3418
                password: "secret1234".to_string(),
1✔
3419
            })
1✔
3420
            .await
1✔
3421
            .unwrap();
1✔
3422

3423
        let admin_session = admin_login(&app_ctx).await;
1✔
3424

3425
        app_ctx
1✔
3426
            .session_context(admin_session.clone())
1✔
3427
            .db()
1✔
3428
            .update_quota_available_by_user(&user, 1)
1✔
3429
            .await
1✔
3430
            .unwrap();
1✔
3431

1✔
3432
        let quota = initialize_quota_tracking(
1✔
3433
            QuotaTrackingMode::Check,
1✔
3434
            app_ctx.session_context(admin_session).db(),
1✔
3435
            0,
1✔
3436
            60,
1✔
3437
        );
1✔
3438

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

1✔
3441
        tracking.mock_work_unit_done();
1✔
3442
        tracking.mock_work_unit_done();
1✔
3443

1✔
3444
        let db = app_ctx.session_context(session).db();
1✔
3445

1✔
3446
        // wait for quota to be recorded
1✔
3447
        let mut success = false;
1✔
3448
        for _ in 0..10 {
3✔
3449
            let available = db.quota_available().await.unwrap();
3✔
3450
            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
3✔
3451

3452
            if available == -1 {
3✔
3453
                success = true;
1✔
3454
                break;
1✔
3455
            }
2✔
3456
        }
3457

3458
        assert!(success);
1✔
3459
    }
1✔
3460

3461
    #[ge_context::test]
3✔
3462
    async fn it_updates_quota_in_postgres(app_ctx: PostgresContext<NoTls>) {
1✔
3463
        let user = app_ctx
1✔
3464
            .register_user(UserRegistration {
1✔
3465
                email: "foo@example.com".to_string(),
1✔
3466
                password: "secret1234".to_string(),
1✔
3467
                real_name: "Foo Bar".to_string(),
1✔
3468
            })
1✔
3469
            .await
1✔
3470
            .unwrap();
1✔
3471

3472
        let session = app_ctx
1✔
3473
            .login(UserCredentials {
1✔
3474
                email: "foo@example.com".to_string(),
1✔
3475
                password: "secret1234".to_string(),
1✔
3476
            })
1✔
3477
            .await
1✔
3478
            .unwrap();
1✔
3479

1✔
3480
        let db = app_ctx.session_context(session.clone()).db();
1✔
3481
        let admin_db = app_ctx.session_context(UserSession::admin_session()).db();
1✔
3482

3483
        assert_eq!(
1✔
3484
            db.quota_available().await.unwrap(),
1✔
3485
            crate::config::get_config_element::<crate::config::Quota>()
1✔
3486
                .unwrap()
1✔
3487
                .initial_credits
3488
        );
3489

3490
        assert_eq!(
1✔
3491
            admin_db.quota_available_by_user(&user).await.unwrap(),
1✔
3492
            crate::config::get_config_element::<crate::config::Quota>()
1✔
3493
                .unwrap()
1✔
3494
                .initial_credits
3495
        );
3496

3497
        admin_db
1✔
3498
            .update_quota_available_by_user(&user, 123)
1✔
3499
            .await
1✔
3500
            .unwrap();
1✔
3501

3502
        assert_eq!(db.quota_available().await.unwrap(), 123);
1✔
3503

3504
        assert_eq!(admin_db.quota_available_by_user(&user).await.unwrap(), 123);
1✔
3505
    }
1✔
3506

3507
    #[allow(clippy::too_many_lines)]
3508
    #[ge_context::test]
3✔
3509
    async fn it_removes_layer_collections(app_ctx: PostgresContext<NoTls>) {
1✔
3510
        let session = admin_login(&app_ctx).await;
1✔
3511

3512
        let layer_db = app_ctx.session_context(session).db();
1✔
3513

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

3532
        let root_collection = &layer_db.get_root_layer_collection_id().await.unwrap();
1✔
3533

1✔
3534
        let collection = AddLayerCollection {
1✔
3535
            name: "top collection".to_string(),
1✔
3536
            description: "description".to_string(),
1✔
3537
            properties: Default::default(),
1✔
3538
        };
1✔
3539

3540
        let top_c_id = layer_db
1✔
3541
            .add_layer_collection(collection, root_collection)
1✔
3542
            .await
1✔
3543
            .unwrap();
1✔
3544

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

1✔
3547
        let collection = AddLayerCollection {
1✔
3548
            name: "empty collection".to_string(),
1✔
3549
            description: "description".to_string(),
1✔
3550
            properties: Default::default(),
1✔
3551
        };
1✔
3552

3553
        let empty_c_id = layer_db
1✔
3554
            .add_layer_collection(collection, &top_c_id)
1✔
3555
            .await
1✔
3556
            .unwrap();
1✔
3557

3558
        let items = layer_db
1✔
3559
            .load_layer_collection(
1✔
3560
                &top_c_id,
1✔
3561
                LayerCollectionListOptions {
1✔
3562
                    offset: 0,
1✔
3563
                    limit: 20,
1✔
3564
                },
1✔
3565
            )
1✔
3566
            .await
1✔
3567
            .unwrap();
1✔
3568

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

3603
        // remove empty collection
3604
        layer_db.remove_layer_collection(&empty_c_id).await.unwrap();
1✔
3605

3606
        let items = layer_db
1✔
3607
            .load_layer_collection(
1✔
3608
                &top_c_id,
1✔
3609
                LayerCollectionListOptions {
1✔
3610
                    offset: 0,
1✔
3611
                    limit: 20,
1✔
3612
                },
1✔
3613
            )
1✔
3614
            .await
1✔
3615
            .unwrap();
1✔
3616

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

3640
        // remove top (not root) collection
3641
        layer_db.remove_layer_collection(&top_c_id).await.unwrap();
1✔
3642

1✔
3643
        layer_db
1✔
3644
            .load_layer_collection(
1✔
3645
                &top_c_id,
1✔
3646
                LayerCollectionListOptions {
1✔
3647
                    offset: 0,
1✔
3648
                    limit: 20,
1✔
3649
                },
1✔
3650
            )
1✔
3651
            .await
1✔
3652
            .unwrap_err();
1✔
3653

1✔
3654
        // should be deleted automatically
1✔
3655
        layer_db.load_layer(&l_id).await.unwrap_err();
1✔
3656

1✔
3657
        // it is not allowed to remove the root collection
1✔
3658
        layer_db
1✔
3659
            .remove_layer_collection(root_collection)
1✔
3660
            .await
1✔
3661
            .unwrap_err();
1✔
3662
        layer_db
1✔
3663
            .load_layer_collection(
1✔
3664
                root_collection,
1✔
3665
                LayerCollectionListOptions {
1✔
3666
                    offset: 0,
1✔
3667
                    limit: 20,
1✔
3668
                },
1✔
3669
            )
1✔
3670
            .await
1✔
3671
            .unwrap();
1✔
3672
    }
1✔
3673

3674
    #[ge_context::test]
3✔
3675
    #[allow(clippy::too_many_lines)]
3676
    async fn it_removes_collections_from_collections(app_ctx: PostgresContext<NoTls>) {
1✔
3677
        let session = admin_login(&app_ctx).await;
1✔
3678

3679
        let db = app_ctx.session_context(session).db();
1✔
3680

3681
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
3682

3683
        let mid_collection_id = db
1✔
3684
            .add_layer_collection(
1✔
3685
                AddLayerCollection {
1✔
3686
                    name: "mid collection".to_string(),
1✔
3687
                    description: "description".to_string(),
1✔
3688
                    properties: Default::default(),
1✔
3689
                },
1✔
3690
                root_collection_id,
1✔
3691
            )
1✔
3692
            .await
1✔
3693
            .unwrap();
1✔
3694

3695
        let bottom_collection_id = db
1✔
3696
            .add_layer_collection(
1✔
3697
                AddLayerCollection {
1✔
3698
                    name: "bottom collection".to_string(),
1✔
3699
                    description: "description".to_string(),
1✔
3700
                    properties: Default::default(),
1✔
3701
                },
1✔
3702
                &mid_collection_id,
1✔
3703
            )
1✔
3704
            .await
1✔
3705
            .unwrap();
1✔
3706

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

1✔
3731
        // removing the mid collection…
1✔
3732
        db.remove_layer_collection_from_parent(&mid_collection_id, root_collection_id)
1✔
3733
            .await
1✔
3734
            .unwrap();
1✔
3735

1✔
3736
        // …should remove itself
1✔
3737
        db.load_layer_collection(&mid_collection_id, LayerCollectionListOptions::default())
1✔
3738
            .await
1✔
3739
            .unwrap_err();
1✔
3740

1✔
3741
        // …should remove the bottom collection
1✔
3742
        db.load_layer_collection(&bottom_collection_id, LayerCollectionListOptions::default())
1✔
3743
            .await
1✔
3744
            .unwrap_err();
1✔
3745

1✔
3746
        // … and should remove the layer of the bottom collection
1✔
3747
        db.load_layer(&layer_id).await.unwrap_err();
1✔
3748

1✔
3749
        // the root collection is still there
1✔
3750
        db.load_layer_collection(root_collection_id, LayerCollectionListOptions::default())
1✔
3751
            .await
1✔
3752
            .unwrap();
1✔
3753
    }
1✔
3754

3755
    #[ge_context::test]
3✔
3756
    #[allow(clippy::too_many_lines)]
3757
    async fn it_removes_layers_from_collections(app_ctx: PostgresContext<NoTls>) {
1✔
3758
        let session = admin_login(&app_ctx).await;
1✔
3759

3760
        let db = app_ctx.session_context(session).db();
1✔
3761

3762
        let root_collection = &db.get_root_layer_collection_id().await.unwrap();
1✔
3763

3764
        let another_collection = db
1✔
3765
            .add_layer_collection(
1✔
3766
                AddLayerCollection {
1✔
3767
                    name: "top collection".to_string(),
1✔
3768
                    description: "description".to_string(),
1✔
3769
                    properties: Default::default(),
1✔
3770
                },
1✔
3771
                root_collection,
1✔
3772
            )
1✔
3773
            .await
1✔
3774
            .unwrap();
1✔
3775

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

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

1✔
3824
        db.load_layer(&layer_in_two_collections).await.unwrap();
1✔
3825

1✔
3826
        db.add_layer_to_collection(&layer_in_two_collections, root_collection)
1✔
3827
            .await
1✔
3828
            .unwrap();
1✔
3829

1✔
3830
        // remove first layer --> should be deleted entirely
1✔
3831

1✔
3832
        db.remove_layer_from_collection(&layer_in_one_collection, &another_collection)
1✔
3833
            .await
1✔
3834
            .unwrap();
1✔
3835

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

3853
        db.load_layer(&layer_in_one_collection).await.unwrap_err();
1✔
3854

1✔
3855
        // remove second layer --> should only be gone in collection
1✔
3856

1✔
3857
        db.remove_layer_from_collection(&layer_in_two_collections, &another_collection)
1✔
3858
            .await
1✔
3859
            .unwrap();
1✔
3860

3861
        let number_of_layer_in_collection = db
1✔
3862
            .load_layer_collection(
1✔
3863
                &another_collection,
1✔
3864
                LayerCollectionListOptions {
1✔
3865
                    offset: 0,
1✔
3866
                    limit: 20,
1✔
3867
                },
1✔
3868
            )
1✔
3869
            .await
1✔
3870
            .unwrap()
1✔
3871
            .items
1✔
3872
            .len();
1✔
3873
        assert_eq!(
1✔
3874
            number_of_layer_in_collection,
1✔
3875
            0 /* both layers were deleted */
1✔
3876
        );
1✔
3877

3878
        db.load_layer(&layer_in_two_collections).await.unwrap();
1✔
3879
    }
1✔
3880

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

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

3939
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
3940

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

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

1✔
3965
        assert!(db.load_dataset(&dataset_id).await.is_ok());
1✔
3966

3967
        db.delete_dataset(dataset_id).await.unwrap();
1✔
3968

1✔
3969
        assert!(db.load_dataset(&dataset_id).await.is_err());
1✔
3970
    }
1✔
3971

3972
    #[ge_context::test]
3✔
3973
    #[allow(clippy::too_many_lines)]
3974
    async fn it_deletes_admin_dataset(app_ctx: PostgresContext<NoTls>) {
1✔
3975
        let dataset_name = DatasetName::new(None, "my_dataset");
1✔
3976

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

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

4032
        let session = admin_login(&app_ctx).await;
1✔
4033

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

1✔
4056
        assert!(db.load_dataset(&dataset_id).await.is_ok());
1✔
4057

4058
        db.delete_dataset(dataset_id).await.unwrap();
1✔
4059

1✔
4060
        assert!(db.load_dataset(&dataset_id).await.is_err());
1✔
4061
    }
1✔
4062

4063
    #[ge_context::test]
3✔
4064
    async fn test_missing_layer_dataset_in_collection_listing(app_ctx: PostgresContext<NoTls>) {
1✔
4065
        let session = admin_login(&app_ctx).await;
1✔
4066
        let db = app_ctx.session_context(session).db();
1✔
4067

4068
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
4069

4070
        let top_collection_id = db
1✔
4071
            .add_layer_collection(
1✔
4072
                AddLayerCollection {
1✔
4073
                    name: "top collection".to_string(),
1✔
4074
                    description: "description".to_string(),
1✔
4075
                    properties: Default::default(),
1✔
4076
                },
1✔
4077
                root_collection_id,
1✔
4078
            )
1✔
4079
            .await
1✔
4080
            .unwrap();
1✔
4081

1✔
4082
        let faux_layer = LayerId("faux".to_string());
1✔
4083

1✔
4084
        // this should fail
1✔
4085
        db.add_layer_to_collection(&faux_layer, &top_collection_id)
1✔
4086
            .await
1✔
4087
            .unwrap_err();
1✔
4088

4089
        let root_collection_layers = db
1✔
4090
            .load_layer_collection(
1✔
4091
                &top_collection_id,
1✔
4092
                LayerCollectionListOptions {
1✔
4093
                    offset: 0,
1✔
4094
                    limit: 20,
1✔
4095
                },
1✔
4096
            )
1✔
4097
            .await
1✔
4098
            .unwrap();
1✔
4099

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

4118
    #[allow(clippy::too_many_lines)]
4119
    #[ge_context::test]
3✔
4120
    async fn it_restricts_layer_permissions(app_ctx: PostgresContext<NoTls>) {
1✔
4121
        let admin_session = admin_login(&app_ctx).await;
1✔
4122
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
4123

1✔
4124
        let admin_db = app_ctx.session_context(admin_session.clone()).db();
1✔
4125
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
4126

4127
        let root = admin_db.get_root_layer_collection_id().await.unwrap();
1✔
4128

4129
        // add new collection as admin
4130
        let new_collection_id = admin_db
1✔
4131
            .add_layer_collection(
1✔
4132
                AddLayerCollection {
1✔
4133
                    name: "admin collection".to_string(),
1✔
4134
                    description: String::new(),
1✔
4135
                    properties: Default::default(),
1✔
4136
                },
1✔
4137
                &root,
1✔
4138
            )
1✔
4139
            .await
1✔
4140
            .unwrap();
1✔
4141

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

4158
        // give user read permission
4159
        admin_db
1✔
4160
            .add_permission(
1✔
4161
                session1.user.id.into(),
1✔
4162
                new_collection_id.clone(),
1✔
4163
                Permission::Read,
1✔
4164
            )
1✔
4165
            .await
1✔
4166
            .unwrap();
1✔
4167

4168
        // now visible
4169
        let collection = db1
1✔
4170
            .load_layer_collection(
1✔
4171
                &root,
1✔
4172
                LayerCollectionListOptions {
1✔
4173
                    offset: 0,
1✔
4174
                    limit: 10,
1✔
4175
                },
1✔
4176
            )
1✔
4177
            .await
1✔
4178
            .unwrap();
1✔
4179

1✔
4180
        assert!(collection.items.iter().any(|c| match c {
1✔
4181
            CollectionItem::Collection(c) => c.id.collection_id == new_collection_id,
1✔
4182
            CollectionItem::Layer(_) => false,
×
4183
        }));
1✔
4184
    }
1✔
4185

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

1✔
4199
        let admin_db = app_ctx.session_context(admin_session.clone()).db();
1✔
4200

4201
        // create a new role
4202
        let role_id = admin_db.add_role("foo").await.unwrap();
1✔
4203

4204
        let user_session = app_ctx
1✔
4205
            .login(UserCredentials {
1✔
4206
                email: "foo@example.com".to_string(),
1✔
4207
                password: "secret123".to_string(),
1✔
4208
            })
1✔
4209
            .await
1✔
4210
            .unwrap();
1✔
4211

1✔
4212
        // user does not have the role yet
1✔
4213

1✔
4214
        assert!(!user_session.roles.contains(&role_id));
1✔
4215

4216
        //user can query their role descriptions (user role and registered user)
4217
        assert_eq!(user_session.roles.len(), 2);
1✔
4218

4219
        let expected_user_role_description = RoleDescription {
1✔
4220
            role: Role {
1✔
4221
                id: RoleId::from(user_id),
1✔
4222
                name: "foo@example.com".to_string(),
1✔
4223
            },
1✔
4224
            individual: true,
1✔
4225
        };
1✔
4226
        let expected_registered_role_description = RoleDescription {
1✔
4227
            role: Role {
1✔
4228
                id: Role::registered_user_role_id(),
1✔
4229
                name: "user".to_string(),
1✔
4230
            },
1✔
4231
            individual: false,
1✔
4232
        };
1✔
4233

4234
        let user_role_descriptions = app_ctx
1✔
4235
            .session_context(user_session.clone())
1✔
4236
            .db()
1✔
4237
            .get_role_descriptions(&user_id)
1✔
4238
            .await
1✔
4239
            .unwrap();
1✔
4240
        assert_eq!(
1✔
4241
            vec![
1✔
4242
                expected_user_role_description.clone(),
1✔
4243
                expected_registered_role_description.clone(),
1✔
4244
            ],
1✔
4245
            user_role_descriptions
1✔
4246
        );
1✔
4247

4248
        // we assign the role to the user
4249
        admin_db.assign_role(&role_id, &user_id).await.unwrap();
1✔
4250

4251
        let user_session = app_ctx
1✔
4252
            .login(UserCredentials {
1✔
4253
                email: "foo@example.com".to_string(),
1✔
4254
                password: "secret123".to_string(),
1✔
4255
            })
1✔
4256
            .await
1✔
4257
            .unwrap();
1✔
4258

1✔
4259
        // should be present now
1✔
4260
        assert!(user_session.roles.contains(&role_id));
1✔
4261

4262
        //user can query their role descriptions (now an additional foo role)
4263
        let expected_foo_role_description = RoleDescription {
1✔
4264
            role: Role {
1✔
4265
                id: role_id,
1✔
4266
                name: "foo".to_string(),
1✔
4267
            },
1✔
4268
            individual: false,
1✔
4269
        };
1✔
4270

4271
        let user_role_descriptions = app_ctx
1✔
4272
            .session_context(user_session.clone())
1✔
4273
            .db()
1✔
4274
            .get_role_descriptions(&user_id)
1✔
4275
            .await
1✔
4276
            .unwrap();
1✔
4277
        assert_eq!(
1✔
4278
            vec![
1✔
4279
                expected_foo_role_description,
1✔
4280
                expected_user_role_description.clone(),
1✔
4281
                expected_registered_role_description.clone(),
1✔
4282
            ],
1✔
4283
            user_role_descriptions
1✔
4284
        );
1✔
4285

4286
        // we revoke it
4287
        admin_db.revoke_role(&role_id, &user_id).await.unwrap();
1✔
4288

4289
        let user_session = app_ctx
1✔
4290
            .login(UserCredentials {
1✔
4291
                email: "foo@example.com".to_string(),
1✔
4292
                password: "secret123".to_string(),
1✔
4293
            })
1✔
4294
            .await
1✔
4295
            .unwrap();
1✔
4296

1✔
4297
        // the role is gone now
1✔
4298
        assert!(!user_session.roles.contains(&role_id));
1✔
4299

4300
        //user can query their role descriptions (user role and registered user)
4301
        let user_role_descriptions = app_ctx
1✔
4302
            .session_context(user_session.clone())
1✔
4303
            .db()
1✔
4304
            .get_role_descriptions(&user_id)
1✔
4305
            .await
1✔
4306
            .unwrap();
1✔
4307
        assert_eq!(
1✔
4308
            vec![
1✔
4309
                expected_user_role_description.clone(),
1✔
4310
                expected_registered_role_description.clone(),
1✔
4311
            ],
1✔
4312
            user_role_descriptions
1✔
4313
        );
1✔
4314

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

4317
        admin_db.assign_role(&role_id, &user_id).await.unwrap();
1✔
4318

1✔
4319
        admin_db.remove_role(&role_id).await.unwrap();
1✔
4320

4321
        let user_session = app_ctx
1✔
4322
            .login(UserCredentials {
1✔
4323
                email: "foo@example.com".to_string(),
1✔
4324
                password: "secret123".to_string(),
1✔
4325
            })
1✔
4326
            .await
1✔
4327
            .unwrap();
1✔
4328

1✔
4329
        assert!(!user_session.roles.contains(&role_id));
1✔
4330

4331
        //user can query their role descriptions (user role and registered user)
4332
        let user_role_descriptions = app_ctx
1✔
4333
            .session_context(user_session.clone())
1✔
4334
            .db()
1✔
4335
            .get_role_descriptions(&user_id)
1✔
4336
            .await
1✔
4337
            .unwrap();
1✔
4338
        assert_eq!(
1✔
4339
            vec![
1✔
4340
                expected_user_role_description,
1✔
4341
                expected_registered_role_description.clone(),
1✔
4342
            ],
1✔
4343
            user_role_descriptions
1✔
4344
        );
1✔
4345
    }
1✔
4346

4347
    #[allow(clippy::too_many_lines)]
4348
    #[ge_context::test]
3✔
4349
    async fn it_updates_project_layer_symbology(app_ctx: PostgresContext<NoTls>) {
1✔
4350
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
4351

4352
        let (_, workflow_id) = register_ndvi_workflow_helper(&app_ctx).await;
1✔
4353

4354
        let db = app_ctx.session_context(session.clone()).db();
1✔
4355

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

4383
        let project_id = db.create_project(create_project).await.unwrap();
1✔
4384

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

1✔
4419
        db.update_project(update).await.unwrap();
1✔
4420

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

1✔
4497
        db.update_project(update).await.unwrap();
1✔
4498

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

1✔
4575
        db.update_project(update).await.unwrap();
1✔
4576

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

4653
        // run two updates concurrently
4654
        let (r0, r1) = join!(db.update_project(update.clone()), db.update_project(update));
1✔
4655

4656
        assert!(r0.is_ok());
1✔
4657
        assert!(r1.is_ok());
1✔
4658
    }
1✔
4659

4660
    #[ge_context::test]
3✔
4661
    #[allow(clippy::too_many_lines)]
4662
    async fn it_resolves_dataset_names_to_ids(app_ctx: PostgresContext<NoTls>) {
1✔
4663
        let admin_session = UserSession::admin_session();
1✔
4664
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
4665

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

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

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

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

4770
        assert_eq!(
1✔
4771
            db.resolve_dataset_name_to_id(&dataset_name1)
1✔
4772
                .await
1✔
4773
                .unwrap()
1✔
4774
                .unwrap(),
1✔
4775
            dataset_id1
4776
        );
4777
        assert_eq!(
1✔
4778
            db.resolve_dataset_name_to_id(&dataset_name2)
1✔
4779
                .await
1✔
4780
                .unwrap()
1✔
4781
                .unwrap(),
1✔
4782
            dataset_id2
4783
        );
4784
    }
1✔
4785

4786
    #[ge_context::test]
3✔
4787
    #[allow(clippy::too_many_lines)]
4788
    async fn it_bulk_updates_quota(app_ctx: PostgresContext<NoTls>) {
1✔
4789
        let admin_session = UserSession::admin_session();
1✔
4790
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
4791

4792
        let user1 = app_ctx
1✔
4793
            .register_user(UserRegistration {
1✔
4794
                email: "user1@example.com".into(),
1✔
4795
                password: "12345678".into(),
1✔
4796
                real_name: "User1".into(),
1✔
4797
            })
1✔
4798
            .await
1✔
4799
            .unwrap();
1✔
4800

4801
        let user2 = app_ctx
1✔
4802
            .register_user(UserRegistration {
1✔
4803
                email: "user2@example.com".into(),
1✔
4804
                password: "12345678".into(),
1✔
4805
                real_name: "User2".into(),
1✔
4806
            })
1✔
4807
            .await
1✔
4808
            .unwrap();
1✔
4809

1✔
4810
        // single item in bulk
1✔
4811
        db.bulk_increment_quota_used([(user1, 1)]).await.unwrap();
1✔
4812

4813
        assert_eq!(db.quota_used_by_user(&user1).await.unwrap(), 1);
1✔
4814

4815
        // multiple items in bulk
4816
        db.bulk_increment_quota_used([(user1, 1), (user2, 3)])
1✔
4817
            .await
1✔
4818
            .unwrap();
1✔
4819

4820
        assert_eq!(db.quota_used_by_user(&user1).await.unwrap(), 2);
1✔
4821
        assert_eq!(db.quota_used_by_user(&user2).await.unwrap(), 3);
1✔
4822
    }
1✔
4823

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

4836
        let login_result = app_ctx
2✔
4837
            .login_external(external_user_claims.clone(), tokens)
2✔
4838
            .await;
2✔
4839
        assert!(login_result.is_ok());
2✔
4840

4841
        let session_id = login_result.unwrap().id;
2✔
4842

4843
        let access_token = app_ctx.get_access_token(session_id).await.unwrap();
2✔
4844

2✔
4845
        assert_eq!(
2✔
4846
            "FIRST_ACCESS_TOKEN".to_string(),
2✔
4847
            access_token.secret().to_owned()
2✔
4848
        );
2✔
4849

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

4853
        let access_token = app_ctx.get_access_token(session_id).await.unwrap();
2✔
4854

2✔
4855
        assert_eq!(
2✔
4856
            "SECOND_ACCESS_TOKEN".to_string(),
2✔
4857
            access_token.secret().to_owned()
2✔
4858
        );
2✔
4859
    }
2✔
4860

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

1✔
4873
        let (server, oidc_manager) = mock_refresh_server(mock_refresh_server_config);
1✔
4874

1✔
4875
        (server, move || {
1✔
4876
            OidcManager::from_oidc_with_static_tokens(oidc_manager.clone())
1✔
4877
        })
1✔
4878
    }
1✔
4879

4880
    #[ge_context::test(oidc_db = "oidc_only_refresh")]
3✔
4881
    async fn it_handles_oidc_tokens_without_encryption(app_ctx: PostgresContext<NoTls>) {
1✔
4882
        it_handles_oidc_tokens(app_ctx).await;
1✔
4883
    }
1✔
4884

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

1✔
4897
        let (server, oidc_manager) = mock_refresh_server(mock_refresh_server_config);
1✔
4898

1✔
4899
        (server, move || {
1✔
4900
            OidcManager::from_oidc_with_static_tokens(oidc_manager.clone())
1✔
4901
        })
1✔
4902
    }
1✔
4903

4904
    #[ge_context::test(oidc_db = "oidc_only_refresh_with_encryption")]
3✔
4905
    async fn it_handles_oidc_tokens_with_encryption(app_ctx: PostgresContext<NoTls>) {
1✔
4906
        it_handles_oidc_tokens(app_ctx).await;
1✔
4907
    }
1✔
4908
}
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