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

geo-engine / geoengine / 12767614094

14 Jan 2025 12:26PM UTC coverage: 90.64% (+0.06%) from 90.576%
12767614094

push

github

web-flow
Merge pull request #1006 from geo-engine/migrate-pro-api

Migrate-pro-api

1106 of 1152 new or added lines in 24 files covered. (96.01%)

248 existing lines in 13 files now uncovered.

133501 of 147287 relevant lines covered (90.64%)

54652.85 hits per line

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

97.21
/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::contexts::{
7
    initialize_database, ApplicationContext, CurrentSchemaMigration, MigrationResult,
8
    PostgresContext, QueryContextImpl, SessionId,
9
};
10
use crate::contexts::{GeoEngineDb, SessionContext};
11
use crate::datasets::upload::Volumes;
12
use crate::datasets::DatasetName;
13
use crate::error::{self, Error, Result};
14
use crate::machine_learning::error::MachineLearningError;
15
use crate::machine_learning::name::MlModelName;
16
use crate::pro::layers::add_from_directory::{
17
    add_layer_collections_from_directory, add_layers_from_directory,
18
    add_pro_providers_from_directory,
19
};
20
use crate::pro::quota::{initialize_quota_tracking, QuotaTrackingFactory};
21
use crate::pro::tasks::{ProTaskManager, ProTaskManagerBackend};
22
use crate::pro::users::OidcManager;
23
use crate::pro::users::{UserAuth, UserSession};
24
use crate::pro::util::config::{Cache, Oidc, Quota};
25
use crate::tasks::SimpleTaskManagerContext;
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 uuid::Uuid;
45

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

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

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

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

87
        Self::create_pro_database(pool.get().await?).await?;
226✔
88

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

207
        Ok(app_ctx)
×
208
    }
×
209

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

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

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

228
#[async_trait]
229
impl<Tls> ApplicationContext for ProPostgresContext<Tls>
230
where
231
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
232
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
233
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
234
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
235
{
236
    type SessionContext = PostgresSessionContext<Tls>;
237
    type Session = UserSession;
238

239
    fn session_context(&self, session: Self::Session) -> Self::SessionContext {
507✔
240
        PostgresSessionContext {
507✔
241
            session,
507✔
242
            context: self.clone(),
507✔
243
        }
507✔
244
    }
507✔
245

246
    async fn session_by_id(&self, session_id: SessionId) -> Result<Self::Session> {
171✔
247
        self.user_session_by_id(session_id)
171✔
248
            .await
171✔
249
            .map_err(Box::new)
165✔
250
            .context(error::Unauthorized)
165✔
251
    }
336✔
252
}
253

254
impl<Tls> ProApplicationContext for ProPostgresContext<Tls>
255
where
256
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
257
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
258
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
259
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
260
{
261
    fn oidc_manager(&self) -> &OidcManager {
30✔
262
        &self.oidc_manager
30✔
263
    }
30✔
264
}
265

266
#[derive(Clone)]
267
pub struct PostgresSessionContext<Tls>
268
where
269
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
270
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
271
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
272
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
273
{
274
    session: UserSession,
275
    context: ProPostgresContext<Tls>,
276
}
277

278
#[async_trait]
279
impl<Tls> SessionContext for PostgresSessionContext<Tls>
280
where
281
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
282
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
283
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
284
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
285
{
286
    type Session = UserSession;
287
    type GeoEngineDB = ProPostgresDb<Tls>;
288

289
    type TaskContext = SimpleTaskManagerContext;
290
    type TaskManager = ProTaskManager; // this does not persist across restarts
291
    type QueryContext = QueryContextImpl;
292
    type ExecutionContext = ExecutionContextImpl<Self::GeoEngineDB>;
293

294
    fn db(&self) -> Self::GeoEngineDB {
682✔
295
        ProPostgresDb::new(self.context.pool.clone(), self.session.clone())
682✔
296
    }
682✔
297

298
    fn tasks(&self) -> Self::TaskManager {
41✔
299
        ProTaskManager::new(self.context.task_manager.clone(), self.session.clone())
41✔
300
    }
41✔
301

302
    fn query_context(&self, workflow: Uuid, computation: Uuid) -> Result<Self::QueryContext> {
30✔
303
        // TODO: load config only once
30✔
304

30✔
305
        Ok(QueryContextImpl::new_with_extensions(
30✔
306
            self.context.query_ctx_chunk_size,
30✔
307
            self.context.thread_pool.clone(),
30✔
308
            Some(self.context.tile_cache.clone()),
30✔
309
            Some(
30✔
310
                self.context
30✔
311
                    .quota
30✔
312
                    .create_quota_tracking(&self.session, workflow, computation),
30✔
313
            ),
30✔
314
            Some(Box::new(QuotaCheckerImpl { user_db: self.db() }) as QuotaChecker),
30✔
315
        ))
30✔
316
    }
30✔
317

318
    fn execution_context(&self) -> Result<Self::ExecutionContext> {
55✔
319
        Ok(ExecutionContextImpl::<ProPostgresDb<Tls>>::new(
55✔
320
            self.db(),
55✔
321
            self.context.thread_pool.clone(),
55✔
322
            self.context.exe_ctx_tiling_spec,
55✔
323
        ))
55✔
324
    }
55✔
325

326
    fn volumes(&self) -> Result<Vec<Volume>> {
1✔
327
        Ok(self
1✔
328
            .context
1✔
329
            .volumes
1✔
330
            .volumes
1✔
331
            .iter()
1✔
332
            .map(|v| Volume {
1✔
333
                name: v.name.0.clone(),
1✔
334
                path: if self.session.is_admin() {
1✔
335
                    Some(v.path.to_string_lossy().to_string())
1✔
336
                } else {
337
                    None
×
338
                },
339
            })
1✔
340
            .collect())
1✔
341
    }
1✔
342

343
    fn session(&self) -> &Self::Session {
30✔
344
        &self.session
30✔
345
    }
30✔
346
}
347

348
#[derive(Debug)]
349
pub struct ProPostgresDb<Tls>
350
where
351
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
352
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
353
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
354
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
355
{
356
    pub(crate) conn_pool: Pool<PostgresConnectionManager<Tls>>,
357
    pub(crate) session: UserSession,
358
}
359

360
impl<Tls> ProPostgresDb<Tls>
361
where
362
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
363
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
364
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
365
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
366
{
367
    pub fn new(conn_pool: Pool<PostgresConnectionManager<Tls>>, session: UserSession) -> Self {
909✔
368
        Self { conn_pool, session }
909✔
369
    }
909✔
370

371
    /// Check whether the namepsace of the given dataset is allowed for insertion
372
    pub(crate) fn check_dataset_namespace(&self, id: &DatasetName) -> Result<()> {
93✔
373
        let is_ok = match &id.namespace {
93✔
374
            Some(namespace) => namespace.as_str() == self.session.user.id.to_string(),
27✔
375
            None => self.session.is_admin(),
66✔
376
        };
377

378
        if is_ok {
93✔
379
            Ok(())
93✔
380
        } else {
381
            Err(Error::InvalidDatasetIdNamespace)
×
382
        }
383
    }
93✔
384

385
    /// Check whether the namepsace of the given model is allowed for insertion
386
    pub(crate) fn check_ml_model_namespace(
1✔
387
        &self,
1✔
388
        name: &MlModelName,
1✔
389
    ) -> Result<(), MachineLearningError> {
1✔
390
        let is_ok = match &name.namespace {
1✔
391
            Some(namespace) => namespace.as_str() == self.session.user.id.to_string(),
1✔
392
            None => self.session.is_admin(),
×
393
        };
394

395
        if is_ok {
1✔
396
            Ok(())
1✔
397
        } else {
398
            Err(MachineLearningError::InvalidModelNamespace { name: name.clone() })
×
399
        }
400
    }
1✔
401
}
402

403
impl<Tls> GeoEngineDb for ProPostgresDb<Tls>
404
where
405
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
406
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
407
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
408
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
409
{
410
}
411

412
impl<Tls> ProGeoEngineDb for ProPostgresDb<Tls>
413
where
414
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
415
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
416
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
417
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
418
{
419
}
420

421
#[cfg(test)]
422
mod tests {
423
    use super::*;
424
    use crate::datasets::external::netcdfcf::NetCdfCfDataProviderDefinition;
425
    use crate::datasets::listing::{DatasetListOptions, DatasetListing, ProvenanceOutput};
426
    use crate::datasets::listing::{DatasetProvider, Provenance};
427
    use crate::datasets::storage::{DatasetStore, MetaDataDefinition};
428
    use crate::datasets::upload::{FileId, UploadId};
429
    use crate::datasets::upload::{FileUpload, Upload, UploadDb};
430
    use crate::datasets::{AddDataset, DatasetIdAndName};
431
    use crate::layers::add_from_directory::UNSORTED_COLLECTION_ID;
432
    use crate::layers::layer::{
433
        AddLayer, AddLayerCollection, CollectionItem, LayerCollection, LayerCollectionListOptions,
434
        LayerCollectionListing, LayerListing, ProviderLayerCollectionId, ProviderLayerId,
435
    };
436
    use crate::layers::listing::{
437
        LayerCollectionId, LayerCollectionProvider, SearchParameters, SearchType,
438
    };
439
    use crate::layers::storage::{
440
        LayerDb, LayerProviderDb, LayerProviderListing, LayerProviderListingOptions,
441
        INTERNAL_PROVIDER_ID,
442
    };
443
    use crate::pro::ge_context;
444
    use crate::pro::permissions::{Permission, PermissionDb, Role, RoleDescription, RoleId};
445
    use crate::pro::users::{OidcTokens, SessionTokenStore};
446
    use crate::pro::users::{
447
        RoleDb, UserClaims, UserCredentials, UserDb, UserId, UserRegistration,
448
    };
449
    use crate::pro::util::config::QuotaTrackingMode;
450
    use crate::pro::util::tests::mock_oidc::{mock_refresh_server, MockRefreshServerConfig};
451
    use crate::pro::util::tests::{admin_login, register_ndvi_workflow_helper, MockQuotaTracking};
452
    use crate::projects::{
453
        CreateProject, LayerUpdate, LoadVersion, OrderBy, Plot, PlotUpdate, PointSymbology,
454
        ProjectDb, ProjectId, ProjectLayer, ProjectListOptions, ProjectListing, STRectangle,
455
        UpdateProject,
456
    };
457
    use crate::workflows::registry::WorkflowRegistry;
458
    use crate::workflows::workflow::Workflow;
459
    use bb8_postgres::tokio_postgres::NoTls;
460
    use futures::join;
461
    use geoengine_datatypes::collections::VectorDataType;
462
    use geoengine_datatypes::dataset::{DataProviderId, LayerId};
463
    use geoengine_datatypes::primitives::{
464
        BoundingBox2D, Coordinate2D, DateTime, Duration, FeatureDataType, Measurement,
465
        RasterQueryRectangle, SpatialResolution, TimeGranularity, TimeInstance, TimeInterval,
466
        TimeStep, VectorQueryRectangle,
467
    };
468
    use geoengine_datatypes::primitives::{CacheTtlSeconds, ColumnSelection};
469
    use geoengine_datatypes::raster::RasterDataType;
470
    use geoengine_datatypes::spatial_reference::{SpatialReference, SpatialReferenceOption};
471
    use geoengine_datatypes::test_data;
472
    use geoengine_datatypes::util::Identifier;
473
    use geoengine_operators::engine::{
474
        MetaData, MetaDataProvider, MultipleRasterOrSingleVectorSource, PlotOperator,
475
        RasterBandDescriptors, RasterResultDescriptor, StaticMetaData, TypedOperator,
476
        TypedResultDescriptor, VectorColumnInfo, VectorOperator, VectorResultDescriptor,
477
    };
478
    use geoengine_operators::mock::{MockPointSource, MockPointSourceParams};
479
    use geoengine_operators::plot::{Statistics, StatisticsParams};
480
    use geoengine_operators::source::{
481
        CsvHeader, FileNotFoundHandling, FormatSpecifics, GdalDatasetGeoTransform,
482
        GdalDatasetParameters, GdalLoadingInfo, GdalMetaDataList, GdalMetaDataRegular,
483
        GdalMetaDataStatic, GdalMetadataNetCdfCf, OgrSourceColumnSpec, OgrSourceDataset,
484
        OgrSourceDatasetTimeType, OgrSourceDurationSpec, OgrSourceErrorSpec, OgrSourceTimeFormat,
485
    };
486
    use geoengine_operators::util::input::MultiRasterOrVectorOperator::Raster;
487
    use httptest::Server;
488
    use oauth2::{AccessToken, RefreshToken};
489
    use openidconnect::SubjectIdentifier;
490
    use serde_json::json;
491
    use std::str::FromStr;
492

493
    #[ge_context::test]
2✔
494
    async fn test(app_ctx: ProPostgresContext<NoTls>) {
1✔
495
        anonymous(&app_ctx).await;
1✔
496

497
        let _user_id = user_reg_login(&app_ctx).await;
1✔
498

499
        let session = app_ctx
1✔
500
            .login(UserCredentials {
1✔
501
                email: "foo@example.com".into(),
1✔
502
                password: "secret123".into(),
1✔
503
            })
1✔
504
            .await
1✔
505
            .unwrap();
1✔
506

1✔
507
        create_projects(&app_ctx, &session).await;
1✔
508

509
        let projects = list_projects(&app_ctx, &session).await;
1✔
510

511
        set_session(&app_ctx, &projects).await;
1✔
512

513
        let project_id = projects[0].id;
1✔
514

1✔
515
        update_projects(&app_ctx, &session, project_id).await;
1✔
516

517
        add_permission(&app_ctx, &session, project_id).await;
1✔
518

519
        delete_project(&app_ctx, &session, project_id).await;
1✔
520
    }
1✔
521

522
    #[ge_context::test]
2✔
523
    async fn test_external(app_ctx: ProPostgresContext<NoTls>) {
1✔
524
        anonymous(&app_ctx).await;
1✔
525

526
        let session = external_user_login_twice(&app_ctx).await;
1✔
527

528
        create_projects(&app_ctx, &session).await;
1✔
529

530
        let projects = list_projects(&app_ctx, &session).await;
1✔
531

532
        set_session_external(&app_ctx, &projects).await;
1✔
533

534
        let project_id = projects[0].id;
1✔
535

1✔
536
        update_projects(&app_ctx, &session, project_id).await;
1✔
537

538
        add_permission(&app_ctx, &session, project_id).await;
1✔
539

540
        delete_project(&app_ctx, &session, project_id).await;
1✔
541
    }
1✔
542

543
    fn tokens_from_duration(duration: Duration) -> OidcTokens {
3✔
544
        OidcTokens {
3✔
545
            access: AccessToken::new("AccessToken".to_string()),
3✔
546
            refresh: None,
3✔
547
            expires_in: duration,
3✔
548
        }
3✔
549
    }
3✔
550

551
    async fn set_session(app_ctx: &ProPostgresContext<NoTls>, projects: &[ProjectListing]) {
1✔
552
        let credentials = UserCredentials {
1✔
553
            email: "foo@example.com".into(),
1✔
554
            password: "secret123".into(),
1✔
555
        };
1✔
556

557
        let session = app_ctx.login(credentials).await.unwrap();
1✔
558

1✔
559
        set_session_in_database(app_ctx, projects, session).await;
1✔
560
    }
1✔
561

562
    async fn set_session_external(
1✔
563
        app_ctx: &ProPostgresContext<NoTls>,
1✔
564
        projects: &[ProjectListing],
1✔
565
    ) {
1✔
566
        let external_user_claims = UserClaims {
1✔
567
            external_id: SubjectIdentifier::new("Foo bar Id".into()),
1✔
568
            email: "foo@bar.de".into(),
1✔
569
            real_name: "Foo Bar".into(),
1✔
570
        };
1✔
571

572
        let session = app_ctx
1✔
573
            .login_external(
1✔
574
                external_user_claims,
1✔
575
                tokens_from_duration(Duration::minutes(10)),
1✔
576
            )
1✔
577
            .await
1✔
578
            .unwrap();
1✔
579

1✔
580
        set_session_in_database(app_ctx, projects, session).await;
1✔
581
    }
1✔
582

583
    async fn set_session_in_database(
2✔
584
        app_ctx: &ProPostgresContext<NoTls>,
2✔
585
        projects: &[ProjectListing],
2✔
586
        session: UserSession,
2✔
587
    ) {
2✔
588
        let db = app_ctx.session_context(session.clone()).db();
2✔
589

2✔
590
        db.set_session_project(projects[0].id).await.unwrap();
2✔
591

592
        assert_eq!(
2✔
593
            app_ctx.session_by_id(session.id).await.unwrap().project,
2✔
594
            Some(projects[0].id)
2✔
595
        );
596

597
        let rect = STRectangle::new_unchecked(SpatialReference::epsg_4326(), 0., 1., 2., 3., 1, 2);
2✔
598
        db.set_session_view(rect.clone()).await.unwrap();
2✔
599
        assert_eq!(
2✔
600
            app_ctx.session_by_id(session.id).await.unwrap().view,
2✔
601
            Some(rect)
2✔
602
        );
603
    }
2✔
604

605
    async fn delete_project(
2✔
606
        app_ctx: &ProPostgresContext<NoTls>,
2✔
607
        session: &UserSession,
2✔
608
        project_id: ProjectId,
2✔
609
    ) {
2✔
610
        let db = app_ctx.session_context(session.clone()).db();
2✔
611

2✔
612
        db.delete_project(project_id).await.unwrap();
2✔
613

2✔
614
        assert!(db.load_project(project_id).await.is_err());
2✔
615
    }
2✔
616

617
    async fn add_permission(
2✔
618
        app_ctx: &ProPostgresContext<NoTls>,
2✔
619
        session: &UserSession,
2✔
620
        project_id: ProjectId,
2✔
621
    ) {
2✔
622
        let db = app_ctx.session_context(session.clone()).db();
2✔
623

2✔
624
        assert!(db
2✔
625
            .has_permission(project_id, Permission::Owner)
2✔
626
            .await
2✔
627
            .unwrap());
2✔
628

629
        let user2 = app_ctx
2✔
630
            .register_user(UserRegistration {
2✔
631
                email: "user2@example.com".into(),
2✔
632
                password: "12345678".into(),
2✔
633
                real_name: "User2".into(),
2✔
634
            })
2✔
635
            .await
2✔
636
            .unwrap();
2✔
637

638
        let session2 = app_ctx
2✔
639
            .login(UserCredentials {
2✔
640
                email: "user2@example.com".into(),
2✔
641
                password: "12345678".into(),
2✔
642
            })
2✔
643
            .await
2✔
644
            .unwrap();
2✔
645

2✔
646
        let db2 = app_ctx.session_context(session2.clone()).db();
2✔
647
        assert!(!db2
2✔
648
            .has_permission(project_id, Permission::Owner)
2✔
649
            .await
2✔
650
            .unwrap());
2✔
651

652
        db.add_permission(user2.into(), project_id, Permission::Read)
2✔
653
            .await
2✔
654
            .unwrap();
2✔
655

2✔
656
        assert!(db2
2✔
657
            .has_permission(project_id, Permission::Read)
2✔
658
            .await
2✔
659
            .unwrap());
2✔
660
    }
2✔
661

662
    #[allow(clippy::too_many_lines)]
663
    async fn update_projects(
2✔
664
        app_ctx: &ProPostgresContext<NoTls>,
2✔
665
        session: &UserSession,
2✔
666
        project_id: ProjectId,
2✔
667
    ) {
2✔
668
        let db = app_ctx.session_context(session.clone()).db();
2✔
669

670
        let project = db
2✔
671
            .load_project_version(project_id, LoadVersion::Latest)
2✔
672
            .await
2✔
673
            .unwrap();
2✔
674

675
        let layer_workflow_id = db
2✔
676
            .register_workflow(Workflow {
2✔
677
                operator: TypedOperator::Vector(
2✔
678
                    MockPointSource {
2✔
679
                        params: MockPointSourceParams {
2✔
680
                            points: vec![Coordinate2D::new(1., 2.); 3],
2✔
681
                        },
2✔
682
                    }
2✔
683
                    .boxed(),
2✔
684
                ),
2✔
685
            })
2✔
686
            .await
2✔
687
            .unwrap();
2✔
688

2✔
689
        assert!(db.load_workflow(&layer_workflow_id).await.is_ok());
2✔
690

691
        let plot_workflow_id = db
2✔
692
            .register_workflow(Workflow {
2✔
693
                operator: Statistics {
2✔
694
                    params: StatisticsParams {
2✔
695
                        column_names: vec![],
2✔
696
                        percentiles: vec![],
2✔
697
                    },
2✔
698
                    sources: MultipleRasterOrSingleVectorSource {
2✔
699
                        source: Raster(vec![]),
2✔
700
                    },
2✔
701
                }
2✔
702
                .boxed()
2✔
703
                .into(),
2✔
704
            })
2✔
705
            .await
2✔
706
            .unwrap();
2✔
707

2✔
708
        assert!(db.load_workflow(&plot_workflow_id).await.is_ok());
2✔
709

710
        // add a plot
711
        let update = UpdateProject {
2✔
712
            id: project.id,
2✔
713
            name: Some("Test9 Updated".into()),
2✔
714
            description: None,
2✔
715
            layers: Some(vec![LayerUpdate::UpdateOrInsert(ProjectLayer {
2✔
716
                workflow: layer_workflow_id,
2✔
717
                name: "TestLayer".into(),
2✔
718
                symbology: PointSymbology::default().into(),
2✔
719
                visibility: Default::default(),
2✔
720
            })]),
2✔
721
            plots: Some(vec![PlotUpdate::UpdateOrInsert(Plot {
2✔
722
                workflow: plot_workflow_id,
2✔
723
                name: "Test Plot".into(),
2✔
724
            })]),
2✔
725
            bounds: None,
2✔
726
            time_step: None,
2✔
727
        };
2✔
728
        db.update_project(update).await.unwrap();
2✔
729

730
        let versions = db.list_project_versions(project_id).await.unwrap();
2✔
731
        assert_eq!(versions.len(), 2);
2✔
732

733
        // add second plot
734
        let update = UpdateProject {
2✔
735
            id: project.id,
2✔
736
            name: Some("Test9 Updated".into()),
2✔
737
            description: None,
2✔
738
            layers: Some(vec![LayerUpdate::UpdateOrInsert(ProjectLayer {
2✔
739
                workflow: layer_workflow_id,
2✔
740
                name: "TestLayer".into(),
2✔
741
                symbology: PointSymbology::default().into(),
2✔
742
                visibility: Default::default(),
2✔
743
            })]),
2✔
744
            plots: Some(vec![
2✔
745
                PlotUpdate::UpdateOrInsert(Plot {
2✔
746
                    workflow: plot_workflow_id,
2✔
747
                    name: "Test Plot".into(),
2✔
748
                }),
2✔
749
                PlotUpdate::UpdateOrInsert(Plot {
2✔
750
                    workflow: plot_workflow_id,
2✔
751
                    name: "Test Plot".into(),
2✔
752
                }),
2✔
753
            ]),
2✔
754
            bounds: None,
2✔
755
            time_step: None,
2✔
756
        };
2✔
757
        db.update_project(update).await.unwrap();
2✔
758

759
        let versions = db.list_project_versions(project_id).await.unwrap();
2✔
760
        assert_eq!(versions.len(), 3);
2✔
761

762
        // delete plots
763
        let update = UpdateProject {
2✔
764
            id: project.id,
2✔
765
            name: None,
2✔
766
            description: None,
2✔
767
            layers: None,
2✔
768
            plots: Some(vec![]),
2✔
769
            bounds: None,
2✔
770
            time_step: None,
2✔
771
        };
2✔
772
        db.update_project(update).await.unwrap();
2✔
773

774
        let versions = db.list_project_versions(project_id).await.unwrap();
2✔
775
        assert_eq!(versions.len(), 4);
2✔
776
    }
2✔
777

778
    async fn list_projects(
2✔
779
        app_ctx: &ProPostgresContext<NoTls>,
2✔
780
        session: &UserSession,
2✔
781
    ) -> Vec<ProjectListing> {
2✔
782
        let options = ProjectListOptions {
2✔
783
            order: OrderBy::NameDesc,
2✔
784
            offset: 0,
2✔
785
            limit: 2,
2✔
786
        };
2✔
787

2✔
788
        let db = app_ctx.session_context(session.clone()).db();
2✔
789

790
        let projects = db.list_projects(options).await.unwrap();
2✔
791

2✔
792
        assert_eq!(projects.len(), 2);
2✔
793
        assert_eq!(projects[0].name, "Test9");
2✔
794
        assert_eq!(projects[1].name, "Test8");
2✔
795
        projects
2✔
796
    }
2✔
797

798
    async fn create_projects(app_ctx: &ProPostgresContext<NoTls>, session: &UserSession) {
2✔
799
        let db = app_ctx.session_context(session.clone()).db();
2✔
800

801
        for i in 0..10 {
22✔
802
            let create = CreateProject {
20✔
803
                name: format!("Test{i}"),
20✔
804
                description: format!("Test{}", 10 - i),
20✔
805
                bounds: STRectangle::new(
20✔
806
                    SpatialReferenceOption::Unreferenced,
20✔
807
                    0.,
20✔
808
                    0.,
20✔
809
                    1.,
20✔
810
                    1.,
20✔
811
                    0,
20✔
812
                    1,
20✔
813
                )
20✔
814
                .unwrap(),
20✔
815
                time_step: None,
20✔
816
            };
20✔
817
            db.create_project(create).await.unwrap();
20✔
818
        }
819
    }
2✔
820

821
    async fn user_reg_login(app_ctx: &ProPostgresContext<NoTls>) -> UserId {
1✔
822
        let user_registration = UserRegistration {
1✔
823
            email: "foo@example.com".into(),
1✔
824
            password: "secret123".into(),
1✔
825
            real_name: "Foo Bar".into(),
1✔
826
        };
1✔
827

828
        let user_id = app_ctx.register_user(user_registration).await.unwrap();
1✔
829

1✔
830
        let credentials = UserCredentials {
1✔
831
            email: "foo@example.com".into(),
1✔
832
            password: "secret123".into(),
1✔
833
        };
1✔
834

835
        let session = app_ctx.login(credentials).await.unwrap();
1✔
836

1✔
837
        let db = app_ctx.session_context(session.clone()).db();
1✔
838

1✔
839
        app_ctx.session_by_id(session.id).await.unwrap();
1✔
840

1✔
841
        db.logout().await.unwrap();
1✔
842

1✔
843
        assert!(app_ctx.session_by_id(session.id).await.is_err());
1✔
844

845
        user_id
1✔
846
    }
1✔
847

848
    async fn external_user_login_twice(app_ctx: &ProPostgresContext<NoTls>) -> UserSession {
1✔
849
        let external_user_claims = UserClaims {
1✔
850
            external_id: SubjectIdentifier::new("Foo bar Id".into()),
1✔
851
            email: "foo@bar.de".into(),
1✔
852
            real_name: "Foo Bar".into(),
1✔
853
        };
1✔
854
        let duration = Duration::minutes(30);
1✔
855

856
        let login_result = app_ctx
1✔
857
            .login_external(external_user_claims.clone(), tokens_from_duration(duration))
1✔
858
            .await;
1✔
859
        assert!(login_result.is_ok());
1✔
860

861
        let session_1 = login_result.unwrap();
1✔
862
        let user_id = session_1.user.id; //TODO: Not a deterministic test.
1✔
863

1✔
864
        let db1 = app_ctx.session_context(session_1.clone()).db();
1✔
865

1✔
866
        assert!(session_1.user.email.is_some());
1✔
867
        assert_eq!(session_1.user.email.unwrap(), "foo@bar.de");
1✔
868
        assert!(session_1.user.real_name.is_some());
1✔
869
        assert_eq!(session_1.user.real_name.unwrap(), "Foo Bar");
1✔
870

871
        let expected_duration = session_1.created + duration;
1✔
872
        assert_eq!(session_1.valid_until, expected_duration);
1✔
873

874
        assert!(app_ctx.session_by_id(session_1.id).await.is_ok());
1✔
875

876
        assert!(db1.logout().await.is_ok());
1✔
877

878
        assert!(app_ctx.session_by_id(session_1.id).await.is_err());
1✔
879

880
        let duration = Duration::minutes(10);
1✔
881
        let login_result = app_ctx
1✔
882
            .login_external(external_user_claims.clone(), tokens_from_duration(duration))
1✔
883
            .await;
1✔
884
        assert!(login_result.is_ok());
1✔
885

886
        let session_2 = login_result.unwrap();
1✔
887
        let result = session_2.clone();
1✔
888

1✔
889
        assert!(session_2.user.email.is_some()); //TODO: Technically, user details could change for each login. For simplicity, this is not covered yet.
1✔
890
        assert_eq!(session_2.user.email.unwrap(), "foo@bar.de");
1✔
891
        assert!(session_2.user.real_name.is_some());
1✔
892
        assert_eq!(session_2.user.real_name.unwrap(), "Foo Bar");
1✔
893
        assert_eq!(session_2.user.id, user_id);
1✔
894

895
        let expected_duration = session_2.created + duration;
1✔
896
        assert_eq!(session_2.valid_until, expected_duration);
1✔
897

898
        assert!(app_ctx.session_by_id(session_2.id).await.is_ok());
1✔
899

900
        result
1✔
901
    }
1✔
902

903
    async fn anonymous(app_ctx: &ProPostgresContext<NoTls>) {
2✔
904
        let now: DateTime = chrono::offset::Utc::now().into();
2✔
905
        let session = app_ctx.create_anonymous_session().await.unwrap();
2✔
906
        let then: DateTime = chrono::offset::Utc::now().into();
2✔
907

2✔
908
        assert!(session.created >= now && session.created <= then);
2✔
909
        assert!(session.valid_until > session.created);
2✔
910

911
        let session = app_ctx.session_by_id(session.id).await.unwrap();
2✔
912

2✔
913
        let db = app_ctx.session_context(session.clone()).db();
2✔
914

2✔
915
        db.logout().await.unwrap();
2✔
916

2✔
917
        assert!(app_ctx.session_by_id(session.id).await.is_err());
2✔
918
    }
2✔
919

920
    #[ge_context::test]
2✔
921
    async fn it_persists_workflows(app_ctx: ProPostgresContext<NoTls>) {
1✔
922
        let workflow = Workflow {
1✔
923
            operator: TypedOperator::Vector(
1✔
924
                MockPointSource {
1✔
925
                    params: MockPointSourceParams {
1✔
926
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
927
                    },
1✔
928
                }
1✔
929
                .boxed(),
1✔
930
            ),
1✔
931
        };
1✔
932

933
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
934
        let ctx = app_ctx.session_context(session);
1✔
935

1✔
936
        let db = ctx.db();
1✔
937
        let id = db.register_workflow(workflow).await.unwrap();
1✔
938

1✔
939
        drop(ctx);
1✔
940

941
        let workflow = db.load_workflow(&id).await.unwrap();
1✔
942

1✔
943
        let json = serde_json::to_string(&workflow).unwrap();
1✔
944
        assert_eq!(
1✔
945
            json,
1✔
946
            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✔
947
        );
1✔
948
    }
1✔
949

950
    #[allow(clippy::too_many_lines)]
951
    #[ge_context::test]
2✔
952
    async fn it_persists_datasets(app_ctx: ProPostgresContext<NoTls>) {
1✔
953
        let loading_info = OgrSourceDataset {
1✔
954
            file_name: PathBuf::from("test.csv"),
1✔
955
            layer_name: "test.csv".to_owned(),
1✔
956
            data_type: Some(VectorDataType::MultiPoint),
1✔
957
            time: OgrSourceDatasetTimeType::Start {
1✔
958
                start_field: "start".to_owned(),
1✔
959
                start_format: OgrSourceTimeFormat::Auto,
1✔
960
                duration: OgrSourceDurationSpec::Zero,
1✔
961
            },
1✔
962
            default_geometry: None,
1✔
963
            columns: Some(OgrSourceColumnSpec {
1✔
964
                format_specifics: Some(FormatSpecifics::Csv {
1✔
965
                    header: CsvHeader::Auto,
1✔
966
                }),
1✔
967
                x: "x".to_owned(),
1✔
968
                y: None,
1✔
969
                int: vec![],
1✔
970
                float: vec![],
1✔
971
                text: vec![],
1✔
972
                bool: vec![],
1✔
973
                datetime: vec![],
1✔
974
                rename: None,
1✔
975
            }),
1✔
976
            force_ogr_time_filter: false,
1✔
977
            force_ogr_spatial_filter: false,
1✔
978
            on_error: OgrSourceErrorSpec::Ignore,
1✔
979
            sql_query: None,
1✔
980
            attribute_query: None,
1✔
981
            cache_ttl: CacheTtlSeconds::default(),
1✔
982
        };
1✔
983

1✔
984
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
985
            OgrSourceDataset,
1✔
986
            VectorResultDescriptor,
1✔
987
            VectorQueryRectangle,
1✔
988
        > {
1✔
989
            loading_info: loading_info.clone(),
1✔
990
            result_descriptor: VectorResultDescriptor {
1✔
991
                data_type: VectorDataType::MultiPoint,
1✔
992
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
993
                columns: [(
1✔
994
                    "foo".to_owned(),
1✔
995
                    VectorColumnInfo {
1✔
996
                        data_type: FeatureDataType::Float,
1✔
997
                        measurement: Measurement::Unitless,
1✔
998
                    },
1✔
999
                )]
1✔
1000
                .into_iter()
1✔
1001
                .collect(),
1✔
1002
                time: None,
1✔
1003
                bbox: None,
1✔
1004
            },
1✔
1005
            phantom: Default::default(),
1✔
1006
        });
1✔
1007

1008
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
1009

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

1✔
1012
        let db = app_ctx.session_context(session.clone()).db();
1✔
1013
        let DatasetIdAndName {
1014
            id: dataset_id,
1✔
1015
            name: dataset_name,
1✔
1016
        } = db
1✔
1017
            .add_dataset(
1✔
1018
                AddDataset {
1✔
1019
                    name: Some(dataset_name.clone()),
1✔
1020
                    display_name: "Ogr Test".to_owned(),
1✔
1021
                    description: "desc".to_owned(),
1✔
1022
                    source_operator: "OgrSource".to_owned(),
1✔
1023
                    symbology: None,
1✔
1024
                    provenance: Some(vec![Provenance {
1✔
1025
                        citation: "citation".to_owned(),
1✔
1026
                        license: "license".to_owned(),
1✔
1027
                        uri: "uri".to_owned(),
1✔
1028
                    }]),
1✔
1029
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1030
                },
1✔
1031
                meta_data,
1✔
1032
            )
1✔
1033
            .await
1✔
1034
            .unwrap();
1✔
1035

1036
        let datasets = db
1✔
1037
            .list_datasets(DatasetListOptions {
1✔
1038
                filter: None,
1✔
1039
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
1040
                offset: 0,
1✔
1041
                limit: 10,
1✔
1042
                tags: None,
1✔
1043
            })
1✔
1044
            .await
1✔
1045
            .unwrap();
1✔
1046

1✔
1047
        assert_eq!(datasets.len(), 1);
1✔
1048

1049
        assert_eq!(
1✔
1050
            datasets[0],
1✔
1051
            DatasetListing {
1✔
1052
                id: dataset_id,
1✔
1053
                name: dataset_name,
1✔
1054
                display_name: "Ogr Test".to_owned(),
1✔
1055
                description: "desc".to_owned(),
1✔
1056
                source_operator: "OgrSource".to_owned(),
1✔
1057
                symbology: None,
1✔
1058
                tags: vec!["upload".to_owned(), "test".to_owned()],
1✔
1059
                result_descriptor: TypedResultDescriptor::Vector(VectorResultDescriptor {
1✔
1060
                    data_type: VectorDataType::MultiPoint,
1✔
1061
                    spatial_reference: SpatialReference::epsg_4326().into(),
1✔
1062
                    columns: [(
1✔
1063
                        "foo".to_owned(),
1✔
1064
                        VectorColumnInfo {
1✔
1065
                            data_type: FeatureDataType::Float,
1✔
1066
                            measurement: Measurement::Unitless
1✔
1067
                        }
1✔
1068
                    )]
1✔
1069
                    .into_iter()
1✔
1070
                    .collect(),
1✔
1071
                    time: None,
1✔
1072
                    bbox: None,
1✔
1073
                })
1✔
1074
            },
1✔
1075
        );
1✔
1076

1077
        let provenance = db.load_provenance(&dataset_id).await.unwrap();
1✔
1078

1✔
1079
        assert_eq!(
1✔
1080
            provenance,
1✔
1081
            ProvenanceOutput {
1✔
1082
                data: dataset_id.into(),
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
            }
1✔
1089
        );
1✔
1090

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

1094
        assert_eq!(
1✔
1095
            meta_data
1✔
1096
                .loading_info(VectorQueryRectangle {
1✔
1097
                    spatial_bounds: BoundingBox2D::new_unchecked(
1✔
1098
                        (-180., -90.).into(),
1✔
1099
                        (180., 90.).into()
1✔
1100
                    ),
1✔
1101
                    time_interval: TimeInterval::default(),
1✔
1102
                    spatial_resolution: SpatialResolution::zero_point_one(),
1✔
1103
                    attributes: ColumnSelection::all()
1✔
1104
                })
1✔
1105
                .await
1✔
1106
                .unwrap(),
1✔
1107
            loading_info
1108
        );
1109
    }
1✔
1110

1111
    #[ge_context::test]
2✔
1112
    async fn it_persists_uploads(app_ctx: ProPostgresContext<NoTls>) {
1✔
1113
        let id = UploadId::from_str("2de18cd8-4a38-4111-a445-e3734bc18a80").unwrap();
1✔
1114
        let input = Upload {
1✔
1115
            id,
1✔
1116
            files: vec![FileUpload {
1✔
1117
                id: FileId::from_str("e80afab0-831d-4d40-95d6-1e4dfd277e72").unwrap(),
1✔
1118
                name: "test.csv".to_owned(),
1✔
1119
                byte_size: 1337,
1✔
1120
            }],
1✔
1121
        };
1✔
1122

1123
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
1124

1✔
1125
        let db = app_ctx.session_context(session.clone()).db();
1✔
1126

1✔
1127
        db.create_upload(input.clone()).await.unwrap();
1✔
1128

1129
        let upload = db.load_upload(id).await.unwrap();
1✔
1130

1✔
1131
        assert_eq!(upload, input);
1✔
1132
    }
1✔
1133

1134
    #[allow(clippy::too_many_lines)]
1135
    #[ge_context::test]
2✔
1136
    async fn it_persists_layer_providers(app_ctx: ProPostgresContext<NoTls>) {
1✔
1137
        let db = app_ctx.session_context(UserSession::admin_session()).db();
1✔
1138

1✔
1139
        let provider = NetCdfCfDataProviderDefinition {
1✔
1140
            name: "netcdfcf".to_string(),
1✔
1141
            description: "NetCdfCfProviderDefinition".to_string(),
1✔
1142
            priority: Some(33),
1✔
1143
            data: test_data!("netcdf4d/").into(),
1✔
1144
            overviews: test_data!("netcdf4d/overviews/").into(),
1✔
1145
            cache_ttl: CacheTtlSeconds::new(0),
1✔
1146
        };
1✔
1147

1148
        let provider_id = db.add_layer_provider(provider.into()).await.unwrap();
1✔
1149

1150
        let providers = db
1✔
1151
            .list_layer_providers(LayerProviderListingOptions {
1✔
1152
                offset: 0,
1✔
1153
                limit: 10,
1✔
1154
            })
1✔
1155
            .await
1✔
1156
            .unwrap();
1✔
1157

1✔
1158
        assert_eq!(providers.len(), 1);
1✔
1159

1160
        assert_eq!(
1✔
1161
            providers[0],
1✔
1162
            LayerProviderListing {
1✔
1163
                id: provider_id,
1✔
1164
                name: "netcdfcf".to_owned(),
1✔
1165
                priority: 33,
1✔
1166
            }
1✔
1167
        );
1✔
1168

1169
        let provider = db.load_layer_provider(provider_id).await.unwrap();
1✔
1170

1171
        let datasets = provider
1✔
1172
            .load_layer_collection(
1✔
1173
                &provider.get_root_layer_collection_id().await.unwrap(),
1✔
1174
                LayerCollectionListOptions {
1✔
1175
                    offset: 0,
1✔
1176
                    limit: 10,
1✔
1177
                },
1✔
1178
            )
1✔
1179
            .await
1✔
1180
            .unwrap();
1✔
1181

1✔
1182
        assert_eq!(datasets.items.len(), 5);
1✔
1183
    }
1✔
1184

1185
    #[ge_context::test]
2✔
1186
    async fn it_lists_only_permitted_datasets(app_ctx: ProPostgresContext<NoTls>) {
1✔
1187
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1188
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1189

1✔
1190
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1191
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1192

1✔
1193
        let descriptor = VectorResultDescriptor {
1✔
1194
            data_type: VectorDataType::Data,
1✔
1195
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1196
            columns: Default::default(),
1✔
1197
            time: None,
1✔
1198
            bbox: None,
1✔
1199
        };
1✔
1200

1✔
1201
        let ds = AddDataset {
1✔
1202
            name: None,
1✔
1203
            display_name: "OgrDataset".to_string(),
1✔
1204
            description: "My Ogr dataset".to_string(),
1✔
1205
            source_operator: "OgrSource".to_string(),
1✔
1206
            symbology: None,
1✔
1207
            provenance: None,
1✔
1208
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1209
        };
1✔
1210

1✔
1211
        let meta = StaticMetaData {
1✔
1212
            loading_info: OgrSourceDataset {
1✔
1213
                file_name: Default::default(),
1✔
1214
                layer_name: String::new(),
1✔
1215
                data_type: None,
1✔
1216
                time: Default::default(),
1✔
1217
                default_geometry: None,
1✔
1218
                columns: None,
1✔
1219
                force_ogr_time_filter: false,
1✔
1220
                force_ogr_spatial_filter: false,
1✔
1221
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1222
                sql_query: None,
1✔
1223
                attribute_query: None,
1✔
1224
                cache_ttl: CacheTtlSeconds::default(),
1✔
1225
            },
1✔
1226
            result_descriptor: descriptor.clone(),
1✔
1227
            phantom: Default::default(),
1✔
1228
        };
1✔
1229

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

1232
        let list1 = db1
1✔
1233
            .list_datasets(DatasetListOptions {
1✔
1234
                filter: None,
1✔
1235
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
1236
                offset: 0,
1✔
1237
                limit: 1,
1✔
1238
                tags: None,
1✔
1239
            })
1✔
1240
            .await
1✔
1241
            .unwrap();
1✔
1242

1✔
1243
        assert_eq!(list1.len(), 1);
1✔
1244

1245
        let list2 = db2
1✔
1246
            .list_datasets(DatasetListOptions {
1✔
1247
                filter: None,
1✔
1248
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
1249
                offset: 0,
1✔
1250
                limit: 1,
1✔
1251
                tags: None,
1✔
1252
            })
1✔
1253
            .await
1✔
1254
            .unwrap();
1✔
1255

1✔
1256
        assert_eq!(list2.len(), 0);
1✔
1257
    }
1✔
1258

1259
    #[ge_context::test]
2✔
1260
    async fn it_shows_only_permitted_provenance(app_ctx: ProPostgresContext<NoTls>) {
1✔
1261
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1262
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1263

1✔
1264
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1265
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1266

1✔
1267
        let descriptor = VectorResultDescriptor {
1✔
1268
            data_type: VectorDataType::Data,
1✔
1269
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1270
            columns: Default::default(),
1✔
1271
            time: None,
1✔
1272
            bbox: None,
1✔
1273
        };
1✔
1274

1✔
1275
        let ds = AddDataset {
1✔
1276
            name: None,
1✔
1277
            display_name: "OgrDataset".to_string(),
1✔
1278
            description: "My Ogr dataset".to_string(),
1✔
1279
            source_operator: "OgrSource".to_string(),
1✔
1280
            symbology: None,
1✔
1281
            provenance: None,
1✔
1282
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1283
        };
1✔
1284

1✔
1285
        let meta = StaticMetaData {
1✔
1286
            loading_info: OgrSourceDataset {
1✔
1287
                file_name: Default::default(),
1✔
1288
                layer_name: String::new(),
1✔
1289
                data_type: None,
1✔
1290
                time: Default::default(),
1✔
1291
                default_geometry: None,
1✔
1292
                columns: None,
1✔
1293
                force_ogr_time_filter: false,
1✔
1294
                force_ogr_spatial_filter: false,
1✔
1295
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1296
                sql_query: None,
1✔
1297
                attribute_query: None,
1✔
1298
                cache_ttl: CacheTtlSeconds::default(),
1✔
1299
            },
1✔
1300
            result_descriptor: descriptor.clone(),
1✔
1301
            phantom: Default::default(),
1✔
1302
        };
1✔
1303

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

1✔
1306
        assert!(db1.load_provenance(&id).await.is_ok());
1✔
1307

1308
        assert!(db2.load_provenance(&id).await.is_err());
1✔
1309
    }
1✔
1310

1311
    #[ge_context::test]
2✔
1312
    async fn it_updates_permissions(app_ctx: ProPostgresContext<NoTls>) {
1✔
1313
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1314
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1315

1✔
1316
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1317
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1318

1✔
1319
        let descriptor = VectorResultDescriptor {
1✔
1320
            data_type: VectorDataType::Data,
1✔
1321
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1322
            columns: Default::default(),
1✔
1323
            time: None,
1✔
1324
            bbox: None,
1✔
1325
        };
1✔
1326

1✔
1327
        let ds = AddDataset {
1✔
1328
            name: None,
1✔
1329
            display_name: "OgrDataset".to_string(),
1✔
1330
            description: "My Ogr dataset".to_string(),
1✔
1331
            source_operator: "OgrSource".to_string(),
1✔
1332
            symbology: None,
1✔
1333
            provenance: None,
1✔
1334
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1335
        };
1✔
1336

1✔
1337
        let meta = StaticMetaData {
1✔
1338
            loading_info: OgrSourceDataset {
1✔
1339
                file_name: Default::default(),
1✔
1340
                layer_name: String::new(),
1✔
1341
                data_type: None,
1✔
1342
                time: Default::default(),
1✔
1343
                default_geometry: None,
1✔
1344
                columns: None,
1✔
1345
                force_ogr_time_filter: false,
1✔
1346
                force_ogr_spatial_filter: false,
1✔
1347
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1348
                sql_query: None,
1✔
1349
                attribute_query: None,
1✔
1350
                cache_ttl: CacheTtlSeconds::default(),
1✔
1351
            },
1✔
1352
            result_descriptor: descriptor.clone(),
1✔
1353
            phantom: Default::default(),
1✔
1354
        };
1✔
1355

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

1✔
1358
        assert!(db1.load_dataset(&id).await.is_ok());
1✔
1359

1360
        assert!(db2.load_dataset(&id).await.is_err());
1✔
1361

1362
        db1.add_permission(session2.user.id.into(), id, Permission::Read)
1✔
1363
            .await
1✔
1364
            .unwrap();
1✔
1365

1✔
1366
        assert!(db2.load_dataset(&id).await.is_ok());
1✔
1367
    }
1✔
1368

1369
    #[ge_context::test]
2✔
1370
    async fn it_uses_roles_for_permissions(app_ctx: ProPostgresContext<NoTls>) {
1✔
1371
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1372
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1373

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

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

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

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

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

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

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

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

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

1427
    #[ge_context::test]
2✔
1428
    async fn it_secures_meta_data(app_ctx: ProPostgresContext<NoTls>) {
1✔
1429
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1430
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1431

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

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

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

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

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

1474
        let meta: geoengine_operators::util::Result<
1✔
1475
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1476
        > = db1.meta_data(&id.into()).await;
1✔
1477

1478
        assert!(meta.is_ok());
1✔
1479

1480
        let meta: geoengine_operators::util::Result<
1✔
1481
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1482
        > = db2.meta_data(&id.into()).await;
1✔
1483

1484
        assert!(meta.is_err());
1✔
1485

1486
        db1.add_permission(session2.user.id.into(), id, Permission::Read)
1✔
1487
            .await
1✔
1488
            .unwrap();
1✔
1489

1490
        let meta: geoengine_operators::util::Result<
1✔
1491
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1492
        > = db2.meta_data(&id.into()).await;
1✔
1493

1494
        assert!(meta.is_ok());
1✔
1495
    }
1✔
1496

1497
    #[allow(clippy::too_many_lines)]
1498
    #[ge_context::test]
2✔
1499
    async fn it_loads_all_meta_data_types(app_ctx: ProPostgresContext<NoTls>) {
1✔
1500
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
1501

1✔
1502
        let db = app_ctx.session_context(session.clone()).db();
1✔
1503

1✔
1504
        let vector_descriptor = VectorResultDescriptor {
1✔
1505
            data_type: VectorDataType::Data,
1✔
1506
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1507
            columns: Default::default(),
1✔
1508
            time: None,
1✔
1509
            bbox: None,
1✔
1510
        };
1✔
1511

1✔
1512
        let raster_descriptor = RasterResultDescriptor {
1✔
1513
            data_type: RasterDataType::U8,
1✔
1514
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1515
            time: None,
1✔
1516
            bbox: None,
1✔
1517
            resolution: None,
1✔
1518
            bands: RasterBandDescriptors::new_single_band(),
1✔
1519
        };
1✔
1520

1✔
1521
        let vector_ds = AddDataset {
1✔
1522
            name: None,
1✔
1523
            display_name: "OgrDataset".to_string(),
1✔
1524
            description: "My Ogr dataset".to_string(),
1✔
1525
            source_operator: "OgrSource".to_string(),
1✔
1526
            symbology: None,
1✔
1527
            provenance: None,
1✔
1528
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1529
        };
1✔
1530

1✔
1531
        let raster_ds = AddDataset {
1✔
1532
            name: None,
1✔
1533
            display_name: "GdalDataset".to_string(),
1✔
1534
            description: "My Gdal dataset".to_string(),
1✔
1535
            source_operator: "GdalSource".to_string(),
1✔
1536
            symbology: None,
1✔
1537
            provenance: None,
1✔
1538
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1539
        };
1✔
1540

1✔
1541
        let gdal_params = GdalDatasetParameters {
1✔
1542
            file_path: Default::default(),
1✔
1543
            rasterband_channel: 0,
1✔
1544
            geo_transform: GdalDatasetGeoTransform {
1✔
1545
                origin_coordinate: Default::default(),
1✔
1546
                x_pixel_size: 0.0,
1✔
1547
                y_pixel_size: 0.0,
1✔
1548
            },
1✔
1549
            width: 0,
1✔
1550
            height: 0,
1✔
1551
            file_not_found_handling: FileNotFoundHandling::NoData,
1✔
1552
            no_data_value: None,
1✔
1553
            properties_mapping: None,
1✔
1554
            gdal_open_options: None,
1✔
1555
            gdal_config_options: None,
1✔
1556
            allow_alphaband_as_mask: false,
1✔
1557
            retry: None,
1✔
1558
        };
1✔
1559

1✔
1560
        let meta = StaticMetaData {
1✔
1561
            loading_info: OgrSourceDataset {
1✔
1562
                file_name: Default::default(),
1✔
1563
                layer_name: String::new(),
1✔
1564
                data_type: None,
1✔
1565
                time: Default::default(),
1✔
1566
                default_geometry: None,
1✔
1567
                columns: None,
1✔
1568
                force_ogr_time_filter: false,
1✔
1569
                force_ogr_spatial_filter: false,
1✔
1570
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1571
                sql_query: None,
1✔
1572
                attribute_query: None,
1✔
1573
                cache_ttl: CacheTtlSeconds::default(),
1✔
1574
            },
1✔
1575
            result_descriptor: vector_descriptor.clone(),
1✔
1576
            phantom: Default::default(),
1✔
1577
        };
1✔
1578

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

1581
        let meta: geoengine_operators::util::Result<
1✔
1582
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1583
        > = db.meta_data(&id.into()).await;
1✔
1584

1585
        assert!(meta.is_ok());
1✔
1586

1587
        let meta = GdalMetaDataRegular {
1✔
1588
            result_descriptor: raster_descriptor.clone(),
1✔
1589
            params: gdal_params.clone(),
1✔
1590
            time_placeholders: Default::default(),
1✔
1591
            data_time: Default::default(),
1✔
1592
            step: TimeStep {
1✔
1593
                granularity: TimeGranularity::Millis,
1✔
1594
                step: 0,
1✔
1595
            },
1✔
1596
            cache_ttl: CacheTtlSeconds::default(),
1✔
1597
        };
1✔
1598

1599
        let id = db
1✔
1600
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1601
            .await
1✔
1602
            .unwrap()
1✔
1603
            .id;
1604

1605
        let meta: geoengine_operators::util::Result<
1✔
1606
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1607
        > = db.meta_data(&id.into()).await;
1✔
1608

1609
        assert!(meta.is_ok());
1✔
1610

1611
        let meta = GdalMetaDataStatic {
1✔
1612
            time: None,
1✔
1613
            params: gdal_params.clone(),
1✔
1614
            result_descriptor: raster_descriptor.clone(),
1✔
1615
            cache_ttl: CacheTtlSeconds::default(),
1✔
1616
        };
1✔
1617

1618
        let id = db
1✔
1619
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1620
            .await
1✔
1621
            .unwrap()
1✔
1622
            .id;
1623

1624
        let meta: geoengine_operators::util::Result<
1✔
1625
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1626
        > = db.meta_data(&id.into()).await;
1✔
1627

1628
        assert!(meta.is_ok());
1✔
1629

1630
        let meta = GdalMetaDataList {
1✔
1631
            result_descriptor: raster_descriptor.clone(),
1✔
1632
            params: vec![],
1✔
1633
        };
1✔
1634

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

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

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

1647
        let meta = GdalMetadataNetCdfCf {
1✔
1648
            result_descriptor: raster_descriptor.clone(),
1✔
1649
            params: gdal_params.clone(),
1✔
1650
            start: TimeInstance::MIN,
1✔
1651
            end: TimeInstance::MAX,
1✔
1652
            step: TimeStep {
1✔
1653
                granularity: TimeGranularity::Millis,
1✔
1654
                step: 0,
1✔
1655
            },
1✔
1656
            band_offset: 0,
1✔
1657
            cache_ttl: CacheTtlSeconds::default(),
1✔
1658
        };
1✔
1659

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

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

1670
        assert!(meta.is_ok());
1✔
1671
    }
1✔
1672

1673
    #[ge_context::test]
2✔
1674
    async fn it_secures_uploads(app_ctx: ProPostgresContext<NoTls>) {
1✔
1675
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1676
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
1✔
1677

1✔
1678
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1679
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1680

1✔
1681
        let upload_id = UploadId::new();
1✔
1682

1✔
1683
        let upload = Upload {
1✔
1684
            id: upload_id,
1✔
1685
            files: vec![FileUpload {
1✔
1686
                id: FileId::new(),
1✔
1687
                name: "test.bin".to_owned(),
1✔
1688
                byte_size: 1024,
1✔
1689
            }],
1✔
1690
        };
1✔
1691

1✔
1692
        db1.create_upload(upload).await.unwrap();
1✔
1693

1✔
1694
        assert!(db1.load_upload(upload_id).await.is_ok());
1✔
1695

1696
        assert!(db2.load_upload(upload_id).await.is_err());
1✔
1697
    }
1✔
1698

1699
    #[allow(clippy::too_many_lines)]
1700
    #[ge_context::test]
2✔
1701
    async fn it_collects_layers(app_ctx: ProPostgresContext<NoTls>) {
1✔
1702
        let session = admin_login(&app_ctx).await;
1✔
1703

1704
        let layer_db = app_ctx.session_context(session).db();
1✔
1705

1✔
1706
        let workflow = Workflow {
1✔
1707
            operator: TypedOperator::Vector(
1✔
1708
                MockPointSource {
1✔
1709
                    params: MockPointSourceParams {
1✔
1710
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1711
                    },
1✔
1712
                }
1✔
1713
                .boxed(),
1✔
1714
            ),
1✔
1715
        };
1✔
1716

1717
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1718

1719
        let layer1 = layer_db
1✔
1720
            .add_layer(
1✔
1721
                AddLayer {
1✔
1722
                    name: "Layer1".to_string(),
1✔
1723
                    description: "Layer 1".to_string(),
1✔
1724
                    symbology: None,
1✔
1725
                    workflow: workflow.clone(),
1✔
1726
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1727
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1728
                },
1✔
1729
                &root_collection_id,
1✔
1730
            )
1✔
1731
            .await
1✔
1732
            .unwrap();
1✔
1733

1734
        assert_eq!(
1✔
1735
            layer_db.load_layer(&layer1).await.unwrap(),
1✔
1736
            crate::layers::layer::Layer {
1✔
1737
                id: ProviderLayerId {
1✔
1738
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1739
                    layer_id: layer1.clone(),
1✔
1740
                },
1✔
1741
                name: "Layer1".to_string(),
1✔
1742
                description: "Layer 1".to_string(),
1✔
1743
                symbology: None,
1✔
1744
                workflow: workflow.clone(),
1✔
1745
                metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1746
                properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1747
            }
1✔
1748
        );
1749

1750
        let collection1_id = layer_db
1✔
1751
            .add_layer_collection(
1✔
1752
                AddLayerCollection {
1✔
1753
                    name: "Collection1".to_string(),
1✔
1754
                    description: "Collection 1".to_string(),
1✔
1755
                    properties: Default::default(),
1✔
1756
                },
1✔
1757
                &root_collection_id,
1✔
1758
            )
1✔
1759
            .await
1✔
1760
            .unwrap();
1✔
1761

1762
        let layer2 = layer_db
1✔
1763
            .add_layer(
1✔
1764
                AddLayer {
1✔
1765
                    name: "Layer2".to_string(),
1✔
1766
                    description: "Layer 2".to_string(),
1✔
1767
                    symbology: None,
1✔
1768
                    workflow: workflow.clone(),
1✔
1769
                    metadata: Default::default(),
1✔
1770
                    properties: Default::default(),
1✔
1771
                },
1✔
1772
                &collection1_id,
1✔
1773
            )
1✔
1774
            .await
1✔
1775
            .unwrap();
1✔
1776

1777
        let collection2_id = layer_db
1✔
1778
            .add_layer_collection(
1✔
1779
                AddLayerCollection {
1✔
1780
                    name: "Collection2".to_string(),
1✔
1781
                    description: "Collection 2".to_string(),
1✔
1782
                    properties: Default::default(),
1✔
1783
                },
1✔
1784
                &collection1_id,
1✔
1785
            )
1✔
1786
            .await
1✔
1787
            .unwrap();
1✔
1788

1✔
1789
        layer_db
1✔
1790
            .add_collection_to_parent(&collection2_id, &collection1_id)
1✔
1791
            .await
1✔
1792
            .unwrap();
1✔
1793

1794
        let root_collection = layer_db
1✔
1795
            .load_layer_collection(
1✔
1796
                &root_collection_id,
1✔
1797
                LayerCollectionListOptions {
1✔
1798
                    offset: 0,
1✔
1799
                    limit: 20,
1✔
1800
                },
1✔
1801
            )
1✔
1802
            .await
1✔
1803
            .unwrap();
1✔
1804

1✔
1805
        assert_eq!(
1✔
1806
            root_collection,
1✔
1807
            LayerCollection {
1✔
1808
                id: ProviderLayerCollectionId {
1✔
1809
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1810
                    collection_id: root_collection_id,
1✔
1811
                },
1✔
1812
                name: "Layers".to_string(),
1✔
1813
                description: "All available Geo Engine layers".to_string(),
1✔
1814
                items: vec![
1✔
1815
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1816
                        id: ProviderLayerCollectionId {
1✔
1817
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1818
                            collection_id: collection1_id.clone(),
1✔
1819
                        },
1✔
1820
                        name: "Collection1".to_string(),
1✔
1821
                        description: "Collection 1".to_string(),
1✔
1822
                        properties: Default::default(),
1✔
1823
                    }),
1✔
1824
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1825
                        id: ProviderLayerCollectionId {
1✔
1826
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1827
                            collection_id: LayerCollectionId(UNSORTED_COLLECTION_ID.to_string()),
1✔
1828
                        },
1✔
1829
                        name: "Unsorted".to_string(),
1✔
1830
                        description: "Unsorted Layers".to_string(),
1✔
1831
                        properties: Default::default(),
1✔
1832
                    }),
1✔
1833
                    CollectionItem::Layer(LayerListing {
1✔
1834
                        id: ProviderLayerId {
1✔
1835
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1836
                            layer_id: layer1,
1✔
1837
                        },
1✔
1838
                        name: "Layer1".to_string(),
1✔
1839
                        description: "Layer 1".to_string(),
1✔
1840
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1841
                    })
1✔
1842
                ],
1✔
1843
                entry_label: None,
1✔
1844
                properties: vec![],
1✔
1845
            }
1✔
1846
        );
1✔
1847

1848
        let collection1 = layer_db
1✔
1849
            .load_layer_collection(
1✔
1850
                &collection1_id,
1✔
1851
                LayerCollectionListOptions {
1✔
1852
                    offset: 0,
1✔
1853
                    limit: 20,
1✔
1854
                },
1✔
1855
            )
1✔
1856
            .await
1✔
1857
            .unwrap();
1✔
1858

1✔
1859
        assert_eq!(
1✔
1860
            collection1,
1✔
1861
            LayerCollection {
1✔
1862
                id: ProviderLayerCollectionId {
1✔
1863
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1864
                    collection_id: collection1_id,
1✔
1865
                },
1✔
1866
                name: "Collection1".to_string(),
1✔
1867
                description: "Collection 1".to_string(),
1✔
1868
                items: vec![
1✔
1869
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1870
                        id: ProviderLayerCollectionId {
1✔
1871
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1872
                            collection_id: collection2_id,
1✔
1873
                        },
1✔
1874
                        name: "Collection2".to_string(),
1✔
1875
                        description: "Collection 2".to_string(),
1✔
1876
                        properties: Default::default(),
1✔
1877
                    }),
1✔
1878
                    CollectionItem::Layer(LayerListing {
1✔
1879
                        id: ProviderLayerId {
1✔
1880
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1881
                            layer_id: layer2,
1✔
1882
                        },
1✔
1883
                        name: "Layer2".to_string(),
1✔
1884
                        description: "Layer 2".to_string(),
1✔
1885
                        properties: vec![],
1✔
1886
                    })
1✔
1887
                ],
1✔
1888
                entry_label: None,
1✔
1889
                properties: vec![],
1✔
1890
            }
1✔
1891
        );
1✔
1892
    }
1✔
1893

1894
    #[allow(clippy::too_many_lines)]
1895
    #[ge_context::test]
2✔
1896
    async fn it_searches_layers(app_ctx: ProPostgresContext<NoTls>) {
1✔
1897
        let session = admin_login(&app_ctx).await;
1✔
1898

1899
        let layer_db = app_ctx.session_context(session).db();
1✔
1900

1✔
1901
        let workflow = Workflow {
1✔
1902
            operator: TypedOperator::Vector(
1✔
1903
                MockPointSource {
1✔
1904
                    params: MockPointSourceParams {
1✔
1905
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1906
                    },
1✔
1907
                }
1✔
1908
                .boxed(),
1✔
1909
            ),
1✔
1910
        };
1✔
1911

1912
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1913

1914
        let layer1 = layer_db
1✔
1915
            .add_layer(
1✔
1916
                AddLayer {
1✔
1917
                    name: "Layer1".to_string(),
1✔
1918
                    description: "Layer 1".to_string(),
1✔
1919
                    symbology: None,
1✔
1920
                    workflow: workflow.clone(),
1✔
1921
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1922
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1923
                },
1✔
1924
                &root_collection_id,
1✔
1925
            )
1✔
1926
            .await
1✔
1927
            .unwrap();
1✔
1928

1929
        let collection1_id = layer_db
1✔
1930
            .add_layer_collection(
1✔
1931
                AddLayerCollection {
1✔
1932
                    name: "Collection1".to_string(),
1✔
1933
                    description: "Collection 1".to_string(),
1✔
1934
                    properties: Default::default(),
1✔
1935
                },
1✔
1936
                &root_collection_id,
1✔
1937
            )
1✔
1938
            .await
1✔
1939
            .unwrap();
1✔
1940

1941
        let layer2 = layer_db
1✔
1942
            .add_layer(
1✔
1943
                AddLayer {
1✔
1944
                    name: "Layer2".to_string(),
1✔
1945
                    description: "Layer 2".to_string(),
1✔
1946
                    symbology: None,
1✔
1947
                    workflow: workflow.clone(),
1✔
1948
                    metadata: Default::default(),
1✔
1949
                    properties: Default::default(),
1✔
1950
                },
1✔
1951
                &collection1_id,
1✔
1952
            )
1✔
1953
            .await
1✔
1954
            .unwrap();
1✔
1955

1956
        let collection2_id = layer_db
1✔
1957
            .add_layer_collection(
1✔
1958
                AddLayerCollection {
1✔
1959
                    name: "Collection2".to_string(),
1✔
1960
                    description: "Collection 2".to_string(),
1✔
1961
                    properties: Default::default(),
1✔
1962
                },
1✔
1963
                &collection1_id,
1✔
1964
            )
1✔
1965
            .await
1✔
1966
            .unwrap();
1✔
1967

1968
        let root_collection_all = layer_db
1✔
1969
            .search(
1✔
1970
                &root_collection_id,
1✔
1971
                SearchParameters {
1✔
1972
                    search_type: SearchType::Fulltext,
1✔
1973
                    search_string: String::new(),
1✔
1974
                    limit: 10,
1✔
1975
                    offset: 0,
1✔
1976
                },
1✔
1977
            )
1✔
1978
            .await
1✔
1979
            .unwrap();
1✔
1980

1✔
1981
        assert_eq!(
1✔
1982
            root_collection_all,
1✔
1983
            LayerCollection {
1✔
1984
                id: ProviderLayerCollectionId {
1✔
1985
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1986
                    collection_id: root_collection_id.clone(),
1✔
1987
                },
1✔
1988
                name: "Layers".to_string(),
1✔
1989
                description: "All available Geo Engine layers".to_string(),
1✔
1990
                items: vec![
1✔
1991
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1992
                        id: ProviderLayerCollectionId {
1✔
1993
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1994
                            collection_id: collection1_id.clone(),
1✔
1995
                        },
1✔
1996
                        name: "Collection1".to_string(),
1✔
1997
                        description: "Collection 1".to_string(),
1✔
1998
                        properties: Default::default(),
1✔
1999
                    }),
1✔
2000
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2001
                        id: ProviderLayerCollectionId {
1✔
2002
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2003
                            collection_id: collection2_id.clone(),
1✔
2004
                        },
1✔
2005
                        name: "Collection2".to_string(),
1✔
2006
                        description: "Collection 2".to_string(),
1✔
2007
                        properties: Default::default(),
1✔
2008
                    }),
1✔
2009
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2010
                        id: ProviderLayerCollectionId {
1✔
2011
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2012
                            collection_id: LayerCollectionId(
1✔
2013
                                "ffb2dd9e-f5ad-427c-b7f1-c9a0c7a0ae3f".to_string()
1✔
2014
                            ),
1✔
2015
                        },
1✔
2016
                        name: "Unsorted".to_string(),
1✔
2017
                        description: "Unsorted Layers".to_string(),
1✔
2018
                        properties: Default::default(),
1✔
2019
                    }),
1✔
2020
                    CollectionItem::Layer(LayerListing {
1✔
2021
                        id: ProviderLayerId {
1✔
2022
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2023
                            layer_id: layer1.clone(),
1✔
2024
                        },
1✔
2025
                        name: "Layer1".to_string(),
1✔
2026
                        description: "Layer 1".to_string(),
1✔
2027
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2028
                    }),
1✔
2029
                    CollectionItem::Layer(LayerListing {
1✔
2030
                        id: ProviderLayerId {
1✔
2031
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2032
                            layer_id: layer2.clone(),
1✔
2033
                        },
1✔
2034
                        name: "Layer2".to_string(),
1✔
2035
                        description: "Layer 2".to_string(),
1✔
2036
                        properties: vec![],
1✔
2037
                    }),
1✔
2038
                ],
1✔
2039
                entry_label: None,
1✔
2040
                properties: vec![],
1✔
2041
            }
1✔
2042
        );
1✔
2043

2044
        let root_collection_filtered = layer_db
1✔
2045
            .search(
1✔
2046
                &root_collection_id,
1✔
2047
                SearchParameters {
1✔
2048
                    search_type: SearchType::Fulltext,
1✔
2049
                    search_string: "lection".to_string(),
1✔
2050
                    limit: 10,
1✔
2051
                    offset: 0,
1✔
2052
                },
1✔
2053
            )
1✔
2054
            .await
1✔
2055
            .unwrap();
1✔
2056

1✔
2057
        assert_eq!(
1✔
2058
            root_collection_filtered,
1✔
2059
            LayerCollection {
1✔
2060
                id: ProviderLayerCollectionId {
1✔
2061
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2062
                    collection_id: root_collection_id.clone(),
1✔
2063
                },
1✔
2064
                name: "Layers".to_string(),
1✔
2065
                description: "All available Geo Engine layers".to_string(),
1✔
2066
                items: vec![
1✔
2067
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2068
                        id: ProviderLayerCollectionId {
1✔
2069
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2070
                            collection_id: collection1_id.clone(),
1✔
2071
                        },
1✔
2072
                        name: "Collection1".to_string(),
1✔
2073
                        description: "Collection 1".to_string(),
1✔
2074
                        properties: Default::default(),
1✔
2075
                    }),
1✔
2076
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2077
                        id: ProviderLayerCollectionId {
1✔
2078
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2079
                            collection_id: collection2_id.clone(),
1✔
2080
                        },
1✔
2081
                        name: "Collection2".to_string(),
1✔
2082
                        description: "Collection 2".to_string(),
1✔
2083
                        properties: Default::default(),
1✔
2084
                    }),
1✔
2085
                ],
1✔
2086
                entry_label: None,
1✔
2087
                properties: vec![],
1✔
2088
            }
1✔
2089
        );
1✔
2090

2091
        let collection1_all = layer_db
1✔
2092
            .search(
1✔
2093
                &collection1_id,
1✔
2094
                SearchParameters {
1✔
2095
                    search_type: SearchType::Fulltext,
1✔
2096
                    search_string: String::new(),
1✔
2097
                    limit: 10,
1✔
2098
                    offset: 0,
1✔
2099
                },
1✔
2100
            )
1✔
2101
            .await
1✔
2102
            .unwrap();
1✔
2103

1✔
2104
        assert_eq!(
1✔
2105
            collection1_all,
1✔
2106
            LayerCollection {
1✔
2107
                id: ProviderLayerCollectionId {
1✔
2108
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2109
                    collection_id: collection1_id.clone(),
1✔
2110
                },
1✔
2111
                name: "Collection1".to_string(),
1✔
2112
                description: "Collection 1".to_string(),
1✔
2113
                items: vec![
1✔
2114
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2115
                        id: ProviderLayerCollectionId {
1✔
2116
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2117
                            collection_id: collection2_id.clone(),
1✔
2118
                        },
1✔
2119
                        name: "Collection2".to_string(),
1✔
2120
                        description: "Collection 2".to_string(),
1✔
2121
                        properties: Default::default(),
1✔
2122
                    }),
1✔
2123
                    CollectionItem::Layer(LayerListing {
1✔
2124
                        id: ProviderLayerId {
1✔
2125
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2126
                            layer_id: layer2.clone(),
1✔
2127
                        },
1✔
2128
                        name: "Layer2".to_string(),
1✔
2129
                        description: "Layer 2".to_string(),
1✔
2130
                        properties: vec![],
1✔
2131
                    }),
1✔
2132
                ],
1✔
2133
                entry_label: None,
1✔
2134
                properties: vec![],
1✔
2135
            }
1✔
2136
        );
1✔
2137

2138
        let collection1_filtered_fulltext = layer_db
1✔
2139
            .search(
1✔
2140
                &collection1_id,
1✔
2141
                SearchParameters {
1✔
2142
                    search_type: SearchType::Fulltext,
1✔
2143
                    search_string: "ay".to_string(),
1✔
2144
                    limit: 10,
1✔
2145
                    offset: 0,
1✔
2146
                },
1✔
2147
            )
1✔
2148
            .await
1✔
2149
            .unwrap();
1✔
2150

1✔
2151
        assert_eq!(
1✔
2152
            collection1_filtered_fulltext,
1✔
2153
            LayerCollection {
1✔
2154
                id: ProviderLayerCollectionId {
1✔
2155
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2156
                    collection_id: collection1_id.clone(),
1✔
2157
                },
1✔
2158
                name: "Collection1".to_string(),
1✔
2159
                description: "Collection 1".to_string(),
1✔
2160
                items: vec![CollectionItem::Layer(LayerListing {
1✔
2161
                    id: ProviderLayerId {
1✔
2162
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
2163
                        layer_id: layer2.clone(),
1✔
2164
                    },
1✔
2165
                    name: "Layer2".to_string(),
1✔
2166
                    description: "Layer 2".to_string(),
1✔
2167
                    properties: vec![],
1✔
2168
                }),],
1✔
2169
                entry_label: None,
1✔
2170
                properties: vec![],
1✔
2171
            }
1✔
2172
        );
1✔
2173

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

1✔
2187
        assert_eq!(
1✔
2188
            collection1_filtered_prefix,
1✔
2189
            LayerCollection {
1✔
2190
                id: ProviderLayerCollectionId {
1✔
2191
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2192
                    collection_id: collection1_id.clone(),
1✔
2193
                },
1✔
2194
                name: "Collection1".to_string(),
1✔
2195
                description: "Collection 1".to_string(),
1✔
2196
                items: vec![],
1✔
2197
                entry_label: None,
1✔
2198
                properties: vec![],
1✔
2199
            }
1✔
2200
        );
1✔
2201

2202
        let collection1_filtered_prefix2 = layer_db
1✔
2203
            .search(
1✔
2204
                &collection1_id,
1✔
2205
                SearchParameters {
1✔
2206
                    search_type: SearchType::Prefix,
1✔
2207
                    search_string: "Lay".to_string(),
1✔
2208
                    limit: 10,
1✔
2209
                    offset: 0,
1✔
2210
                },
1✔
2211
            )
1✔
2212
            .await
1✔
2213
            .unwrap();
1✔
2214

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

2239
    #[allow(clippy::too_many_lines)]
2240
    #[ge_context::test]
2✔
2241
    async fn it_searches_layers_with_permissions(app_ctx: ProPostgresContext<NoTls>) {
1✔
2242
        let admin_session = admin_login(&app_ctx).await;
1✔
2243
        let admin_layer_db = app_ctx.session_context(admin_session).db();
1✔
2244

2245
        let user_session = app_ctx.create_anonymous_session().await.unwrap();
1✔
2246
        let user_layer_db = app_ctx.session_context(user_session.clone()).db();
1✔
2247

1✔
2248
        let workflow = Workflow {
1✔
2249
            operator: TypedOperator::Vector(
1✔
2250
                MockPointSource {
1✔
2251
                    params: MockPointSourceParams {
1✔
2252
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2253
                    },
1✔
2254
                }
1✔
2255
                .boxed(),
1✔
2256
            ),
1✔
2257
        };
1✔
2258

2259
        let root_collection_id = admin_layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2260

2261
        let layer1 = admin_layer_db
1✔
2262
            .add_layer(
1✔
2263
                AddLayer {
1✔
2264
                    name: "Layer1".to_string(),
1✔
2265
                    description: "Layer 1".to_string(),
1✔
2266
                    symbology: None,
1✔
2267
                    workflow: workflow.clone(),
1✔
2268
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
2269
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2270
                },
1✔
2271
                &root_collection_id,
1✔
2272
            )
1✔
2273
            .await
1✔
2274
            .unwrap();
1✔
2275

2276
        let collection1_id = admin_layer_db
1✔
2277
            .add_layer_collection(
1✔
2278
                AddLayerCollection {
1✔
2279
                    name: "Collection1".to_string(),
1✔
2280
                    description: "Collection 1".to_string(),
1✔
2281
                    properties: Default::default(),
1✔
2282
                },
1✔
2283
                &root_collection_id,
1✔
2284
            )
1✔
2285
            .await
1✔
2286
            .unwrap();
1✔
2287

2288
        let layer2 = admin_layer_db
1✔
2289
            .add_layer(
1✔
2290
                AddLayer {
1✔
2291
                    name: "Layer2".to_string(),
1✔
2292
                    description: "Layer 2".to_string(),
1✔
2293
                    symbology: None,
1✔
2294
                    workflow: workflow.clone(),
1✔
2295
                    metadata: Default::default(),
1✔
2296
                    properties: Default::default(),
1✔
2297
                },
1✔
2298
                &collection1_id,
1✔
2299
            )
1✔
2300
            .await
1✔
2301
            .unwrap();
1✔
2302

2303
        let collection2_id = admin_layer_db
1✔
2304
            .add_layer_collection(
1✔
2305
                AddLayerCollection {
1✔
2306
                    name: "Collection2".to_string(),
1✔
2307
                    description: "Collection 2".to_string(),
1✔
2308
                    properties: Default::default(),
1✔
2309
                },
1✔
2310
                &collection1_id,
1✔
2311
            )
1✔
2312
            .await
1✔
2313
            .unwrap();
1✔
2314

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

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

1✔
2342
        // Grant user permissions for collection1, collection2, layer1 and layer2
1✔
2343
        admin_layer_db
1✔
2344
            .add_permission(
1✔
2345
                user_session.user.id.into(),
1✔
2346
                collection1_id.clone(),
1✔
2347
                Permission::Read,
1✔
2348
            )
1✔
2349
            .await
1✔
2350
            .unwrap();
1✔
2351

1✔
2352
        admin_layer_db
1✔
2353
            .add_permission(
1✔
2354
                user_session.user.id.into(),
1✔
2355
                collection2_id.clone(),
1✔
2356
                Permission::Read,
1✔
2357
            )
1✔
2358
            .await
1✔
2359
            .unwrap();
1✔
2360

1✔
2361
        admin_layer_db
1✔
2362
            .add_permission(
1✔
2363
                user_session.user.id.into(),
1✔
2364
                layer1.clone(),
1✔
2365
                Permission::Read,
1✔
2366
            )
1✔
2367
            .await
1✔
2368
            .unwrap();
1✔
2369

1✔
2370
        admin_layer_db
1✔
2371
            .add_permission(
1✔
2372
                user_session.user.id.into(),
1✔
2373
                layer2.clone(),
1✔
2374
                Permission::Read,
1✔
2375
            )
1✔
2376
            .await
1✔
2377
            .unwrap();
1✔
2378

2379
        // Ensure admin sees everything we added
2380
        let admin_root_collection_all = admin_layer_db
1✔
2381
            .search(
1✔
2382
                &root_collection_id,
1✔
2383
                SearchParameters {
1✔
2384
                    search_type: SearchType::Fulltext,
1✔
2385
                    search_string: String::new(),
1✔
2386
                    limit: 10,
1✔
2387
                    offset: 0,
1✔
2388
                },
1✔
2389
            )
1✔
2390
            .await
1✔
2391
            .unwrap();
1✔
2392

1✔
2393
        assert_eq!(
1✔
2394
            admin_root_collection_all,
1✔
2395
            LayerCollection {
1✔
2396
                id: ProviderLayerCollectionId {
1✔
2397
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2398
                    collection_id: root_collection_id.clone(),
1✔
2399
                },
1✔
2400
                name: "Layers".to_string(),
1✔
2401
                description: "All available Geo Engine layers".to_string(),
1✔
2402
                items: vec![
1✔
2403
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2404
                        id: ProviderLayerCollectionId {
1✔
2405
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2406
                            collection_id: collection1_id.clone(),
1✔
2407
                        },
1✔
2408
                        name: "Collection1".to_string(),
1✔
2409
                        description: "Collection 1".to_string(),
1✔
2410
                        properties: Default::default(),
1✔
2411
                    }),
1✔
2412
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2413
                        id: ProviderLayerCollectionId {
1✔
2414
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2415
                            collection_id: collection2_id.clone(),
1✔
2416
                        },
1✔
2417
                        name: "Collection2".to_string(),
1✔
2418
                        description: "Collection 2".to_string(),
1✔
2419
                        properties: Default::default(),
1✔
2420
                    }),
1✔
2421
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2422
                        id: ProviderLayerCollectionId {
1✔
2423
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2424
                            collection_id: collection3_id.clone(),
1✔
2425
                        },
1✔
2426
                        name: "Collection3".to_string(),
1✔
2427
                        description: "Collection 3".to_string(),
1✔
2428
                        properties: Default::default(),
1✔
2429
                    }),
1✔
2430
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2431
                        id: ProviderLayerCollectionId {
1✔
2432
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2433
                            collection_id: LayerCollectionId(
1✔
2434
                                "ffb2dd9e-f5ad-427c-b7f1-c9a0c7a0ae3f".to_string()
1✔
2435
                            ),
1✔
2436
                        },
1✔
2437
                        name: "Unsorted".to_string(),
1✔
2438
                        description: "Unsorted Layers".to_string(),
1✔
2439
                        properties: Default::default(),
1✔
2440
                    }),
1✔
2441
                    CollectionItem::Layer(LayerListing {
1✔
2442
                        id: ProviderLayerId {
1✔
2443
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2444
                            layer_id: layer1.clone(),
1✔
2445
                        },
1✔
2446
                        name: "Layer1".to_string(),
1✔
2447
                        description: "Layer 1".to_string(),
1✔
2448
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2449
                    }),
1✔
2450
                    CollectionItem::Layer(LayerListing {
1✔
2451
                        id: ProviderLayerId {
1✔
2452
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2453
                            layer_id: layer2.clone(),
1✔
2454
                        },
1✔
2455
                        name: "Layer2".to_string(),
1✔
2456
                        description: "Layer 2".to_string(),
1✔
2457
                        properties: vec![],
1✔
2458
                    }),
1✔
2459
                    CollectionItem::Layer(LayerListing {
1✔
2460
                        id: ProviderLayerId {
1✔
2461
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2462
                            layer_id: layer3.clone(),
1✔
2463
                        },
1✔
2464
                        name: "Layer3".to_string(),
1✔
2465
                        description: "Layer 3".to_string(),
1✔
2466
                        properties: vec![],
1✔
2467
                    }),
1✔
2468
                ],
1✔
2469
                entry_label: None,
1✔
2470
                properties: vec![],
1✔
2471
            }
1✔
2472
        );
1✔
2473

2474
        let root_collection_all = user_layer_db
1✔
2475
            .search(
1✔
2476
                &root_collection_id,
1✔
2477
                SearchParameters {
1✔
2478
                    search_type: SearchType::Fulltext,
1✔
2479
                    search_string: String::new(),
1✔
2480
                    limit: 10,
1✔
2481
                    offset: 0,
1✔
2482
                },
1✔
2483
            )
1✔
2484
            .await
1✔
2485
            .unwrap();
1✔
2486

1✔
2487
        assert_eq!(
1✔
2488
            root_collection_all,
1✔
2489
            LayerCollection {
1✔
2490
                id: ProviderLayerCollectionId {
1✔
2491
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2492
                    collection_id: root_collection_id.clone(),
1✔
2493
                },
1✔
2494
                name: "Layers".to_string(),
1✔
2495
                description: "All available Geo Engine layers".to_string(),
1✔
2496
                items: vec![
1✔
2497
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2498
                        id: ProviderLayerCollectionId {
1✔
2499
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2500
                            collection_id: collection1_id.clone(),
1✔
2501
                        },
1✔
2502
                        name: "Collection1".to_string(),
1✔
2503
                        description: "Collection 1".to_string(),
1✔
2504
                        properties: Default::default(),
1✔
2505
                    }),
1✔
2506
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2507
                        id: ProviderLayerCollectionId {
1✔
2508
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2509
                            collection_id: collection2_id.clone(),
1✔
2510
                        },
1✔
2511
                        name: "Collection2".to_string(),
1✔
2512
                        description: "Collection 2".to_string(),
1✔
2513
                        properties: Default::default(),
1✔
2514
                    }),
1✔
2515
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2516
                        id: ProviderLayerCollectionId {
1✔
2517
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2518
                            collection_id: LayerCollectionId(
1✔
2519
                                "ffb2dd9e-f5ad-427c-b7f1-c9a0c7a0ae3f".to_string()
1✔
2520
                            ),
1✔
2521
                        },
1✔
2522
                        name: "Unsorted".to_string(),
1✔
2523
                        description: "Unsorted Layers".to_string(),
1✔
2524
                        properties: Default::default(),
1✔
2525
                    }),
1✔
2526
                    CollectionItem::Layer(LayerListing {
1✔
2527
                        id: ProviderLayerId {
1✔
2528
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2529
                            layer_id: layer1.clone(),
1✔
2530
                        },
1✔
2531
                        name: "Layer1".to_string(),
1✔
2532
                        description: "Layer 1".to_string(),
1✔
2533
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2534
                    }),
1✔
2535
                    CollectionItem::Layer(LayerListing {
1✔
2536
                        id: ProviderLayerId {
1✔
2537
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2538
                            layer_id: layer2.clone(),
1✔
2539
                        },
1✔
2540
                        name: "Layer2".to_string(),
1✔
2541
                        description: "Layer 2".to_string(),
1✔
2542
                        properties: vec![],
1✔
2543
                    }),
1✔
2544
                ],
1✔
2545
                entry_label: None,
1✔
2546
                properties: vec![],
1✔
2547
            }
1✔
2548
        );
1✔
2549

2550
        let root_collection_filtered = user_layer_db
1✔
2551
            .search(
1✔
2552
                &root_collection_id,
1✔
2553
                SearchParameters {
1✔
2554
                    search_type: SearchType::Fulltext,
1✔
2555
                    search_string: "lection".to_string(),
1✔
2556
                    limit: 10,
1✔
2557
                    offset: 0,
1✔
2558
                },
1✔
2559
            )
1✔
2560
            .await
1✔
2561
            .unwrap();
1✔
2562

1✔
2563
        assert_eq!(
1✔
2564
            root_collection_filtered,
1✔
2565
            LayerCollection {
1✔
2566
                id: ProviderLayerCollectionId {
1✔
2567
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2568
                    collection_id: root_collection_id.clone(),
1✔
2569
                },
1✔
2570
                name: "Layers".to_string(),
1✔
2571
                description: "All available Geo Engine layers".to_string(),
1✔
2572
                items: vec![
1✔
2573
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2574
                        id: ProviderLayerCollectionId {
1✔
2575
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2576
                            collection_id: collection1_id.clone(),
1✔
2577
                        },
1✔
2578
                        name: "Collection1".to_string(),
1✔
2579
                        description: "Collection 1".to_string(),
1✔
2580
                        properties: Default::default(),
1✔
2581
                    }),
1✔
2582
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2583
                        id: ProviderLayerCollectionId {
1✔
2584
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2585
                            collection_id: collection2_id.clone(),
1✔
2586
                        },
1✔
2587
                        name: "Collection2".to_string(),
1✔
2588
                        description: "Collection 2".to_string(),
1✔
2589
                        properties: Default::default(),
1✔
2590
                    }),
1✔
2591
                ],
1✔
2592
                entry_label: None,
1✔
2593
                properties: vec![],
1✔
2594
            }
1✔
2595
        );
1✔
2596

2597
        let collection1_all = user_layer_db
1✔
2598
            .search(
1✔
2599
                &collection1_id,
1✔
2600
                SearchParameters {
1✔
2601
                    search_type: SearchType::Fulltext,
1✔
2602
                    search_string: String::new(),
1✔
2603
                    limit: 10,
1✔
2604
                    offset: 0,
1✔
2605
                },
1✔
2606
            )
1✔
2607
            .await
1✔
2608
            .unwrap();
1✔
2609

1✔
2610
        assert_eq!(
1✔
2611
            collection1_all,
1✔
2612
            LayerCollection {
1✔
2613
                id: ProviderLayerCollectionId {
1✔
2614
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2615
                    collection_id: collection1_id.clone(),
1✔
2616
                },
1✔
2617
                name: "Collection1".to_string(),
1✔
2618
                description: "Collection 1".to_string(),
1✔
2619
                items: vec![
1✔
2620
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2621
                        id: ProviderLayerCollectionId {
1✔
2622
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2623
                            collection_id: collection2_id.clone(),
1✔
2624
                        },
1✔
2625
                        name: "Collection2".to_string(),
1✔
2626
                        description: "Collection 2".to_string(),
1✔
2627
                        properties: Default::default(),
1✔
2628
                    }),
1✔
2629
                    CollectionItem::Layer(LayerListing {
1✔
2630
                        id: ProviderLayerId {
1✔
2631
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2632
                            layer_id: layer2.clone(),
1✔
2633
                        },
1✔
2634
                        name: "Layer2".to_string(),
1✔
2635
                        description: "Layer 2".to_string(),
1✔
2636
                        properties: vec![],
1✔
2637
                    }),
1✔
2638
                ],
1✔
2639
                entry_label: None,
1✔
2640
                properties: vec![],
1✔
2641
            }
1✔
2642
        );
1✔
2643

2644
        let collection1_filtered_fulltext = user_layer_db
1✔
2645
            .search(
1✔
2646
                &collection1_id,
1✔
2647
                SearchParameters {
1✔
2648
                    search_type: SearchType::Fulltext,
1✔
2649
                    search_string: "ay".to_string(),
1✔
2650
                    limit: 10,
1✔
2651
                    offset: 0,
1✔
2652
                },
1✔
2653
            )
1✔
2654
            .await
1✔
2655
            .unwrap();
1✔
2656

1✔
2657
        assert_eq!(
1✔
2658
            collection1_filtered_fulltext,
1✔
2659
            LayerCollection {
1✔
2660
                id: ProviderLayerCollectionId {
1✔
2661
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2662
                    collection_id: collection1_id.clone(),
1✔
2663
                },
1✔
2664
                name: "Collection1".to_string(),
1✔
2665
                description: "Collection 1".to_string(),
1✔
2666
                items: vec![CollectionItem::Layer(LayerListing {
1✔
2667
                    id: ProviderLayerId {
1✔
2668
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
2669
                        layer_id: layer2.clone(),
1✔
2670
                    },
1✔
2671
                    name: "Layer2".to_string(),
1✔
2672
                    description: "Layer 2".to_string(),
1✔
2673
                    properties: vec![],
1✔
2674
                }),],
1✔
2675
                entry_label: None,
1✔
2676
                properties: vec![],
1✔
2677
            }
1✔
2678
        );
1✔
2679

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

1✔
2693
        assert_eq!(
1✔
2694
            collection1_filtered_prefix,
1✔
2695
            LayerCollection {
1✔
2696
                id: ProviderLayerCollectionId {
1✔
2697
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2698
                    collection_id: collection1_id.clone(),
1✔
2699
                },
1✔
2700
                name: "Collection1".to_string(),
1✔
2701
                description: "Collection 1".to_string(),
1✔
2702
                items: vec![],
1✔
2703
                entry_label: None,
1✔
2704
                properties: vec![],
1✔
2705
            }
1✔
2706
        );
1✔
2707

2708
        let collection1_filtered_prefix2 = user_layer_db
1✔
2709
            .search(
1✔
2710
                &collection1_id,
1✔
2711
                SearchParameters {
1✔
2712
                    search_type: SearchType::Prefix,
1✔
2713
                    search_string: "Lay".to_string(),
1✔
2714
                    limit: 10,
1✔
2715
                    offset: 0,
1✔
2716
                },
1✔
2717
            )
1✔
2718
            .await
1✔
2719
            .unwrap();
1✔
2720

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

2745
    #[allow(clippy::too_many_lines)]
2746
    #[ge_context::test]
2✔
2747
    async fn it_autocompletes_layers(app_ctx: ProPostgresContext<NoTls>) {
1✔
2748
        let session = admin_login(&app_ctx).await;
1✔
2749

2750
        let layer_db = app_ctx.session_context(session).db();
1✔
2751

1✔
2752
        let workflow = Workflow {
1✔
2753
            operator: TypedOperator::Vector(
1✔
2754
                MockPointSource {
1✔
2755
                    params: MockPointSourceParams {
1✔
2756
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2757
                    },
1✔
2758
                }
1✔
2759
                .boxed(),
1✔
2760
            ),
1✔
2761
        };
1✔
2762

2763
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2764

2765
        let _layer1 = layer_db
1✔
2766
            .add_layer(
1✔
2767
                AddLayer {
1✔
2768
                    name: "Layer1".to_string(),
1✔
2769
                    description: "Layer 1".to_string(),
1✔
2770
                    symbology: None,
1✔
2771
                    workflow: workflow.clone(),
1✔
2772
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
2773
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2774
                },
1✔
2775
                &root_collection_id,
1✔
2776
            )
1✔
2777
            .await
1✔
2778
            .unwrap();
1✔
2779

2780
        let collection1_id = layer_db
1✔
2781
            .add_layer_collection(
1✔
2782
                AddLayerCollection {
1✔
2783
                    name: "Collection1".to_string(),
1✔
2784
                    description: "Collection 1".to_string(),
1✔
2785
                    properties: Default::default(),
1✔
2786
                },
1✔
2787
                &root_collection_id,
1✔
2788
            )
1✔
2789
            .await
1✔
2790
            .unwrap();
1✔
2791

2792
        let _layer2 = layer_db
1✔
2793
            .add_layer(
1✔
2794
                AddLayer {
1✔
2795
                    name: "Layer2".to_string(),
1✔
2796
                    description: "Layer 2".to_string(),
1✔
2797
                    symbology: None,
1✔
2798
                    workflow: workflow.clone(),
1✔
2799
                    metadata: Default::default(),
1✔
2800
                    properties: Default::default(),
1✔
2801
                },
1✔
2802
                &collection1_id,
1✔
2803
            )
1✔
2804
            .await
1✔
2805
            .unwrap();
1✔
2806

2807
        let _collection2_id = layer_db
1✔
2808
            .add_layer_collection(
1✔
2809
                AddLayerCollection {
1✔
2810
                    name: "Collection2".to_string(),
1✔
2811
                    description: "Collection 2".to_string(),
1✔
2812
                    properties: Default::default(),
1✔
2813
                },
1✔
2814
                &collection1_id,
1✔
2815
            )
1✔
2816
            .await
1✔
2817
            .unwrap();
1✔
2818

2819
        let root_collection_all = layer_db
1✔
2820
            .autocomplete_search(
1✔
2821
                &root_collection_id,
1✔
2822
                SearchParameters {
1✔
2823
                    search_type: SearchType::Fulltext,
1✔
2824
                    search_string: String::new(),
1✔
2825
                    limit: 10,
1✔
2826
                    offset: 0,
1✔
2827
                },
1✔
2828
            )
1✔
2829
            .await
1✔
2830
            .unwrap();
1✔
2831

1✔
2832
        assert_eq!(
1✔
2833
            root_collection_all,
1✔
2834
            vec![
1✔
2835
                "Collection1".to_string(),
1✔
2836
                "Collection2".to_string(),
1✔
2837
                "Layer1".to_string(),
1✔
2838
                "Layer2".to_string(),
1✔
2839
                "Unsorted".to_string(),
1✔
2840
            ]
1✔
2841
        );
1✔
2842

2843
        let root_collection_filtered = layer_db
1✔
2844
            .autocomplete_search(
1✔
2845
                &root_collection_id,
1✔
2846
                SearchParameters {
1✔
2847
                    search_type: SearchType::Fulltext,
1✔
2848
                    search_string: "lection".to_string(),
1✔
2849
                    limit: 10,
1✔
2850
                    offset: 0,
1✔
2851
                },
1✔
2852
            )
1✔
2853
            .await
1✔
2854
            .unwrap();
1✔
2855

1✔
2856
        assert_eq!(
1✔
2857
            root_collection_filtered,
1✔
2858
            vec!["Collection1".to_string(), "Collection2".to_string(),]
1✔
2859
        );
1✔
2860

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

1✔
2874
        assert_eq!(
1✔
2875
            collection1_all,
1✔
2876
            vec!["Collection2".to_string(), "Layer2".to_string(),]
1✔
2877
        );
1✔
2878

2879
        let collection1_filtered_fulltext = layer_db
1✔
2880
            .autocomplete_search(
1✔
2881
                &collection1_id,
1✔
2882
                SearchParameters {
1✔
2883
                    search_type: SearchType::Fulltext,
1✔
2884
                    search_string: "ay".to_string(),
1✔
2885
                    limit: 10,
1✔
2886
                    offset: 0,
1✔
2887
                },
1✔
2888
            )
1✔
2889
            .await
1✔
2890
            .unwrap();
1✔
2891

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

2894
        let collection1_filtered_prefix = layer_db
1✔
2895
            .autocomplete_search(
1✔
2896
                &collection1_id,
1✔
2897
                SearchParameters {
1✔
2898
                    search_type: SearchType::Prefix,
1✔
2899
                    search_string: "ay".to_string(),
1✔
2900
                    limit: 10,
1✔
2901
                    offset: 0,
1✔
2902
                },
1✔
2903
            )
1✔
2904
            .await
1✔
2905
            .unwrap();
1✔
2906

1✔
2907
        assert_eq!(collection1_filtered_prefix, Vec::<String>::new());
1✔
2908

2909
        let collection1_filtered_prefix2 = layer_db
1✔
2910
            .autocomplete_search(
1✔
2911
                &collection1_id,
1✔
2912
                SearchParameters {
1✔
2913
                    search_type: SearchType::Prefix,
1✔
2914
                    search_string: "Lay".to_string(),
1✔
2915
                    limit: 10,
1✔
2916
                    offset: 0,
1✔
2917
                },
1✔
2918
            )
1✔
2919
            .await
1✔
2920
            .unwrap();
1✔
2921

1✔
2922
        assert_eq!(collection1_filtered_prefix2, vec!["Layer2".to_string(),]);
1✔
2923
    }
1✔
2924

2925
    #[allow(clippy::too_many_lines)]
2926
    #[ge_context::test]
2✔
2927
    async fn it_autocompletes_layers_with_permissions(app_ctx: ProPostgresContext<NoTls>) {
1✔
2928
        let admin_session = admin_login(&app_ctx).await;
1✔
2929
        let admin_layer_db = app_ctx.session_context(admin_session).db();
1✔
2930

2931
        let user_session = app_ctx.create_anonymous_session().await.unwrap();
1✔
2932
        let user_layer_db = app_ctx.session_context(user_session.clone()).db();
1✔
2933

1✔
2934
        let workflow = Workflow {
1✔
2935
            operator: TypedOperator::Vector(
1✔
2936
                MockPointSource {
1✔
2937
                    params: MockPointSourceParams {
1✔
2938
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2939
                    },
1✔
2940
                }
1✔
2941
                .boxed(),
1✔
2942
            ),
1✔
2943
        };
1✔
2944

2945
        let root_collection_id = admin_layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2946

2947
        let layer1 = admin_layer_db
1✔
2948
            .add_layer(
1✔
2949
                AddLayer {
1✔
2950
                    name: "Layer1".to_string(),
1✔
2951
                    description: "Layer 1".to_string(),
1✔
2952
                    symbology: None,
1✔
2953
                    workflow: workflow.clone(),
1✔
2954
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
2955
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2956
                },
1✔
2957
                &root_collection_id,
1✔
2958
            )
1✔
2959
            .await
1✔
2960
            .unwrap();
1✔
2961

2962
        let collection1_id = admin_layer_db
1✔
2963
            .add_layer_collection(
1✔
2964
                AddLayerCollection {
1✔
2965
                    name: "Collection1".to_string(),
1✔
2966
                    description: "Collection 1".to_string(),
1✔
2967
                    properties: Default::default(),
1✔
2968
                },
1✔
2969
                &root_collection_id,
1✔
2970
            )
1✔
2971
            .await
1✔
2972
            .unwrap();
1✔
2973

2974
        let layer2 = admin_layer_db
1✔
2975
            .add_layer(
1✔
2976
                AddLayer {
1✔
2977
                    name: "Layer2".to_string(),
1✔
2978
                    description: "Layer 2".to_string(),
1✔
2979
                    symbology: None,
1✔
2980
                    workflow: workflow.clone(),
1✔
2981
                    metadata: Default::default(),
1✔
2982
                    properties: Default::default(),
1✔
2983
                },
1✔
2984
                &collection1_id,
1✔
2985
            )
1✔
2986
            .await
1✔
2987
            .unwrap();
1✔
2988

2989
        let collection2_id = admin_layer_db
1✔
2990
            .add_layer_collection(
1✔
2991
                AddLayerCollection {
1✔
2992
                    name: "Collection2".to_string(),
1✔
2993
                    description: "Collection 2".to_string(),
1✔
2994
                    properties: Default::default(),
1✔
2995
                },
1✔
2996
                &collection1_id,
1✔
2997
            )
1✔
2998
            .await
1✔
2999
            .unwrap();
1✔
3000

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

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

1✔
3028
        // Grant user permissions for collection1, collection2, layer1 and layer2
1✔
3029
        admin_layer_db
1✔
3030
            .add_permission(
1✔
3031
                user_session.user.id.into(),
1✔
3032
                collection1_id.clone(),
1✔
3033
                Permission::Read,
1✔
3034
            )
1✔
3035
            .await
1✔
3036
            .unwrap();
1✔
3037

1✔
3038
        admin_layer_db
1✔
3039
            .add_permission(
1✔
3040
                user_session.user.id.into(),
1✔
3041
                collection2_id.clone(),
1✔
3042
                Permission::Read,
1✔
3043
            )
1✔
3044
            .await
1✔
3045
            .unwrap();
1✔
3046

1✔
3047
        admin_layer_db
1✔
3048
            .add_permission(
1✔
3049
                user_session.user.id.into(),
1✔
3050
                layer1.clone(),
1✔
3051
                Permission::Read,
1✔
3052
            )
1✔
3053
            .await
1✔
3054
            .unwrap();
1✔
3055

1✔
3056
        admin_layer_db
1✔
3057
            .add_permission(
1✔
3058
                user_session.user.id.into(),
1✔
3059
                layer2.clone(),
1✔
3060
                Permission::Read,
1✔
3061
            )
1✔
3062
            .await
1✔
3063
            .unwrap();
1✔
3064

3065
        // Ensure admin sees everything we added
3066
        let admin_root_collection_all = admin_layer_db
1✔
3067
            .autocomplete_search(
1✔
3068
                &root_collection_id,
1✔
3069
                SearchParameters {
1✔
3070
                    search_type: SearchType::Fulltext,
1✔
3071
                    search_string: String::new(),
1✔
3072
                    limit: 10,
1✔
3073
                    offset: 0,
1✔
3074
                },
1✔
3075
            )
1✔
3076
            .await
1✔
3077
            .unwrap();
1✔
3078

1✔
3079
        assert_eq!(
1✔
3080
            admin_root_collection_all,
1✔
3081
            vec![
1✔
3082
                "Collection1".to_string(),
1✔
3083
                "Collection2".to_string(),
1✔
3084
                "Collection3".to_string(),
1✔
3085
                "Layer1".to_string(),
1✔
3086
                "Layer2".to_string(),
1✔
3087
                "Layer3".to_string(),
1✔
3088
                "Unsorted".to_string(),
1✔
3089
            ]
1✔
3090
        );
1✔
3091

3092
        let root_collection_all = user_layer_db
1✔
3093
            .autocomplete_search(
1✔
3094
                &root_collection_id,
1✔
3095
                SearchParameters {
1✔
3096
                    search_type: SearchType::Fulltext,
1✔
3097
                    search_string: String::new(),
1✔
3098
                    limit: 10,
1✔
3099
                    offset: 0,
1✔
3100
                },
1✔
3101
            )
1✔
3102
            .await
1✔
3103
            .unwrap();
1✔
3104

1✔
3105
        assert_eq!(
1✔
3106
            root_collection_all,
1✔
3107
            vec![
1✔
3108
                "Collection1".to_string(),
1✔
3109
                "Collection2".to_string(),
1✔
3110
                "Layer1".to_string(),
1✔
3111
                "Layer2".to_string(),
1✔
3112
                "Unsorted".to_string(),
1✔
3113
            ]
1✔
3114
        );
1✔
3115

3116
        let root_collection_filtered = user_layer_db
1✔
3117
            .autocomplete_search(
1✔
3118
                &root_collection_id,
1✔
3119
                SearchParameters {
1✔
3120
                    search_type: SearchType::Fulltext,
1✔
3121
                    search_string: "lection".to_string(),
1✔
3122
                    limit: 10,
1✔
3123
                    offset: 0,
1✔
3124
                },
1✔
3125
            )
1✔
3126
            .await
1✔
3127
            .unwrap();
1✔
3128

1✔
3129
        assert_eq!(
1✔
3130
            root_collection_filtered,
1✔
3131
            vec!["Collection1".to_string(), "Collection2".to_string(),]
1✔
3132
        );
1✔
3133

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

1✔
3147
        assert_eq!(
1✔
3148
            collection1_all,
1✔
3149
            vec!["Collection2".to_string(), "Layer2".to_string(),]
1✔
3150
        );
1✔
3151

3152
        let collection1_filtered_fulltext = user_layer_db
1✔
3153
            .autocomplete_search(
1✔
3154
                &collection1_id,
1✔
3155
                SearchParameters {
1✔
3156
                    search_type: SearchType::Fulltext,
1✔
3157
                    search_string: "ay".to_string(),
1✔
3158
                    limit: 10,
1✔
3159
                    offset: 0,
1✔
3160
                },
1✔
3161
            )
1✔
3162
            .await
1✔
3163
            .unwrap();
1✔
3164

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

3167
        let collection1_filtered_prefix = user_layer_db
1✔
3168
            .autocomplete_search(
1✔
3169
                &collection1_id,
1✔
3170
                SearchParameters {
1✔
3171
                    search_type: SearchType::Prefix,
1✔
3172
                    search_string: "ay".to_string(),
1✔
3173
                    limit: 10,
1✔
3174
                    offset: 0,
1✔
3175
                },
1✔
3176
            )
1✔
3177
            .await
1✔
3178
            .unwrap();
1✔
3179

1✔
3180
        assert_eq!(collection1_filtered_prefix, Vec::<String>::new());
1✔
3181

3182
        let collection1_filtered_prefix2 = user_layer_db
1✔
3183
            .autocomplete_search(
1✔
3184
                &collection1_id,
1✔
3185
                SearchParameters {
1✔
3186
                    search_type: SearchType::Prefix,
1✔
3187
                    search_string: "Lay".to_string(),
1✔
3188
                    limit: 10,
1✔
3189
                    offset: 0,
1✔
3190
                },
1✔
3191
            )
1✔
3192
            .await
1✔
3193
            .unwrap();
1✔
3194

1✔
3195
        assert_eq!(collection1_filtered_prefix2, vec!["Layer2".to_string(),]);
1✔
3196
    }
1✔
3197

3198
    #[allow(clippy::too_many_lines)]
3199
    #[ge_context::test]
2✔
3200
    async fn it_reports_search_capabilities(app_ctx: ProPostgresContext<NoTls>) {
1✔
3201
        let session = admin_login(&app_ctx).await;
1✔
3202

3203
        let layer_db = app_ctx.session_context(session).db();
1✔
3204

1✔
3205
        let capabilities = layer_db.capabilities().search;
1✔
3206

3207
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
3208

1✔
3209
        if capabilities.search_types.fulltext {
1✔
3210
            assert!(layer_db
1✔
3211
                .search(
1✔
3212
                    &root_collection_id,
1✔
3213
                    SearchParameters {
1✔
3214
                        search_type: SearchType::Fulltext,
1✔
3215
                        search_string: String::new(),
1✔
3216
                        limit: 10,
1✔
3217
                        offset: 0,
1✔
3218
                    },
1✔
3219
                )
1✔
3220
                .await
1✔
3221
                .is_ok());
1✔
3222

3223
            if capabilities.autocomplete {
1✔
3224
                assert!(layer_db
1✔
3225
                    .autocomplete_search(
1✔
3226
                        &root_collection_id,
1✔
3227
                        SearchParameters {
1✔
3228
                            search_type: SearchType::Fulltext,
1✔
3229
                            search_string: String::new(),
1✔
3230
                            limit: 10,
1✔
3231
                            offset: 0,
1✔
3232
                        },
1✔
3233
                    )
1✔
3234
                    .await
1✔
3235
                    .is_ok());
1✔
3236
            } else {
3237
                assert!(layer_db
×
3238
                    .autocomplete_search(
×
3239
                        &root_collection_id,
×
3240
                        SearchParameters {
×
3241
                            search_type: SearchType::Fulltext,
×
3242
                            search_string: String::new(),
×
3243
                            limit: 10,
×
3244
                            offset: 0,
×
3245
                        },
×
3246
                    )
×
3247
                    .await
×
3248
                    .is_err());
×
3249
            }
3250
        }
×
3251
        if capabilities.search_types.prefix {
1✔
3252
            assert!(layer_db
1✔
3253
                .search(
1✔
3254
                    &root_collection_id,
1✔
3255
                    SearchParameters {
1✔
3256
                        search_type: SearchType::Prefix,
1✔
3257
                        search_string: String::new(),
1✔
3258
                        limit: 10,
1✔
3259
                        offset: 0,
1✔
3260
                    },
1✔
3261
                )
1✔
3262
                .await
1✔
3263
                .is_ok());
1✔
3264

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

3295
    #[ge_context::test]
2✔
3296
    async fn it_tracks_used_quota_in_postgres(app_ctx: ProPostgresContext<NoTls>) {
1✔
3297
        let _user = app_ctx
1✔
3298
            .register_user(UserRegistration {
1✔
3299
                email: "foo@example.com".to_string(),
1✔
3300
                password: "secret1234".to_string(),
1✔
3301
                real_name: "Foo Bar".to_string(),
1✔
3302
            })
1✔
3303
            .await
1✔
3304
            .unwrap();
1✔
3305

3306
        let session = app_ctx
1✔
3307
            .login(UserCredentials {
1✔
3308
                email: "foo@example.com".to_string(),
1✔
3309
                password: "secret1234".to_string(),
1✔
3310
            })
1✔
3311
            .await
1✔
3312
            .unwrap();
1✔
3313

3314
        let admin_session = admin_login(&app_ctx).await;
1✔
3315

3316
        let quota = initialize_quota_tracking(
1✔
3317
            QuotaTrackingMode::Check,
1✔
3318
            app_ctx.session_context(admin_session).db(),
1✔
3319
            0,
1✔
3320
            60,
1✔
3321
        );
1✔
3322

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

1✔
3325
        tracking.mock_work_unit_done();
1✔
3326
        tracking.mock_work_unit_done();
1✔
3327

1✔
3328
        let db = app_ctx.session_context(session).db();
1✔
3329

1✔
3330
        // wait for quota to be recorded
1✔
3331
        let mut success = false;
1✔
3332
        for _ in 0..10 {
1✔
3333
            let used = db.quota_used().await.unwrap();
1✔
3334
            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
1✔
3335

3336
            if used == 2 {
1✔
3337
                success = true;
1✔
3338
                break;
1✔
UNCOV
3339
            }
×
3340
        }
3341

3342
        assert!(success);
1✔
3343
    }
1✔
3344

3345
    #[ge_context::test]
2✔
3346
    async fn it_tracks_available_quota(app_ctx: ProPostgresContext<NoTls>) {
1✔
3347
        let user = app_ctx
1✔
3348
            .register_user(UserRegistration {
1✔
3349
                email: "foo@example.com".to_string(),
1✔
3350
                password: "secret1234".to_string(),
1✔
3351
                real_name: "Foo Bar".to_string(),
1✔
3352
            })
1✔
3353
            .await
1✔
3354
            .unwrap();
1✔
3355

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

3364
        let admin_session = admin_login(&app_ctx).await;
1✔
3365

3366
        app_ctx
1✔
3367
            .session_context(admin_session.clone())
1✔
3368
            .db()
1✔
3369
            .update_quota_available_by_user(&user, 1)
1✔
3370
            .await
1✔
3371
            .unwrap();
1✔
3372

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

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

1✔
3382
        tracking.mock_work_unit_done();
1✔
3383
        tracking.mock_work_unit_done();
1✔
3384

1✔
3385
        let db = app_ctx.session_context(session).db();
1✔
3386

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

3393
            if available == -1 {
2✔
3394
                success = true;
1✔
3395
                break;
1✔
3396
            }
1✔
3397
        }
3398

3399
        assert!(success);
1✔
3400
    }
1✔
3401

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

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

1✔
3421
        let db = app_ctx.session_context(session.clone()).db();
1✔
3422
        let admin_db = app_ctx.session_context(UserSession::admin_session()).db();
1✔
3423

3424
        assert_eq!(
1✔
3425
            db.quota_available().await.unwrap(),
1✔
3426
            crate::util::config::get_config_element::<crate::pro::util::config::Quota>()
1✔
3427
                .unwrap()
1✔
3428
                .initial_credits
3429
        );
3430

3431
        assert_eq!(
1✔
3432
            admin_db.quota_available_by_user(&user).await.unwrap(),
1✔
3433
            crate::util::config::get_config_element::<crate::pro::util::config::Quota>()
1✔
3434
                .unwrap()
1✔
3435
                .initial_credits
3436
        );
3437

3438
        admin_db
1✔
3439
            .update_quota_available_by_user(&user, 123)
1✔
3440
            .await
1✔
3441
            .unwrap();
1✔
3442

3443
        assert_eq!(db.quota_available().await.unwrap(), 123);
1✔
3444

3445
        assert_eq!(admin_db.quota_available_by_user(&user).await.unwrap(), 123);
1✔
3446
    }
1✔
3447

3448
    #[allow(clippy::too_many_lines)]
3449
    #[ge_context::test]
2✔
3450
    async fn it_removes_layer_collections(app_ctx: ProPostgresContext<NoTls>) {
1✔
3451
        let session = admin_login(&app_ctx).await;
1✔
3452

3453
        let layer_db = app_ctx.session_context(session).db();
1✔
3454

1✔
3455
        let layer = AddLayer {
1✔
3456
            name: "layer".to_string(),
1✔
3457
            description: "description".to_string(),
1✔
3458
            workflow: Workflow {
1✔
3459
                operator: TypedOperator::Vector(
1✔
3460
                    MockPointSource {
1✔
3461
                        params: MockPointSourceParams {
1✔
3462
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3463
                        },
1✔
3464
                    }
1✔
3465
                    .boxed(),
1✔
3466
                ),
1✔
3467
            },
1✔
3468
            symbology: None,
1✔
3469
            metadata: Default::default(),
1✔
3470
            properties: Default::default(),
1✔
3471
        };
1✔
3472

3473
        let root_collection = &layer_db.get_root_layer_collection_id().await.unwrap();
1✔
3474

1✔
3475
        let collection = AddLayerCollection {
1✔
3476
            name: "top collection".to_string(),
1✔
3477
            description: "description".to_string(),
1✔
3478
            properties: Default::default(),
1✔
3479
        };
1✔
3480

3481
        let top_c_id = layer_db
1✔
3482
            .add_layer_collection(collection, root_collection)
1✔
3483
            .await
1✔
3484
            .unwrap();
1✔
3485

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

1✔
3488
        let collection = AddLayerCollection {
1✔
3489
            name: "empty collection".to_string(),
1✔
3490
            description: "description".to_string(),
1✔
3491
            properties: Default::default(),
1✔
3492
        };
1✔
3493

3494
        let empty_c_id = layer_db
1✔
3495
            .add_layer_collection(collection, &top_c_id)
1✔
3496
            .await
1✔
3497
            .unwrap();
1✔
3498

3499
        let items = layer_db
1✔
3500
            .load_layer_collection(
1✔
3501
                &top_c_id,
1✔
3502
                LayerCollectionListOptions {
1✔
3503
                    offset: 0,
1✔
3504
                    limit: 20,
1✔
3505
                },
1✔
3506
            )
1✔
3507
            .await
1✔
3508
            .unwrap();
1✔
3509

1✔
3510
        assert_eq!(
1✔
3511
            items,
1✔
3512
            LayerCollection {
1✔
3513
                id: ProviderLayerCollectionId {
1✔
3514
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
3515
                    collection_id: top_c_id.clone(),
1✔
3516
                },
1✔
3517
                name: "top collection".to_string(),
1✔
3518
                description: "description".to_string(),
1✔
3519
                items: vec![
1✔
3520
                    CollectionItem::Collection(LayerCollectionListing {
1✔
3521
                        id: ProviderLayerCollectionId {
1✔
3522
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
3523
                            collection_id: empty_c_id.clone(),
1✔
3524
                        },
1✔
3525
                        name: "empty collection".to_string(),
1✔
3526
                        description: "description".to_string(),
1✔
3527
                        properties: Default::default(),
1✔
3528
                    }),
1✔
3529
                    CollectionItem::Layer(LayerListing {
1✔
3530
                        id: ProviderLayerId {
1✔
3531
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
3532
                            layer_id: l_id.clone(),
1✔
3533
                        },
1✔
3534
                        name: "layer".to_string(),
1✔
3535
                        description: "description".to_string(),
1✔
3536
                        properties: vec![],
1✔
3537
                    })
1✔
3538
                ],
1✔
3539
                entry_label: None,
1✔
3540
                properties: vec![],
1✔
3541
            }
1✔
3542
        );
1✔
3543

3544
        // remove empty collection
3545
        layer_db.remove_layer_collection(&empty_c_id).await.unwrap();
1✔
3546

3547
        let items = layer_db
1✔
3548
            .load_layer_collection(
1✔
3549
                &top_c_id,
1✔
3550
                LayerCollectionListOptions {
1✔
3551
                    offset: 0,
1✔
3552
                    limit: 20,
1✔
3553
                },
1✔
3554
            )
1✔
3555
            .await
1✔
3556
            .unwrap();
1✔
3557

1✔
3558
        assert_eq!(
1✔
3559
            items,
1✔
3560
            LayerCollection {
1✔
3561
                id: ProviderLayerCollectionId {
1✔
3562
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
3563
                    collection_id: top_c_id.clone(),
1✔
3564
                },
1✔
3565
                name: "top collection".to_string(),
1✔
3566
                description: "description".to_string(),
1✔
3567
                items: vec![CollectionItem::Layer(LayerListing {
1✔
3568
                    id: ProviderLayerId {
1✔
3569
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
3570
                        layer_id: l_id.clone(),
1✔
3571
                    },
1✔
3572
                    name: "layer".to_string(),
1✔
3573
                    description: "description".to_string(),
1✔
3574
                    properties: vec![],
1✔
3575
                })],
1✔
3576
                entry_label: None,
1✔
3577
                properties: vec![],
1✔
3578
            }
1✔
3579
        );
1✔
3580

3581
        // remove top (not root) collection
3582
        layer_db.remove_layer_collection(&top_c_id).await.unwrap();
1✔
3583

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

1✔
3595
        // should be deleted automatically
1✔
3596
        layer_db.load_layer(&l_id).await.unwrap_err();
1✔
3597

1✔
3598
        // it is not allowed to remove the root collection
1✔
3599
        layer_db
1✔
3600
            .remove_layer_collection(root_collection)
1✔
3601
            .await
1✔
3602
            .unwrap_err();
1✔
3603
        layer_db
1✔
3604
            .load_layer_collection(
1✔
3605
                root_collection,
1✔
3606
                LayerCollectionListOptions {
1✔
3607
                    offset: 0,
1✔
3608
                    limit: 20,
1✔
3609
                },
1✔
3610
            )
1✔
3611
            .await
1✔
3612
            .unwrap();
1✔
3613
    }
1✔
3614

3615
    #[ge_context::test]
2✔
3616
    #[allow(clippy::too_many_lines)]
3617
    async fn it_removes_collections_from_collections(app_ctx: ProPostgresContext<NoTls>) {
1✔
3618
        let session = admin_login(&app_ctx).await;
1✔
3619

3620
        let db = app_ctx.session_context(session).db();
1✔
3621

3622
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
3623

3624
        let mid_collection_id = db
1✔
3625
            .add_layer_collection(
1✔
3626
                AddLayerCollection {
1✔
3627
                    name: "mid collection".to_string(),
1✔
3628
                    description: "description".to_string(),
1✔
3629
                    properties: Default::default(),
1✔
3630
                },
1✔
3631
                root_collection_id,
1✔
3632
            )
1✔
3633
            .await
1✔
3634
            .unwrap();
1✔
3635

3636
        let bottom_collection_id = db
1✔
3637
            .add_layer_collection(
1✔
3638
                AddLayerCollection {
1✔
3639
                    name: "bottom collection".to_string(),
1✔
3640
                    description: "description".to_string(),
1✔
3641
                    properties: Default::default(),
1✔
3642
                },
1✔
3643
                &mid_collection_id,
1✔
3644
            )
1✔
3645
            .await
1✔
3646
            .unwrap();
1✔
3647

3648
        let layer_id = db
1✔
3649
            .add_layer(
1✔
3650
                AddLayer {
1✔
3651
                    name: "layer".to_string(),
1✔
3652
                    description: "description".to_string(),
1✔
3653
                    workflow: Workflow {
1✔
3654
                        operator: TypedOperator::Vector(
1✔
3655
                            MockPointSource {
1✔
3656
                                params: MockPointSourceParams {
1✔
3657
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3658
                                },
1✔
3659
                            }
1✔
3660
                            .boxed(),
1✔
3661
                        ),
1✔
3662
                    },
1✔
3663
                    symbology: None,
1✔
3664
                    metadata: Default::default(),
1✔
3665
                    properties: Default::default(),
1✔
3666
                },
1✔
3667
                &mid_collection_id,
1✔
3668
            )
1✔
3669
            .await
1✔
3670
            .unwrap();
1✔
3671

1✔
3672
        // removing the mid collection…
1✔
3673
        db.remove_layer_collection_from_parent(&mid_collection_id, root_collection_id)
1✔
3674
            .await
1✔
3675
            .unwrap();
1✔
3676

1✔
3677
        // …should remove itself
1✔
3678
        db.load_layer_collection(&mid_collection_id, LayerCollectionListOptions::default())
1✔
3679
            .await
1✔
3680
            .unwrap_err();
1✔
3681

1✔
3682
        // …should remove the bottom collection
1✔
3683
        db.load_layer_collection(&bottom_collection_id, LayerCollectionListOptions::default())
1✔
3684
            .await
1✔
3685
            .unwrap_err();
1✔
3686

1✔
3687
        // … and should remove the layer of the bottom collection
1✔
3688
        db.load_layer(&layer_id).await.unwrap_err();
1✔
3689

1✔
3690
        // the root collection is still there
1✔
3691
        db.load_layer_collection(root_collection_id, LayerCollectionListOptions::default())
1✔
3692
            .await
1✔
3693
            .unwrap();
1✔
3694
    }
1✔
3695

3696
    #[ge_context::test]
2✔
3697
    #[allow(clippy::too_many_lines)]
3698
    async fn it_removes_layers_from_collections(app_ctx: ProPostgresContext<NoTls>) {
1✔
3699
        let session = admin_login(&app_ctx).await;
1✔
3700

3701
        let db = app_ctx.session_context(session).db();
1✔
3702

3703
        let root_collection = &db.get_root_layer_collection_id().await.unwrap();
1✔
3704

3705
        let another_collection = db
1✔
3706
            .add_layer_collection(
1✔
3707
                AddLayerCollection {
1✔
3708
                    name: "top collection".to_string(),
1✔
3709
                    description: "description".to_string(),
1✔
3710
                    properties: Default::default(),
1✔
3711
                },
1✔
3712
                root_collection,
1✔
3713
            )
1✔
3714
            .await
1✔
3715
            .unwrap();
1✔
3716

3717
        let layer_in_one_collection = db
1✔
3718
            .add_layer(
1✔
3719
                AddLayer {
1✔
3720
                    name: "layer 1".to_string(),
1✔
3721
                    description: "description".to_string(),
1✔
3722
                    workflow: Workflow {
1✔
3723
                        operator: TypedOperator::Vector(
1✔
3724
                            MockPointSource {
1✔
3725
                                params: MockPointSourceParams {
1✔
3726
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3727
                                },
1✔
3728
                            }
1✔
3729
                            .boxed(),
1✔
3730
                        ),
1✔
3731
                    },
1✔
3732
                    symbology: None,
1✔
3733
                    metadata: Default::default(),
1✔
3734
                    properties: Default::default(),
1✔
3735
                },
1✔
3736
                &another_collection,
1✔
3737
            )
1✔
3738
            .await
1✔
3739
            .unwrap();
1✔
3740

3741
        let layer_in_two_collections = db
1✔
3742
            .add_layer(
1✔
3743
                AddLayer {
1✔
3744
                    name: "layer 2".to_string(),
1✔
3745
                    description: "description".to_string(),
1✔
3746
                    workflow: Workflow {
1✔
3747
                        operator: TypedOperator::Vector(
1✔
3748
                            MockPointSource {
1✔
3749
                                params: MockPointSourceParams {
1✔
3750
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3751
                                },
1✔
3752
                            }
1✔
3753
                            .boxed(),
1✔
3754
                        ),
1✔
3755
                    },
1✔
3756
                    symbology: None,
1✔
3757
                    metadata: Default::default(),
1✔
3758
                    properties: Default::default(),
1✔
3759
                },
1✔
3760
                &another_collection,
1✔
3761
            )
1✔
3762
            .await
1✔
3763
            .unwrap();
1✔
3764

1✔
3765
        db.load_layer(&layer_in_two_collections).await.unwrap();
1✔
3766

1✔
3767
        db.add_layer_to_collection(&layer_in_two_collections, root_collection)
1✔
3768
            .await
1✔
3769
            .unwrap();
1✔
3770

1✔
3771
        // remove first layer --> should be deleted entirely
1✔
3772

1✔
3773
        db.remove_layer_from_collection(&layer_in_one_collection, &another_collection)
1✔
3774
            .await
1✔
3775
            .unwrap();
1✔
3776

3777
        let number_of_layer_in_collection = db
1✔
3778
            .load_layer_collection(
1✔
3779
                &another_collection,
1✔
3780
                LayerCollectionListOptions {
1✔
3781
                    offset: 0,
1✔
3782
                    limit: 20,
1✔
3783
                },
1✔
3784
            )
1✔
3785
            .await
1✔
3786
            .unwrap()
1✔
3787
            .items
1✔
3788
            .len();
1✔
3789
        assert_eq!(
1✔
3790
            number_of_layer_in_collection,
1✔
3791
            1 /* only the other collection should be here */
1✔
3792
        );
1✔
3793

3794
        db.load_layer(&layer_in_one_collection).await.unwrap_err();
1✔
3795

1✔
3796
        // remove second layer --> should only be gone in collection
1✔
3797

1✔
3798
        db.remove_layer_from_collection(&layer_in_two_collections, &another_collection)
1✔
3799
            .await
1✔
3800
            .unwrap();
1✔
3801

3802
        let number_of_layer_in_collection = db
1✔
3803
            .load_layer_collection(
1✔
3804
                &another_collection,
1✔
3805
                LayerCollectionListOptions {
1✔
3806
                    offset: 0,
1✔
3807
                    limit: 20,
1✔
3808
                },
1✔
3809
            )
1✔
3810
            .await
1✔
3811
            .unwrap()
1✔
3812
            .items
1✔
3813
            .len();
1✔
3814
        assert_eq!(
1✔
3815
            number_of_layer_in_collection,
1✔
3816
            0 /* both layers were deleted */
1✔
3817
        );
1✔
3818

3819
        db.load_layer(&layer_in_two_collections).await.unwrap();
1✔
3820
    }
1✔
3821

3822
    #[ge_context::test]
2✔
3823
    #[allow(clippy::too_many_lines)]
3824
    async fn it_deletes_dataset(app_ctx: ProPostgresContext<NoTls>) {
1✔
3825
        let loading_info = OgrSourceDataset {
1✔
3826
            file_name: PathBuf::from("test.csv"),
1✔
3827
            layer_name: "test.csv".to_owned(),
1✔
3828
            data_type: Some(VectorDataType::MultiPoint),
1✔
3829
            time: OgrSourceDatasetTimeType::Start {
1✔
3830
                start_field: "start".to_owned(),
1✔
3831
                start_format: OgrSourceTimeFormat::Auto,
1✔
3832
                duration: OgrSourceDurationSpec::Zero,
1✔
3833
            },
1✔
3834
            default_geometry: None,
1✔
3835
            columns: Some(OgrSourceColumnSpec {
1✔
3836
                format_specifics: Some(FormatSpecifics::Csv {
1✔
3837
                    header: CsvHeader::Auto,
1✔
3838
                }),
1✔
3839
                x: "x".to_owned(),
1✔
3840
                y: None,
1✔
3841
                int: vec![],
1✔
3842
                float: vec![],
1✔
3843
                text: vec![],
1✔
3844
                bool: vec![],
1✔
3845
                datetime: vec![],
1✔
3846
                rename: None,
1✔
3847
            }),
1✔
3848
            force_ogr_time_filter: false,
1✔
3849
            force_ogr_spatial_filter: false,
1✔
3850
            on_error: OgrSourceErrorSpec::Ignore,
1✔
3851
            sql_query: None,
1✔
3852
            attribute_query: None,
1✔
3853
            cache_ttl: CacheTtlSeconds::default(),
1✔
3854
        };
1✔
3855

1✔
3856
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
3857
            OgrSourceDataset,
1✔
3858
            VectorResultDescriptor,
1✔
3859
            VectorQueryRectangle,
1✔
3860
        > {
1✔
3861
            loading_info: loading_info.clone(),
1✔
3862
            result_descriptor: VectorResultDescriptor {
1✔
3863
                data_type: VectorDataType::MultiPoint,
1✔
3864
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3865
                columns: [(
1✔
3866
                    "foo".to_owned(),
1✔
3867
                    VectorColumnInfo {
1✔
3868
                        data_type: FeatureDataType::Float,
1✔
3869
                        measurement: Measurement::Unitless,
1✔
3870
                    },
1✔
3871
                )]
1✔
3872
                .into_iter()
1✔
3873
                .collect(),
1✔
3874
                time: None,
1✔
3875
                bbox: None,
1✔
3876
            },
1✔
3877
            phantom: Default::default(),
1✔
3878
        });
1✔
3879

3880
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
3881

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

1✔
3884
        let db = app_ctx.session_context(session.clone()).db();
1✔
3885
        let dataset_id = db
1✔
3886
            .add_dataset(
1✔
3887
                AddDataset {
1✔
3888
                    name: Some(dataset_name),
1✔
3889
                    display_name: "Ogr Test".to_owned(),
1✔
3890
                    description: "desc".to_owned(),
1✔
3891
                    source_operator: "OgrSource".to_owned(),
1✔
3892
                    symbology: None,
1✔
3893
                    provenance: Some(vec![Provenance {
1✔
3894
                        citation: "citation".to_owned(),
1✔
3895
                        license: "license".to_owned(),
1✔
3896
                        uri: "uri".to_owned(),
1✔
3897
                    }]),
1✔
3898
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
3899
                },
1✔
3900
                meta_data,
1✔
3901
            )
1✔
3902
            .await
1✔
3903
            .unwrap()
1✔
3904
            .id;
1✔
3905

1✔
3906
        assert!(db.load_dataset(&dataset_id).await.is_ok());
1✔
3907

3908
        db.delete_dataset(dataset_id).await.unwrap();
1✔
3909

1✔
3910
        assert!(db.load_dataset(&dataset_id).await.is_err());
1✔
3911
    }
1✔
3912

3913
    #[ge_context::test]
2✔
3914
    #[allow(clippy::too_many_lines)]
3915
    async fn it_deletes_admin_dataset(app_ctx: ProPostgresContext<NoTls>) {
1✔
3916
        let dataset_name = DatasetName::new(None, "my_dataset");
1✔
3917

1✔
3918
        let loading_info = OgrSourceDataset {
1✔
3919
            file_name: PathBuf::from("test.csv"),
1✔
3920
            layer_name: "test.csv".to_owned(),
1✔
3921
            data_type: Some(VectorDataType::MultiPoint),
1✔
3922
            time: OgrSourceDatasetTimeType::Start {
1✔
3923
                start_field: "start".to_owned(),
1✔
3924
                start_format: OgrSourceTimeFormat::Auto,
1✔
3925
                duration: OgrSourceDurationSpec::Zero,
1✔
3926
            },
1✔
3927
            default_geometry: None,
1✔
3928
            columns: Some(OgrSourceColumnSpec {
1✔
3929
                format_specifics: Some(FormatSpecifics::Csv {
1✔
3930
                    header: CsvHeader::Auto,
1✔
3931
                }),
1✔
3932
                x: "x".to_owned(),
1✔
3933
                y: None,
1✔
3934
                int: vec![],
1✔
3935
                float: vec![],
1✔
3936
                text: vec![],
1✔
3937
                bool: vec![],
1✔
3938
                datetime: vec![],
1✔
3939
                rename: None,
1✔
3940
            }),
1✔
3941
            force_ogr_time_filter: false,
1✔
3942
            force_ogr_spatial_filter: false,
1✔
3943
            on_error: OgrSourceErrorSpec::Ignore,
1✔
3944
            sql_query: None,
1✔
3945
            attribute_query: None,
1✔
3946
            cache_ttl: CacheTtlSeconds::default(),
1✔
3947
        };
1✔
3948

1✔
3949
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
3950
            OgrSourceDataset,
1✔
3951
            VectorResultDescriptor,
1✔
3952
            VectorQueryRectangle,
1✔
3953
        > {
1✔
3954
            loading_info: loading_info.clone(),
1✔
3955
            result_descriptor: VectorResultDescriptor {
1✔
3956
                data_type: VectorDataType::MultiPoint,
1✔
3957
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3958
                columns: [(
1✔
3959
                    "foo".to_owned(),
1✔
3960
                    VectorColumnInfo {
1✔
3961
                        data_type: FeatureDataType::Float,
1✔
3962
                        measurement: Measurement::Unitless,
1✔
3963
                    },
1✔
3964
                )]
1✔
3965
                .into_iter()
1✔
3966
                .collect(),
1✔
3967
                time: None,
1✔
3968
                bbox: None,
1✔
3969
            },
1✔
3970
            phantom: Default::default(),
1✔
3971
        });
1✔
3972

3973
        let session = admin_login(&app_ctx).await;
1✔
3974

3975
        let db = app_ctx.session_context(session).db();
1✔
3976
        let dataset_id = db
1✔
3977
            .add_dataset(
1✔
3978
                AddDataset {
1✔
3979
                    name: Some(dataset_name),
1✔
3980
                    display_name: "Ogr Test".to_owned(),
1✔
3981
                    description: "desc".to_owned(),
1✔
3982
                    source_operator: "OgrSource".to_owned(),
1✔
3983
                    symbology: None,
1✔
3984
                    provenance: Some(vec![Provenance {
1✔
3985
                        citation: "citation".to_owned(),
1✔
3986
                        license: "license".to_owned(),
1✔
3987
                        uri: "uri".to_owned(),
1✔
3988
                    }]),
1✔
3989
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
3990
                },
1✔
3991
                meta_data,
1✔
3992
            )
1✔
3993
            .await
1✔
3994
            .unwrap()
1✔
3995
            .id;
1✔
3996

1✔
3997
        assert!(db.load_dataset(&dataset_id).await.is_ok());
1✔
3998

3999
        db.delete_dataset(dataset_id).await.unwrap();
1✔
4000

1✔
4001
        assert!(db.load_dataset(&dataset_id).await.is_err());
1✔
4002
    }
1✔
4003

4004
    #[ge_context::test]
2✔
4005
    async fn test_missing_layer_dataset_in_collection_listing(app_ctx: ProPostgresContext<NoTls>) {
1✔
4006
        let session = admin_login(&app_ctx).await;
1✔
4007
        let db = app_ctx.session_context(session).db();
1✔
4008

4009
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
4010

4011
        let top_collection_id = db
1✔
4012
            .add_layer_collection(
1✔
4013
                AddLayerCollection {
1✔
4014
                    name: "top collection".to_string(),
1✔
4015
                    description: "description".to_string(),
1✔
4016
                    properties: Default::default(),
1✔
4017
                },
1✔
4018
                root_collection_id,
1✔
4019
            )
1✔
4020
            .await
1✔
4021
            .unwrap();
1✔
4022

1✔
4023
        let faux_layer = LayerId("faux".to_string());
1✔
4024

1✔
4025
        // this should fail
1✔
4026
        db.add_layer_to_collection(&faux_layer, &top_collection_id)
1✔
4027
            .await
1✔
4028
            .unwrap_err();
1✔
4029

4030
        let root_collection_layers = db
1✔
4031
            .load_layer_collection(
1✔
4032
                &top_collection_id,
1✔
4033
                LayerCollectionListOptions {
1✔
4034
                    offset: 0,
1✔
4035
                    limit: 20,
1✔
4036
                },
1✔
4037
            )
1✔
4038
            .await
1✔
4039
            .unwrap();
1✔
4040

1✔
4041
        assert_eq!(
1✔
4042
            root_collection_layers,
1✔
4043
            LayerCollection {
1✔
4044
                id: ProviderLayerCollectionId {
1✔
4045
                    provider_id: DataProviderId(
1✔
4046
                        "ce5e84db-cbf9-48a2-9a32-d4b7cc56ea74".try_into().unwrap()
1✔
4047
                    ),
1✔
4048
                    collection_id: top_collection_id.clone(),
1✔
4049
                },
1✔
4050
                name: "top collection".to_string(),
1✔
4051
                description: "description".to_string(),
1✔
4052
                items: vec![],
1✔
4053
                entry_label: None,
1✔
4054
                properties: vec![],
1✔
4055
            }
1✔
4056
        );
1✔
4057
    }
1✔
4058

4059
    #[allow(clippy::too_many_lines)]
4060
    #[ge_context::test]
2✔
4061
    async fn it_restricts_layer_permissions(app_ctx: ProPostgresContext<NoTls>) {
1✔
4062
        let admin_session = admin_login(&app_ctx).await;
1✔
4063
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
4064

1✔
4065
        let admin_db = app_ctx.session_context(admin_session.clone()).db();
1✔
4066
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
4067

4068
        let root = admin_db.get_root_layer_collection_id().await.unwrap();
1✔
4069

4070
        // add new collection as admin
4071
        let new_collection_id = admin_db
1✔
4072
            .add_layer_collection(
1✔
4073
                AddLayerCollection {
1✔
4074
                    name: "admin collection".to_string(),
1✔
4075
                    description: String::new(),
1✔
4076
                    properties: Default::default(),
1✔
4077
                },
1✔
4078
                &root,
1✔
4079
            )
1✔
4080
            .await
1✔
4081
            .unwrap();
1✔
4082

4083
        // load as regular user, not visible
4084
        let collection = db1
1✔
4085
            .load_layer_collection(
1✔
4086
                &root,
1✔
4087
                LayerCollectionListOptions {
1✔
4088
                    offset: 0,
1✔
4089
                    limit: 10,
1✔
4090
                },
1✔
4091
            )
1✔
4092
            .await
1✔
4093
            .unwrap();
1✔
4094
        assert!(!collection.items.iter().any(|c| match c {
1✔
4095
            CollectionItem::Collection(c) => c.id.collection_id == new_collection_id,
1✔
4096
            CollectionItem::Layer(_) => false,
×
4097
        }));
1✔
4098

4099
        // give user read permission
4100
        admin_db
1✔
4101
            .add_permission(
1✔
4102
                session1.user.id.into(),
1✔
4103
                new_collection_id.clone(),
1✔
4104
                Permission::Read,
1✔
4105
            )
1✔
4106
            .await
1✔
4107
            .unwrap();
1✔
4108

4109
        // now visible
4110
        let collection = db1
1✔
4111
            .load_layer_collection(
1✔
4112
                &root,
1✔
4113
                LayerCollectionListOptions {
1✔
4114
                    offset: 0,
1✔
4115
                    limit: 10,
1✔
4116
                },
1✔
4117
            )
1✔
4118
            .await
1✔
4119
            .unwrap();
1✔
4120

1✔
4121
        assert!(collection.items.iter().any(|c| match c {
1✔
4122
            CollectionItem::Collection(c) => c.id.collection_id == new_collection_id,
1✔
4123
            CollectionItem::Layer(_) => false,
×
4124
        }));
1✔
4125
    }
1✔
4126

4127
    #[allow(clippy::too_many_lines)]
4128
    #[ge_context::test]
2✔
4129
    async fn it_handles_user_roles(app_ctx: ProPostgresContext<NoTls>) {
1✔
4130
        let admin_session = admin_login(&app_ctx).await;
1✔
4131
        let user_id = app_ctx
1✔
4132
            .register_user(UserRegistration {
1✔
4133
                email: "foo@example.com".to_string(),
1✔
4134
                password: "secret123".to_string(),
1✔
4135
                real_name: "Foo Bar".to_string(),
1✔
4136
            })
1✔
4137
            .await
1✔
4138
            .unwrap();
1✔
4139

1✔
4140
        let admin_db = app_ctx.session_context(admin_session.clone()).db();
1✔
4141

4142
        // create a new role
4143
        let role_id = admin_db.add_role("foo").await.unwrap();
1✔
4144

4145
        let user_session = app_ctx
1✔
4146
            .login(UserCredentials {
1✔
4147
                email: "foo@example.com".to_string(),
1✔
4148
                password: "secret123".to_string(),
1✔
4149
            })
1✔
4150
            .await
1✔
4151
            .unwrap();
1✔
4152

1✔
4153
        // user does not have the role yet
1✔
4154

1✔
4155
        assert!(!user_session.roles.contains(&role_id));
1✔
4156

4157
        //user can query their role descriptions (user role and registered user)
4158
        assert_eq!(user_session.roles.len(), 2);
1✔
4159

4160
        let expected_user_role_description = RoleDescription {
1✔
4161
            role: Role {
1✔
4162
                id: RoleId::from(user_id),
1✔
4163
                name: "foo@example.com".to_string(),
1✔
4164
            },
1✔
4165
            individual: true,
1✔
4166
        };
1✔
4167
        let expected_registered_role_description = RoleDescription {
1✔
4168
            role: Role {
1✔
4169
                id: Role::registered_user_role_id(),
1✔
4170
                name: "user".to_string(),
1✔
4171
            },
1✔
4172
            individual: false,
1✔
4173
        };
1✔
4174

4175
        let user_role_descriptions = app_ctx
1✔
4176
            .session_context(user_session.clone())
1✔
4177
            .db()
1✔
4178
            .get_role_descriptions(&user_id)
1✔
4179
            .await
1✔
4180
            .unwrap();
1✔
4181
        assert_eq!(
1✔
4182
            vec![
1✔
4183
                expected_user_role_description.clone(),
1✔
4184
                expected_registered_role_description.clone(),
1✔
4185
            ],
1✔
4186
            user_role_descriptions
1✔
4187
        );
1✔
4188

4189
        // we assign the role to the user
4190
        admin_db.assign_role(&role_id, &user_id).await.unwrap();
1✔
4191

4192
        let user_session = app_ctx
1✔
4193
            .login(UserCredentials {
1✔
4194
                email: "foo@example.com".to_string(),
1✔
4195
                password: "secret123".to_string(),
1✔
4196
            })
1✔
4197
            .await
1✔
4198
            .unwrap();
1✔
4199

1✔
4200
        // should be present now
1✔
4201
        assert!(user_session.roles.contains(&role_id));
1✔
4202

4203
        //user can query their role descriptions (now an additional foo role)
4204
        let expected_foo_role_description = RoleDescription {
1✔
4205
            role: Role {
1✔
4206
                id: role_id,
1✔
4207
                name: "foo".to_string(),
1✔
4208
            },
1✔
4209
            individual: false,
1✔
4210
        };
1✔
4211

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

4227
        // we revoke it
4228
        admin_db.revoke_role(&role_id, &user_id).await.unwrap();
1✔
4229

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

1✔
4238
        // the role is gone now
1✔
4239
        assert!(!user_session.roles.contains(&role_id));
1✔
4240

4241
        //user can query their role descriptions (user role and registered user)
4242
        let user_role_descriptions = app_ctx
1✔
4243
            .session_context(user_session.clone())
1✔
4244
            .db()
1✔
4245
            .get_role_descriptions(&user_id)
1✔
4246
            .await
1✔
4247
            .unwrap();
1✔
4248
        assert_eq!(
1✔
4249
            vec![
1✔
4250
                expected_user_role_description.clone(),
1✔
4251
                expected_registered_role_description.clone(),
1✔
4252
            ],
1✔
4253
            user_role_descriptions
1✔
4254
        );
1✔
4255

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

4258
        admin_db.assign_role(&role_id, &user_id).await.unwrap();
1✔
4259

1✔
4260
        admin_db.remove_role(&role_id).await.unwrap();
1✔
4261

4262
        let user_session = app_ctx
1✔
4263
            .login(UserCredentials {
1✔
4264
                email: "foo@example.com".to_string(),
1✔
4265
                password: "secret123".to_string(),
1✔
4266
            })
1✔
4267
            .await
1✔
4268
            .unwrap();
1✔
4269

1✔
4270
        assert!(!user_session.roles.contains(&role_id));
1✔
4271

4272
        //user can query their role descriptions (user role and registered user)
4273
        let user_role_descriptions = app_ctx
1✔
4274
            .session_context(user_session.clone())
1✔
4275
            .db()
1✔
4276
            .get_role_descriptions(&user_id)
1✔
4277
            .await
1✔
4278
            .unwrap();
1✔
4279
        assert_eq!(
1✔
4280
            vec![
1✔
4281
                expected_user_role_description,
1✔
4282
                expected_registered_role_description.clone(),
1✔
4283
            ],
1✔
4284
            user_role_descriptions
1✔
4285
        );
1✔
4286
    }
1✔
4287

4288
    #[allow(clippy::too_many_lines)]
4289
    #[ge_context::test]
2✔
4290
    async fn it_updates_project_layer_symbology(app_ctx: ProPostgresContext<NoTls>) {
1✔
4291
        let session = app_ctx.create_anonymous_session().await.unwrap();
1✔
4292

4293
        let (_, workflow_id) = register_ndvi_workflow_helper(&app_ctx).await;
1✔
4294

4295
        let db = app_ctx.session_context(session.clone()).db();
1✔
4296

1✔
4297
        let create_project: CreateProject = serde_json::from_value(json!({
1✔
4298
            "name": "Default",
1✔
4299
            "description": "Default project",
1✔
4300
            "bounds": {
1✔
4301
                "boundingBox": {
1✔
4302
                    "lowerLeftCoordinate": {
1✔
4303
                        "x": -180,
1✔
4304
                        "y": -90
1✔
4305
                    },
1✔
4306
                    "upperRightCoordinate": {
1✔
4307
                        "x": 180,
1✔
4308
                        "y": 90
1✔
4309
                    }
1✔
4310
                },
1✔
4311
                "spatialReference": "EPSG:4326",
1✔
4312
                "timeInterval": {
1✔
4313
                    "start": 1_396_353_600_000i64,
1✔
4314
                    "end": 1_396_353_600_000i64
1✔
4315
                }
1✔
4316
            },
1✔
4317
            "timeStep": {
1✔
4318
                "step": 1,
1✔
4319
                "granularity": "months"
1✔
4320
            }
1✔
4321
        }))
1✔
4322
        .unwrap();
1✔
4323

4324
        let project_id = db.create_project(create_project).await.unwrap();
1✔
4325

1✔
4326
        let update: UpdateProject = serde_json::from_value(json!({
1✔
4327
            "id": project_id.to_string(),
1✔
4328
            "layers": [{
1✔
4329
                "name": "NDVI",
1✔
4330
                "workflow": workflow_id.to_string(),
1✔
4331
                "visibility": {
1✔
4332
                    "data": true,
1✔
4333
                    "legend": false
1✔
4334
                },
1✔
4335
                "symbology": {
1✔
4336
                    "type": "raster",
1✔
4337
                    "opacity": 1,
1✔
4338
                    "rasterColorizer": {
1✔
4339
                        "type": "singleBand",
1✔
4340
                        "band": 0,
1✔
4341
                        "bandColorizer": {
1✔
4342
                            "type": "linearGradient",
1✔
4343
                            "breakpoints": [{
1✔
4344
                                "value": 1,
1✔
4345
                                "color": [0, 0, 0, 255]
1✔
4346
                            }, {
1✔
4347
                                "value": 255,
1✔
4348
                                "color": [255, 255, 255, 255]
1✔
4349
                            }],
1✔
4350
                            "noDataColor": [0, 0, 0, 0],
1✔
4351
                            "overColor": [255, 255, 255, 127],
1✔
4352
                            "underColor": [255, 255, 255, 127]
1✔
4353
                        }
1✔
4354
                    }
1✔
4355
                }
1✔
4356
            }]
1✔
4357
        }))
1✔
4358
        .unwrap();
1✔
4359

1✔
4360
        db.update_project(update).await.unwrap();
1✔
4361

1✔
4362
        let update: UpdateProject = serde_json::from_value(json!({
1✔
4363
            "id": project_id.to_string(),
1✔
4364
            "layers": [{
1✔
4365
                "name": "NDVI",
1✔
4366
                "workflow": workflow_id.to_string(),
1✔
4367
                "visibility": {
1✔
4368
                    "data": true,
1✔
4369
                    "legend": false
1✔
4370
                },
1✔
4371
                "symbology": {
1✔
4372
                    "type": "raster",
1✔
4373
                    "opacity": 1,
1✔
4374
                    "rasterColorizer": {
1✔
4375
                        "type": "singleBand",
1✔
4376
                        "band": 0,
1✔
4377
                        "bandColorizer": {
1✔
4378
                        "type": "linearGradient",
1✔
4379
                            "breakpoints": [{
1✔
4380
                                "value": 1,
1✔
4381
                                "color": [0, 0, 4, 255]
1✔
4382
                            }, {
1✔
4383
                                "value": 17.866_666_666_666_667,
1✔
4384
                                "color": [11, 9, 36, 255]
1✔
4385
                            }, {
1✔
4386
                                "value": 34.733_333_333_333_334,
1✔
4387
                                "color": [32, 17, 75, 255]
1✔
4388
                            }, {
1✔
4389
                                "value": 51.6,
1✔
4390
                                "color": [59, 15, 112, 255]
1✔
4391
                            }, {
1✔
4392
                                "value": 68.466_666_666_666_67,
1✔
4393
                                "color": [87, 21, 126, 255]
1✔
4394
                            }, {
1✔
4395
                                "value": 85.333_333_333_333_33,
1✔
4396
                                "color": [114, 31, 129, 255]
1✔
4397
                            }, {
1✔
4398
                                "value": 102.199_999_999_999_99,
1✔
4399
                                "color": [140, 41, 129, 255]
1✔
4400
                            }, {
1✔
4401
                                "value": 119.066_666_666_666_65,
1✔
4402
                                "color": [168, 50, 125, 255]
1✔
4403
                            }, {
1✔
4404
                                "value": 135.933_333_333_333_34,
1✔
4405
                                "color": [196, 60, 117, 255]
1✔
4406
                            }, {
1✔
4407
                                "value": 152.799_999_999_999_98,
1✔
4408
                                "color": [222, 73, 104, 255]
1✔
4409
                            }, {
1✔
4410
                                "value": 169.666_666_666_666_66,
1✔
4411
                                "color": [241, 96, 93, 255]
1✔
4412
                            }, {
1✔
4413
                                "value": 186.533_333_333_333_33,
1✔
4414
                                "color": [250, 127, 94, 255]
1✔
4415
                            }, {
1✔
4416
                                "value": 203.399_999_999_999_98,
1✔
4417
                                "color": [254, 159, 109, 255]
1✔
4418
                            }, {
1✔
4419
                                "value": 220.266_666_666_666_65,
1✔
4420
                                "color": [254, 191, 132, 255]
1✔
4421
                            }, {
1✔
4422
                                "value": 237.133_333_333_333_3,
1✔
4423
                                "color": [253, 222, 160, 255]
1✔
4424
                            }, {
1✔
4425
                                "value": 254,
1✔
4426
                                "color": [252, 253, 191, 255]
1✔
4427
                            }],
1✔
4428
                            "noDataColor": [0, 0, 0, 0],
1✔
4429
                            "overColor": [255, 255, 255, 127],
1✔
4430
                            "underColor": [255, 255, 255, 127]
1✔
4431
                        }
1✔
4432
                    }
1✔
4433
                }
1✔
4434
            }]
1✔
4435
        }))
1✔
4436
        .unwrap();
1✔
4437

1✔
4438
        db.update_project(update).await.unwrap();
1✔
4439

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

1✔
4516
        db.update_project(update).await.unwrap();
1✔
4517

1✔
4518
        let update: UpdateProject = serde_json::from_value(json!({
1✔
4519
            "id": project_id.to_string(),
1✔
4520
            "layers": [{
1✔
4521
                "name": "NDVI",
1✔
4522
                "workflow": workflow_id.to_string(),
1✔
4523
                "visibility": {
1✔
4524
                    "data": true,
1✔
4525
                    "legend": false
1✔
4526
                },
1✔
4527
                "symbology": {
1✔
4528
                    "type": "raster",
1✔
4529
                    "opacity": 1,
1✔
4530
                    "rasterColorizer": {
1✔
4531
                        "type": "singleBand",
1✔
4532
                        "band": 0,
1✔
4533
                        "bandColorizer": {
1✔
4534
                            "type": "linearGradient",
1✔
4535
                            "breakpoints": [{
1✔
4536
                                "value": 1,
1✔
4537
                                "color": [0, 0, 4, 255]
1✔
4538
                            }, {
1✔
4539
                                "value": 17.933_333_333_333_334,
1✔
4540
                                "color": [11, 9, 36, 255]
1✔
4541
                            }, {
1✔
4542
                                "value": 34.866_666_666_666_67,
1✔
4543
                                "color": [32, 17, 75, 255]
1✔
4544
                            }, {
1✔
4545
                                "value": 51.800_000_000_000_004,
1✔
4546
                                "color": [59, 15, 112, 255]
1✔
4547
                            }, {
1✔
4548
                                "value": 68.733_333_333_333_33,
1✔
4549
                                "color": [87, 21, 126, 255]
1✔
4550
                            }, {
1✔
4551
                                "value": 85.666_666_666_666_66,
1✔
4552
                                "color": [114, 31, 129, 255]
1✔
4553
                            }, {
1✔
4554
                                "value": 102.6,
1✔
4555
                                "color": [140, 41, 129, 255]
1✔
4556
                            }, {
1✔
4557
                                "value": 119.533_333_333_333_32,
1✔
4558
                                "color": [168, 50, 125, 255]
1✔
4559
                            }, {
1✔
4560
                                "value": 136.466_666_666_666_67,
1✔
4561
                                "color": [196, 60, 117, 255]
1✔
4562
                            }, {
1✔
4563
                                "value": 153.4,
1✔
4564
                                "color": [222, 73, 104, 255]
1✔
4565
                            }, {
1✔
4566
                                "value": 170.333_333_333_333_31,
1✔
4567
                                "color": [241, 96, 93, 255]
1✔
4568
                            }, {
1✔
4569
                                "value": 187.266_666_666_666_65,
1✔
4570
                                "color": [250, 127, 94, 255]
1✔
4571
                            }, {
1✔
4572
                                "value": 204.2,
1✔
4573
                                "color": [254, 159, 109, 255]
1✔
4574
                            }, {
1✔
4575
                                "value": 221.133_333_333_333_33,
1✔
4576
                                "color": [254, 191, 132, 255]
1✔
4577
                            }, {
1✔
4578
                                "value": 238.066_666_666_666_63,
1✔
4579
                                "color": [253, 222, 160, 255]
1✔
4580
                            }, {
1✔
4581
                                "value": 255,
1✔
4582
                                "color": [252, 253, 191, 255]
1✔
4583
                            }],
1✔
4584
                            "noDataColor": [0, 0, 0, 0],
1✔
4585
                            "overColor": [255, 255, 255, 127],
1✔
4586
                            "underColor": [255, 255, 255, 127]
1✔
4587
                        }
1✔
4588
                    }
1✔
4589
                }
1✔
4590
            }]
1✔
4591
        }))
1✔
4592
        .unwrap();
1✔
4593

4594
        // run two updates concurrently
4595
        let (r0, r1) = join!(db.update_project(update.clone()), db.update_project(update));
1✔
4596

4597
        assert!(r0.is_ok());
1✔
4598
        assert!(r1.is_ok());
1✔
4599
    }
1✔
4600

4601
    #[ge_context::test]
2✔
4602
    #[allow(clippy::too_many_lines)]
4603
    async fn it_resolves_dataset_names_to_ids(app_ctx: ProPostgresContext<NoTls>) {
1✔
4604
        let admin_session = UserSession::admin_session();
1✔
4605
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
4606

1✔
4607
        let loading_info = OgrSourceDataset {
1✔
4608
            file_name: PathBuf::from("test.csv"),
1✔
4609
            layer_name: "test.csv".to_owned(),
1✔
4610
            data_type: Some(VectorDataType::MultiPoint),
1✔
4611
            time: OgrSourceDatasetTimeType::Start {
1✔
4612
                start_field: "start".to_owned(),
1✔
4613
                start_format: OgrSourceTimeFormat::Auto,
1✔
4614
                duration: OgrSourceDurationSpec::Zero,
1✔
4615
            },
1✔
4616
            default_geometry: None,
1✔
4617
            columns: Some(OgrSourceColumnSpec {
1✔
4618
                format_specifics: Some(FormatSpecifics::Csv {
1✔
4619
                    header: CsvHeader::Auto,
1✔
4620
                }),
1✔
4621
                x: "x".to_owned(),
1✔
4622
                y: None,
1✔
4623
                int: vec![],
1✔
4624
                float: vec![],
1✔
4625
                text: vec![],
1✔
4626
                bool: vec![],
1✔
4627
                datetime: vec![],
1✔
4628
                rename: None,
1✔
4629
            }),
1✔
4630
            force_ogr_time_filter: false,
1✔
4631
            force_ogr_spatial_filter: false,
1✔
4632
            on_error: OgrSourceErrorSpec::Ignore,
1✔
4633
            sql_query: None,
1✔
4634
            attribute_query: None,
1✔
4635
            cache_ttl: CacheTtlSeconds::default(),
1✔
4636
        };
1✔
4637

1✔
4638
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
4639
            OgrSourceDataset,
1✔
4640
            VectorResultDescriptor,
1✔
4641
            VectorQueryRectangle,
1✔
4642
        > {
1✔
4643
            loading_info: loading_info.clone(),
1✔
4644
            result_descriptor: VectorResultDescriptor {
1✔
4645
                data_type: VectorDataType::MultiPoint,
1✔
4646
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
4647
                columns: [(
1✔
4648
                    "foo".to_owned(),
1✔
4649
                    VectorColumnInfo {
1✔
4650
                        data_type: FeatureDataType::Float,
1✔
4651
                        measurement: Measurement::Unitless,
1✔
4652
                    },
1✔
4653
                )]
1✔
4654
                .into_iter()
1✔
4655
                .collect(),
1✔
4656
                time: None,
1✔
4657
                bbox: None,
1✔
4658
            },
1✔
4659
            phantom: Default::default(),
1✔
4660
        });
1✔
4661

4662
        let DatasetIdAndName {
4663
            id: dataset_id1,
1✔
4664
            name: dataset_name1,
1✔
4665
        } = db
1✔
4666
            .add_dataset(
1✔
4667
                AddDataset {
1✔
4668
                    name: Some(DatasetName::new(None, "my_dataset".to_owned())),
1✔
4669
                    display_name: "Ogr Test".to_owned(),
1✔
4670
                    description: "desc".to_owned(),
1✔
4671
                    source_operator: "OgrSource".to_owned(),
1✔
4672
                    symbology: None,
1✔
4673
                    provenance: Some(vec![Provenance {
1✔
4674
                        citation: "citation".to_owned(),
1✔
4675
                        license: "license".to_owned(),
1✔
4676
                        uri: "uri".to_owned(),
1✔
4677
                    }]),
1✔
4678
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
4679
                },
1✔
4680
                meta_data.clone(),
1✔
4681
            )
1✔
4682
            .await
1✔
4683
            .unwrap();
1✔
4684

4685
        let DatasetIdAndName {
4686
            id: dataset_id2,
1✔
4687
            name: dataset_name2,
1✔
4688
        } = db
1✔
4689
            .add_dataset(
1✔
4690
                AddDataset {
1✔
4691
                    name: Some(DatasetName::new(
1✔
4692
                        Some(admin_session.user.id.to_string()),
1✔
4693
                        "my_dataset".to_owned(),
1✔
4694
                    )),
1✔
4695
                    display_name: "Ogr Test".to_owned(),
1✔
4696
                    description: "desc".to_owned(),
1✔
4697
                    source_operator: "OgrSource".to_owned(),
1✔
4698
                    symbology: None,
1✔
4699
                    provenance: Some(vec![Provenance {
1✔
4700
                        citation: "citation".to_owned(),
1✔
4701
                        license: "license".to_owned(),
1✔
4702
                        uri: "uri".to_owned(),
1✔
4703
                    }]),
1✔
4704
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
4705
                },
1✔
4706
                meta_data,
1✔
4707
            )
1✔
4708
            .await
1✔
4709
            .unwrap();
1✔
4710

4711
        assert_eq!(
1✔
4712
            db.resolve_dataset_name_to_id(&dataset_name1)
1✔
4713
                .await
1✔
4714
                .unwrap()
1✔
4715
                .unwrap(),
1✔
4716
            dataset_id1
4717
        );
4718
        assert_eq!(
1✔
4719
            db.resolve_dataset_name_to_id(&dataset_name2)
1✔
4720
                .await
1✔
4721
                .unwrap()
1✔
4722
                .unwrap(),
1✔
4723
            dataset_id2
4724
        );
4725
    }
1✔
4726

4727
    #[ge_context::test]
2✔
4728
    #[allow(clippy::too_many_lines)]
4729
    async fn it_bulk_updates_quota(app_ctx: ProPostgresContext<NoTls>) {
1✔
4730
        let admin_session = UserSession::admin_session();
1✔
4731
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
4732

4733
        let user1 = app_ctx
1✔
4734
            .register_user(UserRegistration {
1✔
4735
                email: "user1@example.com".into(),
1✔
4736
                password: "12345678".into(),
1✔
4737
                real_name: "User1".into(),
1✔
4738
            })
1✔
4739
            .await
1✔
4740
            .unwrap();
1✔
4741

4742
        let user2 = app_ctx
1✔
4743
            .register_user(UserRegistration {
1✔
4744
                email: "user2@example.com".into(),
1✔
4745
                password: "12345678".into(),
1✔
4746
                real_name: "User2".into(),
1✔
4747
            })
1✔
4748
            .await
1✔
4749
            .unwrap();
1✔
4750

1✔
4751
        // single item in bulk
1✔
4752
        db.bulk_increment_quota_used([(user1, 1)]).await.unwrap();
1✔
4753

4754
        assert_eq!(db.quota_used_by_user(&user1).await.unwrap(), 1);
1✔
4755

4756
        // multiple items in bulk
4757
        db.bulk_increment_quota_used([(user1, 1), (user2, 3)])
1✔
4758
            .await
1✔
4759
            .unwrap();
1✔
4760

4761
        assert_eq!(db.quota_used_by_user(&user1).await.unwrap(), 2);
1✔
4762
        assert_eq!(db.quota_used_by_user(&user2).await.unwrap(), 3);
1✔
4763
    }
1✔
4764

4765
    async fn it_handles_oidc_tokens(app_ctx: ProPostgresContext<NoTls>) {
2✔
4766
        let external_user_claims = UserClaims {
2✔
4767
            external_id: SubjectIdentifier::new("Foo bar Id".into()),
2✔
4768
            email: "foo@bar.de".into(),
2✔
4769
            real_name: "Foo Bar".into(),
2✔
4770
        };
2✔
4771
        let tokens = OidcTokens {
2✔
4772
            access: AccessToken::new("FIRST_ACCESS_TOKEN".into()),
2✔
4773
            refresh: Some(RefreshToken::new("FIRST_REFRESH_TOKEN".into())),
2✔
4774
            expires_in: Duration::seconds(2),
2✔
4775
        };
2✔
4776

4777
        let login_result = app_ctx
2✔
4778
            .login_external(external_user_claims.clone(), tokens)
2✔
4779
            .await;
2✔
4780
        assert!(login_result.is_ok());
2✔
4781

4782
        let session_id = login_result.unwrap().id;
2✔
4783

4784
        let access_token = app_ctx.get_access_token(session_id).await.unwrap();
2✔
4785

2✔
4786
        assert_eq!(
2✔
4787
            "FIRST_ACCESS_TOKEN".to_string(),
2✔
4788
            access_token.secret().to_owned()
2✔
4789
        );
2✔
4790

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

4794
        let access_token = app_ctx.get_access_token(session_id).await.unwrap();
2✔
4795

2✔
4796
        assert_eq!(
2✔
4797
            "SECOND_ACCESS_TOKEN".to_string(),
2✔
4798
            access_token.secret().to_owned()
2✔
4799
        );
2✔
4800
    }
2✔
4801

4802
    pub fn oidc_only_refresh() -> (Server, impl Fn() -> OidcManager) {
1✔
4803
        let mock_refresh_server_config = MockRefreshServerConfig {
1✔
4804
            expected_discoveries: 1,
1✔
4805
            token_duration: std::time::Duration::from_secs(2),
1✔
4806
            creates_first_token: false,
1✔
4807
            first_access_token: "FIRST_ACCESS_TOKEN".to_string(),
1✔
4808
            first_refresh_token: "FIRST_REFRESH_TOKEN".to_string(),
1✔
4809
            second_access_token: "SECOND_ACCESS_TOKEN".to_string(),
1✔
4810
            second_refresh_token: "SECOND_REFRESH_TOKEN".to_string(),
1✔
4811
            client_side_password: None,
1✔
4812
        };
1✔
4813

1✔
4814
        let (server, oidc_manager) = mock_refresh_server(mock_refresh_server_config);
1✔
4815

1✔
4816
        (server, move || {
1✔
4817
            OidcManager::from_oidc_with_static_tokens(oidc_manager.clone())
1✔
4818
        })
1✔
4819
    }
1✔
4820

4821
    #[ge_context::test(oidc_db = "oidc_only_refresh")]
2✔
4822
    async fn it_handles_oidc_tokens_without_encryption(app_ctx: ProPostgresContext<NoTls>) {
1✔
4823
        it_handles_oidc_tokens(app_ctx).await;
1✔
4824
    }
1✔
4825

4826
    pub fn oidc_only_refresh_with_encryption() -> (Server, impl Fn() -> OidcManager) {
1✔
4827
        let mock_refresh_server_config = MockRefreshServerConfig {
1✔
4828
            expected_discoveries: 1,
1✔
4829
            token_duration: std::time::Duration::from_secs(2),
1✔
4830
            creates_first_token: false,
1✔
4831
            first_access_token: "FIRST_ACCESS_TOKEN".to_string(),
1✔
4832
            first_refresh_token: "FIRST_REFRESH_TOKEN".to_string(),
1✔
4833
            second_access_token: "SECOND_ACCESS_TOKEN".to_string(),
1✔
4834
            second_refresh_token: "SECOND_REFRESH_TOKEN".to_string(),
1✔
4835
            client_side_password: Some("password123".to_string()),
1✔
4836
        };
1✔
4837

1✔
4838
        let (server, oidc_manager) = mock_refresh_server(mock_refresh_server_config);
1✔
4839

1✔
4840
        (server, move || {
1✔
4841
            OidcManager::from_oidc_with_static_tokens(oidc_manager.clone())
1✔
4842
        })
1✔
4843
    }
1✔
4844

4845
    #[ge_context::test(oidc_db = "oidc_only_refresh_with_encryption")]
2✔
4846
    async fn it_handles_oidc_tokens_with_encryption(app_ctx: ProPostgresContext<NoTls>) {
1✔
4847
        it_handles_oidc_tokens(app_ctx).await;
1✔
4848
    }
1✔
4849
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc