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

geo-engine / geoengine / 10182401222

31 Jul 2024 02:42PM UTC coverage: 91.14% (+0.07%) from 91.068%
10182401222

Pull #970

github

web-flow
Merge f0fcf6203 into c97f87c56
Pull Request #970: FAIR dataset deletion

1798 of 1863 new or added lines in 13 files covered. (96.51%)

16 existing lines in 9 files now uncovered.

132740 of 145644 relevant lines covered (91.14%)

52956.57 hits per line

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

96.75
/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_providers_from_directory;
4
use crate::api::model::services::Volume;
5
use crate::contexts::{
6
    initialize_database, ApplicationContext, CurrentSchemaMigration, MigrationResult,
7
    PostgresContext, QueryContextImpl, SessionId,
8
};
9
use crate::contexts::{GeoEngineDb, SessionContext};
10
use crate::datasets::upload::Volumes;
11
use crate::datasets::DatasetName;
12
use crate::error::{self, Error, Result};
13
use crate::pro::api::cli::add_datasets_from_directory;
14
use crate::pro::layers::add_from_directory::{
15
    add_layer_collections_from_directory, add_layers_from_directory,
16
    add_pro_providers_from_directory,
17
};
18
use crate::pro::quota::{initialize_quota_tracking, QuotaTrackingFactory};
19
use crate::pro::tasks::{ProTaskManager, ProTaskManagerBackend};
20
use crate::pro::users::OidcManager;
21
use crate::pro::users::{UserAuth, UserSession};
22
use crate::pro::util::config::{Cache, Oidc, Quota};
23
use crate::tasks::SimpleTaskManagerContext;
24
use async_trait::async_trait;
25
use bb8_postgres::{
26
    bb8::Pool,
27
    bb8::PooledConnection,
28
    tokio_postgres::{tls::MakeTlsConnect, tls::TlsConnect, Config, Socket},
29
    PostgresConnectionManager,
30
};
31
use geoengine_datatypes::raster::TilingSpecification;
32
use geoengine_datatypes::util::test::TestDefault;
33
use geoengine_datatypes::util::Identifier;
34
use geoengine_operators::engine::{
35
    ChunkByteSize, ExecutionContextExtensions, QueryContextExtensions,
36
};
37
use geoengine_operators::pro::cache::shared_cache::SharedCache;
38
use geoengine_operators::pro::meta::quota::{ComputationContext, QuotaChecker};
39
use geoengine_operators::util::create_rayon_thread_pool;
40
use log::info;
41
use rayon::ThreadPool;
42
use snafu::ResultExt;
43
use std::path::PathBuf;
44
use std::sync::Arc;
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(
100✔
76
        config: Config,
100✔
77
        tls: Tls,
100✔
78
        exe_ctx_tiling_spec: TilingSpecification,
100✔
79
        query_ctx_chunk_size: ChunkByteSize,
100✔
80
        quota_config: Quota,
100✔
81
        oidc_db: OidcManager,
100✔
82
    ) -> Result<Self> {
100✔
83
        let pg_mgr = PostgresConnectionManager::new(config, tls);
100✔
84

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

87
        Self::create_pro_database(pool.get().await?).await?;
2,135✔
88

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

100✔
97
        Ok(ProPostgresContext {
100✔
98
            task_manager: Default::default(),
100✔
99
            thread_pool: create_rayon_thread_pool(0),
100✔
100
            exe_ctx_tiling_spec,
100✔
101
            query_ctx_chunk_size,
100✔
102
            oidc_manager: oidc_db,
100✔
103
            quota,
100✔
104
            pool,
100✔
105
            volumes: Default::default(),
100✔
106
            tile_cache: Arc::new(SharedCache::test_default()),
100✔
107
        })
100✔
108
    }
100✔
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(
100✔
213
        mut conn: PooledConnection<'_, PostgresConnectionManager<Tls>>,
100✔
214
    ) -> Result<bool> {
100✔
215
        PostgresContext::maybe_clear_database(&conn).await?;
100✔
216

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

224
        Ok(migration == MigrationResult::CreatedDatabase)
100✔
225
    }
100✔
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 {
176✔
240
        PostgresSessionContext {
176✔
241
            session,
176✔
242
            context: self.clone(),
176✔
243
        }
176✔
244
    }
176✔
245

246
    async fn session_by_id(&self, session_id: SessionId) -> Result<Self::Session> {
71✔
247
        self.user_session_by_id(session_id)
71✔
248
            .await
759✔
249
            .map_err(Box::new)
71✔
250
            .context(error::Unauthorized)
71✔
251
    }
71✔
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 {
187✔
295
        ProPostgresDb::new(self.context.pool.clone(), self.session.clone())
187✔
296
    }
187✔
297

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

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

3✔
305
        let mut extensions = QueryContextExtensions::default();
3✔
306
        extensions.insert(
3✔
307
            self.context
3✔
308
                .quota
3✔
309
                .create_quota_tracking(&self.session, ComputationContext::new()),
3✔
310
        );
3✔
311
        extensions.insert(Box::new(QuotaCheckerImpl { user_db: self.db() }) as QuotaChecker);
3✔
312
        extensions.insert(self.context.tile_cache.clone());
3✔
313

3✔
314
        Ok(QueryContextImpl::new_with_extensions(
3✔
315
            self.context.query_ctx_chunk_size,
3✔
316
            self.context.thread_pool.clone(),
3✔
317
            extensions,
3✔
318
        ))
3✔
319
    }
3✔
320

321
    fn execution_context(&self) -> Result<Self::ExecutionContext> {
6✔
322
        let extensions = ExecutionContextExtensions::default();
6✔
323

6✔
324
        Ok(
6✔
325
            ExecutionContextImpl::<ProPostgresDb<Tls>>::new_with_extensions(
6✔
326
                self.db(),
6✔
327
                self.context.thread_pool.clone(),
6✔
328
                self.context.exe_ctx_tiling_spec,
6✔
329
                extensions,
6✔
330
            ),
6✔
331
        )
6✔
332
    }
6✔
333

334
    fn volumes(&self) -> Result<Vec<Volume>> {
×
335
        Ok(self
×
336
            .context
×
337
            .volumes
×
338
            .volumes
×
339
            .iter()
×
340
            .map(|v| Volume {
×
341
                name: v.name.0.clone(),
×
342
                path: if self.session.is_admin() {
×
343
                    Some(v.path.to_string_lossy().to_string())
×
344
                } else {
345
                    None
×
346
                },
347
            })
×
348
            .collect())
×
349
    }
×
350

351
    fn session(&self) -> &Self::Session {
×
352
        &self.session
×
353
    }
×
354
}
355

356
#[derive(Debug)]
357
pub struct ProPostgresDb<Tls>
358
where
359
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
360
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
361
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
362
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
363
{
364
    pub(crate) conn_pool: Pool<PostgresConnectionManager<Tls>>,
365
    pub(crate) session: UserSession,
366
}
367

368
impl<Tls> ProPostgresDb<Tls>
369
where
370
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
371
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
372
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
373
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
374
{
375
    pub fn new(conn_pool: Pool<PostgresConnectionManager<Tls>>, session: UserSession) -> Self {
288✔
376
        Self { conn_pool, session }
288✔
377
    }
288✔
378

379
    /// Check whether the namepsace of the given dataset is allowed for insertion
380
    pub(crate) fn check_namespace(&self, id: &DatasetName) -> Result<()> {
38✔
381
        let is_ok = match &id.namespace {
38✔
382
            Some(namespace) => namespace.as_str() == self.session.user.id.to_string(),
31✔
383
            None => self.session.is_admin(),
7✔
384
        };
385

386
        if is_ok {
38✔
387
            Ok(())
38✔
388
        } else {
389
            Err(Error::InvalidDatasetIdNamespace)
×
390
        }
391
    }
38✔
392
}
393

394
impl<Tls> GeoEngineDb for ProPostgresDb<Tls>
395
where
396
    Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static + std::fmt::Debug,
397
    <Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
398
    <Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
399
    <<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
400
{
401
}
402

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

484
    #[ge_context::test]
3✔
485
    async fn test(app_ctx: ProPostgresContext<NoTls>) {
1✔
486
        anonymous(&app_ctx).await;
44✔
487

488
        let _user_id = user_reg_login(&app_ctx).await;
36✔
489

490
        let session = app_ctx
1✔
491
            .login(UserCredentials {
1✔
492
                email: "foo@example.com".into(),
1✔
493
                password: "secret123".into(),
1✔
494
            })
1✔
495
            .await
11✔
496
            .unwrap();
1✔
497

1✔
498
        create_projects(&app_ctx, &session).await;
116✔
499

500
        let projects = list_projects(&app_ctx, &session).await;
13✔
501

502
        set_session(&app_ctx, &projects).await;
31✔
503

504
        let project_id = projects[0].id;
1✔
505

1✔
506
        update_projects(&app_ctx, &session, project_id).await;
205✔
507

508
        add_permission(&app_ctx, &session, project_id).await;
42✔
509

510
        delete_project(&app_ctx, &session, project_id).await;
11✔
511
    }
1✔
512

513
    #[ge_context::test]
3✔
514
    async fn test_external(app_ctx: ProPostgresContext<NoTls>) {
1✔
515
        anonymous(&app_ctx).await;
30✔
516

517
        let session = external_user_login_twice(&app_ctx).await;
57✔
518

519
        create_projects(&app_ctx, &session).await;
72✔
520

521
        let projects = list_projects(&app_ctx, &session).await;
13✔
522

523
        set_session_external(&app_ctx, &projects).await;
33✔
524

525
        let project_id = projects[0].id;
1✔
526

1✔
527
        update_projects(&app_ctx, &session, project_id).await;
210✔
528

529
        add_permission(&app_ctx, &session, project_id).await;
28✔
530

531
        delete_project(&app_ctx, &session, project_id).await;
9✔
532
    }
1✔
533

534
    fn tokens_from_duration(duration: Duration) -> OidcTokens {
3✔
535
        OidcTokens {
3✔
536
            access: AccessToken::new("AccessToken".to_string()),
3✔
537
            refresh: None,
3✔
538
            expires_in: duration,
3✔
539
        }
3✔
540
    }
3✔
541

542
    async fn set_session(app_ctx: &ProPostgresContext<NoTls>, projects: &[ProjectListing]) {
1✔
543
        let credentials = UserCredentials {
1✔
544
            email: "foo@example.com".into(),
1✔
545
            password: "secret123".into(),
1✔
546
        };
1✔
547

548
        let session = app_ctx.login(credentials).await.unwrap();
11✔
549

1✔
550
        set_session_in_database(app_ctx, projects, session).await;
20✔
551
    }
1✔
552

553
    async fn set_session_external(
1✔
554
        app_ctx: &ProPostgresContext<NoTls>,
1✔
555
        projects: &[ProjectListing],
1✔
556
    ) {
1✔
557
        let external_user_claims = UserClaims {
1✔
558
            external_id: SubjectIdentifier::new("Foo bar Id".into()),
1✔
559
            email: "foo@bar.de".into(),
1✔
560
            real_name: "Foo Bar".into(),
1✔
561
        };
1✔
562

563
        let session = app_ctx
1✔
564
            .login_external(
1✔
565
                external_user_claims,
1✔
566
                tokens_from_duration(Duration::minutes(10)),
1✔
567
            )
1✔
568
            .await
13✔
569
            .unwrap();
1✔
570

1✔
571
        set_session_in_database(app_ctx, projects, session).await;
20✔
572
    }
1✔
573

574
    async fn set_session_in_database(
2✔
575
        app_ctx: &ProPostgresContext<NoTls>,
2✔
576
        projects: &[ProjectListing],
2✔
577
        session: UserSession,
2✔
578
    ) {
2✔
579
        let db = app_ctx.session_context(session.clone()).db();
2✔
580

2✔
581
        db.set_session_project(projects[0].id).await.unwrap();
6✔
582

583
        assert_eq!(
2✔
584
            app_ctx.session_by_id(session.id).await.unwrap().project,
14✔
585
            Some(projects[0].id)
2✔
586
        );
587

588
        let rect = STRectangle::new_unchecked(SpatialReference::epsg_4326(), 0., 1., 2., 3., 1, 2);
2✔
589
        db.set_session_view(rect.clone()).await.unwrap();
6✔
590
        assert_eq!(
2✔
591
            app_ctx.session_by_id(session.id).await.unwrap().view,
14✔
592
            Some(rect)
2✔
593
        );
594
    }
2✔
595

596
    async fn delete_project(
2✔
597
        app_ctx: &ProPostgresContext<NoTls>,
2✔
598
        session: &UserSession,
2✔
599
        project_id: ProjectId,
2✔
600
    ) {
2✔
601
        let db = app_ctx.session_context(session.clone()).db();
2✔
602

2✔
603
        db.delete_project(project_id).await.unwrap();
12✔
604

2✔
605
        assert!(db.load_project(project_id).await.is_err());
8✔
606
    }
2✔
607

608
    async fn add_permission(
2✔
609
        app_ctx: &ProPostgresContext<NoTls>,
2✔
610
        session: &UserSession,
2✔
611
        project_id: ProjectId,
2✔
612
    ) {
2✔
613
        let db = app_ctx.session_context(session.clone()).db();
2✔
614

2✔
615
        assert!(db
2✔
616
            .has_permission(project_id, Permission::Owner)
2✔
617
            .await
10✔
618
            .unwrap());
2✔
619

620
        let user2 = app_ctx
2✔
621
            .register_user(UserRegistration {
2✔
622
                email: "user2@example.com".into(),
2✔
623
                password: "12345678".into(),
2✔
624
                real_name: "User2".into(),
2✔
625
            })
2✔
626
            .await
19✔
627
            .unwrap();
2✔
628

629
        let session2 = app_ctx
2✔
630
            .login(UserCredentials {
2✔
631
                email: "user2@example.com".into(),
2✔
632
                password: "12345678".into(),
2✔
633
            })
2✔
634
            .await
13✔
635
            .unwrap();
2✔
636

2✔
637
        let db2 = app_ctx.session_context(session2.clone()).db();
2✔
638
        assert!(!db2
2✔
639
            .has_permission(project_id, Permission::Owner)
2✔
640
            .await
10✔
641
            .unwrap());
2✔
642

643
        db.add_permission(user2.into(), project_id, Permission::Read)
2✔
644
            .await
13✔
645
            .unwrap();
2✔
646

2✔
647
        assert!(db2
2✔
648
            .has_permission(project_id, Permission::Read)
2✔
649
            .await
5✔
650
            .unwrap());
2✔
651
    }
2✔
652

653
    #[allow(clippy::too_many_lines)]
654
    async fn update_projects(
2✔
655
        app_ctx: &ProPostgresContext<NoTls>,
2✔
656
        session: &UserSession,
2✔
657
        project_id: ProjectId,
2✔
658
    ) {
2✔
659
        let db = app_ctx.session_context(session.clone()).db();
2✔
660

661
        let project = db
2✔
662
            .load_project_version(project_id, LoadVersion::Latest)
2✔
663
            .await
84✔
664
            .unwrap();
2✔
665

666
        let layer_workflow_id = db
2✔
667
            .register_workflow(Workflow {
2✔
668
                operator: TypedOperator::Vector(
2✔
669
                    MockPointSource {
2✔
670
                        params: MockPointSourceParams {
2✔
671
                            points: vec![Coordinate2D::new(1., 2.); 3],
2✔
672
                        },
2✔
673
                    }
2✔
674
                    .boxed(),
2✔
675
                ),
2✔
676
            })
2✔
677
            .await
6✔
678
            .unwrap();
2✔
679

2✔
680
        assert!(db.load_workflow(&layer_workflow_id).await.is_ok());
5✔
681

682
        let plot_workflow_id = db
2✔
683
            .register_workflow(Workflow {
2✔
684
                operator: Statistics {
2✔
685
                    params: StatisticsParams {
2✔
686
                        column_names: vec![],
2✔
687
                        percentiles: vec![],
2✔
688
                    },
2✔
689
                    sources: MultipleRasterOrSingleVectorSource {
2✔
690
                        source: Raster(vec![]),
2✔
691
                    },
2✔
692
                }
2✔
693
                .boxed()
2✔
694
                .into(),
2✔
695
            })
2✔
696
            .await
6✔
697
            .unwrap();
2✔
698

2✔
699
        assert!(db.load_workflow(&plot_workflow_id).await.is_ok());
6✔
700

701
        // add a plot
702
        let update = UpdateProject {
2✔
703
            id: project.id,
2✔
704
            name: Some("Test9 Updated".into()),
2✔
705
            description: None,
2✔
706
            layers: Some(vec![LayerUpdate::UpdateOrInsert(ProjectLayer {
2✔
707
                workflow: layer_workflow_id,
2✔
708
                name: "TestLayer".into(),
2✔
709
                symbology: PointSymbology::default().into(),
2✔
710
                visibility: Default::default(),
2✔
711
            })]),
2✔
712
            plots: Some(vec![PlotUpdate::UpdateOrInsert(Plot {
2✔
713
                workflow: plot_workflow_id,
2✔
714
                name: "Test Plot".into(),
2✔
715
            })]),
2✔
716
            bounds: None,
2✔
717
            time_step: None,
2✔
718
        };
2✔
719
        db.update_project(update).await.unwrap();
158✔
720

721
        let versions = db.list_project_versions(project_id).await.unwrap();
24✔
722
        assert_eq!(versions.len(), 2);
2✔
723

724
        // add second plot
725
        let update = UpdateProject {
2✔
726
            id: project.id,
2✔
727
            name: Some("Test9 Updated".into()),
2✔
728
            description: None,
2✔
729
            layers: Some(vec![LayerUpdate::UpdateOrInsert(ProjectLayer {
2✔
730
                workflow: layer_workflow_id,
2✔
731
                name: "TestLayer".into(),
2✔
732
                symbology: PointSymbology::default().into(),
2✔
733
                visibility: Default::default(),
2✔
734
            })]),
2✔
735
            plots: Some(vec![
2✔
736
                PlotUpdate::UpdateOrInsert(Plot {
2✔
737
                    workflow: plot_workflow_id,
2✔
738
                    name: "Test Plot".into(),
2✔
739
                }),
2✔
740
                PlotUpdate::UpdateOrInsert(Plot {
2✔
741
                    workflow: plot_workflow_id,
2✔
742
                    name: "Test Plot".into(),
2✔
743
                }),
2✔
744
            ]),
2✔
745
            bounds: None,
2✔
746
            time_step: None,
2✔
747
        };
2✔
748
        db.update_project(update).await.unwrap();
53✔
749

750
        let versions = db.list_project_versions(project_id).await.unwrap();
14✔
751
        assert_eq!(versions.len(), 3);
2✔
752

753
        // delete plots
754
        let update = UpdateProject {
2✔
755
            id: project.id,
2✔
756
            name: None,
2✔
757
            description: None,
2✔
758
            layers: None,
2✔
759
            plots: Some(vec![]),
2✔
760
            bounds: None,
2✔
761
            time_step: None,
2✔
762
        };
2✔
763
        db.update_project(update).await.unwrap();
45✔
764

765
        let versions = db.list_project_versions(project_id).await.unwrap();
14✔
766
        assert_eq!(versions.len(), 4);
2✔
767
    }
2✔
768

769
    async fn list_projects(
2✔
770
        app_ctx: &ProPostgresContext<NoTls>,
2✔
771
        session: &UserSession,
2✔
772
    ) -> Vec<ProjectListing> {
2✔
773
        let options = ProjectListOptions {
2✔
774
            order: OrderBy::NameDesc,
2✔
775
            offset: 0,
2✔
776
            limit: 2,
2✔
777
        };
2✔
778

2✔
779
        let db = app_ctx.session_context(session.clone()).db();
2✔
780

781
        let projects = db.list_projects(options).await.unwrap();
26✔
782

2✔
783
        assert_eq!(projects.len(), 2);
2✔
784
        assert_eq!(projects[0].name, "Test9");
2✔
785
        assert_eq!(projects[1].name, "Test8");
2✔
786
        projects
2✔
787
    }
2✔
788

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

792
        for i in 0..10 {
22✔
793
            let create = CreateProject {
20✔
794
                name: format!("Test{i}"),
20✔
795
                description: format!("Test{}", 10 - i),
20✔
796
                bounds: STRectangle::new(
20✔
797
                    SpatialReferenceOption::Unreferenced,
20✔
798
                    0.,
20✔
799
                    0.,
20✔
800
                    1.,
20✔
801
                    1.,
20✔
802
                    0,
20✔
803
                    1,
20✔
804
                )
20✔
805
                .unwrap(),
20✔
806
                time_step: None,
20✔
807
            };
20✔
808
            db.create_project(create).await.unwrap();
188✔
809
        }
810
    }
2✔
811

812
    async fn user_reg_login(app_ctx: &ProPostgresContext<NoTls>) -> UserId {
1✔
813
        let user_registration = UserRegistration {
1✔
814
            email: "foo@example.com".into(),
1✔
815
            password: "secret123".into(),
1✔
816
            real_name: "Foo Bar".into(),
1✔
817
        };
1✔
818

819
        let user_id = app_ctx.register_user(user_registration).await.unwrap();
11✔
820

1✔
821
        let credentials = UserCredentials {
1✔
822
            email: "foo@example.com".into(),
1✔
823
            password: "secret123".into(),
1✔
824
        };
1✔
825

826
        let session = app_ctx.login(credentials).await.unwrap();
11✔
827

1✔
828
        let db = app_ctx.session_context(session.clone()).db();
1✔
829

1✔
830
        app_ctx.session_by_id(session.id).await.unwrap();
7✔
831

1✔
832
        db.logout().await.unwrap();
3✔
833

1✔
834
        assert!(app_ctx.session_by_id(session.id).await.is_err());
4✔
835

836
        user_id
1✔
837
    }
1✔
838

839
    async fn external_user_login_twice(app_ctx: &ProPostgresContext<NoTls>) -> UserSession {
1✔
840
        let external_user_claims = UserClaims {
1✔
841
            external_id: SubjectIdentifier::new("Foo bar Id".into()),
1✔
842
            email: "foo@bar.de".into(),
1✔
843
            real_name: "Foo Bar".into(),
1✔
844
        };
1✔
845
        let duration = Duration::minutes(30);
1✔
846

847
        let login_result = app_ctx
1✔
848
            .login_external(external_user_claims.clone(), tokens_from_duration(duration))
1✔
849
            .await;
23✔
850
        assert!(login_result.is_ok());
1✔
851

852
        let session_1 = login_result.unwrap();
1✔
853
        let user_id = session_1.user.id; //TODO: Not a deterministic test.
1✔
854

1✔
855
        let db1 = app_ctx.session_context(session_1.clone()).db();
1✔
856

1✔
857
        assert!(session_1.user.email.is_some());
1✔
858
        assert_eq!(session_1.user.email.unwrap(), "foo@bar.de");
1✔
859
        assert!(session_1.user.real_name.is_some());
1✔
860
        assert_eq!(session_1.user.real_name.unwrap(), "Foo Bar");
1✔
861

862
        let expected_duration = session_1.created + duration;
1✔
863
        assert_eq!(session_1.valid_until, expected_duration);
1✔
864

865
        assert!(app_ctx.session_by_id(session_1.id).await.is_ok());
7✔
866

867
        assert!(db1.logout().await.is_ok());
3✔
868

869
        assert!(app_ctx.session_by_id(session_1.id).await.is_err());
4✔
870

871
        let duration = Duration::minutes(10);
1✔
872
        let login_result = app_ctx
1✔
873
            .login_external(external_user_claims.clone(), tokens_from_duration(duration))
1✔
874
            .await;
13✔
875
        assert!(login_result.is_ok());
1✔
876

877
        let session_2 = login_result.unwrap();
1✔
878
        let result = session_2.clone();
1✔
879

1✔
880
        assert!(session_2.user.email.is_some()); //TODO: Technically, user details could change for each login. For simplicity, this is not covered yet.
1✔
881
        assert_eq!(session_2.user.email.unwrap(), "foo@bar.de");
1✔
882
        assert!(session_2.user.real_name.is_some());
1✔
883
        assert_eq!(session_2.user.real_name.unwrap(), "Foo Bar");
1✔
884
        assert_eq!(session_2.user.id, user_id);
1✔
885

886
        let expected_duration = session_2.created + duration;
1✔
887
        assert_eq!(session_2.valid_until, expected_duration);
1✔
888

889
        assert!(app_ctx.session_by_id(session_2.id).await.is_ok());
7✔
890

891
        result
1✔
892
    }
1✔
893

894
    async fn anonymous(app_ctx: &ProPostgresContext<NoTls>) {
2✔
895
        let now: DateTime = chrono::offset::Utc::now().into();
2✔
896
        let session = app_ctx.create_anonymous_session().await.unwrap();
16✔
897
        let then: DateTime = chrono::offset::Utc::now().into();
2✔
898

2✔
899
        assert!(session.created >= now && session.created <= then);
2✔
900
        assert!(session.valid_until > session.created);
2✔
901

902
        let session = app_ctx.session_by_id(session.id).await.unwrap();
44✔
903

2✔
904
        let db = app_ctx.session_context(session.clone()).db();
2✔
905

2✔
906
        db.logout().await.unwrap();
6✔
907

2✔
908
        assert!(app_ctx.session_by_id(session.id).await.is_err());
8✔
909
    }
2✔
910

911
    #[ge_context::test]
3✔
912
    async fn it_persists_workflows(app_ctx: ProPostgresContext<NoTls>) {
1✔
913
        let workflow = Workflow {
1✔
914
            operator: TypedOperator::Vector(
1✔
915
                MockPointSource {
1✔
916
                    params: MockPointSourceParams {
1✔
917
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
918
                    },
1✔
919
                }
1✔
920
                .boxed(),
1✔
921
            ),
1✔
922
        };
1✔
923

924
        let session = app_ctx.create_anonymous_session().await.unwrap();
14✔
925
        let ctx = app_ctx.session_context(session);
1✔
926

1✔
927
        let db = ctx.db();
1✔
928
        let id = db.register_workflow(workflow).await.unwrap();
3✔
929

1✔
930
        drop(ctx);
1✔
931

932
        let workflow = db.load_workflow(&id).await.unwrap();
3✔
933

1✔
934
        let json = serde_json::to_string(&workflow).unwrap();
1✔
935
        assert_eq!(
1✔
936
            json,
1✔
937
            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✔
938
        );
1✔
939
    }
1✔
940

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

1✔
975
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
976
            OgrSourceDataset,
1✔
977
            VectorResultDescriptor,
1✔
978
            VectorQueryRectangle,
1✔
979
        > {
1✔
980
            loading_info: loading_info.clone(),
1✔
981
            result_descriptor: VectorResultDescriptor {
1✔
982
                data_type: VectorDataType::MultiPoint,
1✔
983
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
984
                columns: [(
1✔
985
                    "foo".to_owned(),
1✔
986
                    VectorColumnInfo {
1✔
987
                        data_type: FeatureDataType::Float,
1✔
988
                        measurement: Measurement::Unitless,
1✔
989
                    },
1✔
990
                )]
1✔
991
                .into_iter()
1✔
992
                .collect(),
1✔
993
                time: None,
1✔
994
                bbox: None,
1✔
995
            },
1✔
996
            phantom: Default::default(),
1✔
997
        });
1✔
998

999
        let session = app_ctx.create_anonymous_session().await.unwrap();
13✔
1000

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

1✔
1003
        let db = app_ctx.session_context(session.clone()).db();
1✔
1004
        let DatasetIdAndName {
1005
            id: dataset_id,
1✔
1006
            name: dataset_name,
1✔
1007
        } = db
1✔
1008
            .add_dataset(
1✔
1009
                AddDataset {
1✔
1010
                    name: Some(dataset_name.clone()),
1✔
1011
                    display_name: "Ogr Test".to_owned(),
1✔
1012
                    description: "desc".to_owned(),
1✔
1013
                    source_operator: "OgrSource".to_owned(),
1✔
1014
                    symbology: None,
1✔
1015
                    provenance: Some(vec![Provenance {
1✔
1016
                        citation: "citation".to_owned(),
1✔
1017
                        license: "license".to_owned(),
1✔
1018
                        uri: "uri".to_owned(),
1✔
1019
                    }]),
1✔
1020
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1021
                },
1✔
1022
                meta_data,
1✔
1023
            )
1✔
1024
            .await
179✔
1025
            .unwrap();
1✔
1026

1027
        let datasets = db
1✔
1028
            .list_datasets(DatasetListOptions {
1✔
1029
                filter: None,
1✔
1030
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
1031
                offset: 0,
1✔
1032
                limit: 10,
1✔
1033
                tags: None,
1✔
1034
            })
1✔
1035
            .await
15✔
1036
            .unwrap();
1✔
1037

1✔
1038
        assert_eq!(datasets.len(), 1);
1✔
1039

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

1068
        let provenance = db.load_provenance(&dataset_id).await.unwrap();
11✔
1069

1✔
1070
        assert_eq!(
1✔
1071
            provenance,
1✔
1072
            ProvenanceOutput {
1✔
1073
                data: dataset_id.into(),
1✔
1074
                provenance: Some(vec![Provenance {
1✔
1075
                    citation: "citation".to_owned(),
1✔
1076
                    license: "license".to_owned(),
1✔
1077
                    uri: "uri".to_owned(),
1✔
1078
                }])
1✔
1079
            }
1✔
1080
        );
1✔
1081

1082
        let meta_data: Box<dyn MetaData<OgrSourceDataset, _, _>> =
1✔
1083
            db.meta_data(&dataset_id.into()).await.unwrap();
18✔
1084

1085
        assert_eq!(
1✔
1086
            meta_data
1✔
1087
                .loading_info(VectorQueryRectangle {
1✔
1088
                    spatial_bounds: BoundingBox2D::new_unchecked(
1✔
1089
                        (-180., -90.).into(),
1✔
1090
                        (180., 90.).into()
1✔
1091
                    ),
1✔
1092
                    time_interval: TimeInterval::default(),
1✔
1093
                    spatial_resolution: SpatialResolution::zero_point_one(),
1✔
1094
                    attributes: ColumnSelection::all()
1✔
1095
                })
1✔
1096
                .await
×
1097
                .unwrap(),
1✔
1098
            loading_info
1099
        );
1100
    }
1✔
1101

1102
    #[ge_context::test]
3✔
1103
    async fn it_persists_uploads(app_ctx: ProPostgresContext<NoTls>) {
1✔
1104
        let id = UploadId::from_str("2de18cd8-4a38-4111-a445-e3734bc18a80").unwrap();
1✔
1105
        let input = Upload {
1✔
1106
            id,
1✔
1107
            files: vec![FileUpload {
1✔
1108
                id: FileId::from_str("e80afab0-831d-4d40-95d6-1e4dfd277e72").unwrap(),
1✔
1109
                name: "test.csv".to_owned(),
1✔
1110
                byte_size: 1337,
1✔
1111
            }],
1✔
1112
        };
1✔
1113

1114
        let session = app_ctx.create_anonymous_session().await.unwrap();
13✔
1115

1✔
1116
        let db = app_ctx.session_context(session.clone()).db();
1✔
1117

1✔
1118
        db.create_upload(input.clone()).await.unwrap();
12✔
1119

1120
        let upload = db.load_upload(id).await.unwrap();
3✔
1121

1✔
1122
        assert_eq!(upload, input);
1✔
1123
    }
1✔
1124

1125
    #[allow(clippy::too_many_lines)]
1126
    #[ge_context::test]
3✔
1127
    async fn it_persists_layer_providers(app_ctx: ProPostgresContext<NoTls>) {
1✔
1128
        let db = app_ctx.session_context(UserSession::admin_session()).db();
1✔
1129

1✔
1130
        let provider = NetCdfCfDataProviderDefinition {
1✔
1131
            name: "netcdfcf".to_string(),
1✔
1132
            description: "NetCdfCfProviderDefinition".to_string(),
1✔
1133
            priority: Some(33),
1✔
1134
            data: test_data!("netcdf4d/").into(),
1✔
1135
            overviews: test_data!("netcdf4d/overviews/").into(),
1✔
1136
            cache_ttl: CacheTtlSeconds::new(0),
1✔
1137
        };
1✔
1138

1139
        let provider_id = db.add_layer_provider(provider.into()).await.unwrap();
34✔
1140

1141
        let providers = db
1✔
1142
            .list_layer_providers(LayerProviderListingOptions {
1✔
1143
                offset: 0,
1✔
1144
                limit: 10,
1✔
1145
            })
1✔
1146
            .await
3✔
1147
            .unwrap();
1✔
1148

1✔
1149
        assert_eq!(providers.len(), 1);
1✔
1150

1151
        assert_eq!(
1✔
1152
            providers[0],
1✔
1153
            LayerProviderListing {
1✔
1154
                id: provider_id,
1✔
1155
                name: "netcdfcf".to_owned(),
1✔
1156
                priority: 33,
1✔
1157
            }
1✔
1158
        );
1✔
1159

1160
        let provider = db.load_layer_provider(provider_id).await.unwrap();
21✔
1161

1162
        let datasets = provider
1✔
1163
            .load_layer_collection(
1✔
1164
                &provider.get_root_layer_collection_id().await.unwrap(),
1✔
1165
                LayerCollectionListOptions {
1✔
1166
                    offset: 0,
1✔
1167
                    limit: 10,
1✔
1168
                },
1✔
1169
            )
1170
            .await
5✔
1171
            .unwrap();
1✔
1172

1✔
1173
        assert_eq!(datasets.items.len(), 5);
1✔
1174
    }
1✔
1175

1176
    #[ge_context::test]
3✔
1177
    async fn it_lists_only_permitted_datasets(app_ctx: ProPostgresContext<NoTls>) {
1✔
1178
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
14✔
1179
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
15✔
1180

1✔
1181
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1182
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1183

1✔
1184
        let descriptor = VectorResultDescriptor {
1✔
1185
            data_type: VectorDataType::Data,
1✔
1186
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1187
            columns: Default::default(),
1✔
1188
            time: None,
1✔
1189
            bbox: None,
1✔
1190
        };
1✔
1191

1✔
1192
        let ds = AddDataset {
1✔
1193
            name: None,
1✔
1194
            display_name: "OgrDataset".to_string(),
1✔
1195
            description: "My Ogr dataset".to_string(),
1✔
1196
            source_operator: "OgrSource".to_string(),
1✔
1197
            symbology: None,
1✔
1198
            provenance: None,
1✔
1199
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1200
        };
1✔
1201

1✔
1202
        let meta = StaticMetaData {
1✔
1203
            loading_info: OgrSourceDataset {
1✔
1204
                file_name: Default::default(),
1✔
1205
                layer_name: String::new(),
1✔
1206
                data_type: None,
1✔
1207
                time: Default::default(),
1✔
1208
                default_geometry: None,
1✔
1209
                columns: None,
1✔
1210
                force_ogr_time_filter: false,
1✔
1211
                force_ogr_spatial_filter: false,
1✔
1212
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1213
                sql_query: None,
1✔
1214
                attribute_query: None,
1✔
1215
                cache_ttl: CacheTtlSeconds::default(),
1✔
1216
            },
1✔
1217
            result_descriptor: descriptor.clone(),
1✔
1218
            phantom: Default::default(),
1✔
1219
        };
1✔
1220

1221
        let _id = db1.add_dataset(ds, meta.into()).await.unwrap();
178✔
1222

1223
        let list1 = db1
1✔
1224
            .list_datasets(DatasetListOptions {
1✔
1225
                filter: None,
1✔
1226
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
1227
                offset: 0,
1✔
1228
                limit: 1,
1✔
1229
                tags: None,
1✔
1230
            })
1✔
1231
            .await
15✔
1232
            .unwrap();
1✔
1233

1✔
1234
        assert_eq!(list1.len(), 1);
1✔
1235

1236
        let list2 = db2
1✔
1237
            .list_datasets(DatasetListOptions {
1✔
1238
                filter: None,
1✔
1239
                order: crate::datasets::listing::OrderBy::NameAsc,
1✔
1240
                offset: 0,
1✔
1241
                limit: 1,
1✔
1242
                tags: None,
1✔
1243
            })
1✔
1244
            .await
11✔
1245
            .unwrap();
1✔
1246

1✔
1247
        assert_eq!(list2.len(), 0);
1✔
1248
    }
1✔
1249

1250
    #[ge_context::test]
3✔
1251
    async fn it_shows_only_permitted_provenance(app_ctx: ProPostgresContext<NoTls>) {
1✔
1252
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
15✔
1253
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
15✔
1254

1✔
1255
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1256
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1257

1✔
1258
        let descriptor = VectorResultDescriptor {
1✔
1259
            data_type: VectorDataType::Data,
1✔
1260
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1261
            columns: Default::default(),
1✔
1262
            time: None,
1✔
1263
            bbox: None,
1✔
1264
        };
1✔
1265

1✔
1266
        let ds = AddDataset {
1✔
1267
            name: None,
1✔
1268
            display_name: "OgrDataset".to_string(),
1✔
1269
            description: "My Ogr dataset".to_string(),
1✔
1270
            source_operator: "OgrSource".to_string(),
1✔
1271
            symbology: None,
1✔
1272
            provenance: None,
1✔
1273
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1274
        };
1✔
1275

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

1295
        let id = db1.add_dataset(ds, meta.into()).await.unwrap().id;
178✔
1296

1✔
1297
        assert!(db1.load_provenance(&id).await.is_ok());
15✔
1298

1299
        assert!(db2.load_provenance(&id).await.is_err());
11✔
1300
    }
1✔
1301

1302
    #[ge_context::test]
3✔
1303
    async fn it_updates_permissions(app_ctx: ProPostgresContext<NoTls>) {
1✔
1304
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
14✔
1305
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
15✔
1306

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

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

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

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

1347
        let id = db1.add_dataset(ds, meta.into()).await.unwrap().id;
180✔
1348

1✔
1349
        assert!(db1.load_dataset(&id).await.is_ok());
15✔
1350

1351
        assert!(db2.load_dataset(&id).await.is_err());
11✔
1352

1353
        db1.add_permission(session2.user.id.into(), id, Permission::Read)
1✔
1354
            .await
8✔
1355
            .unwrap();
1✔
1356

1✔
1357
        assert!(db2.load_dataset(&id).await.is_ok());
11✔
1358
    }
1✔
1359

1360
    #[ge_context::test]
3✔
1361
    async fn it_uses_roles_for_permissions(app_ctx: ProPostgresContext<NoTls>) {
1✔
1362
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
9✔
1363
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
13✔
1364

1✔
1365
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1366
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1367

1✔
1368
        let descriptor = VectorResultDescriptor {
1✔
1369
            data_type: VectorDataType::Data,
1✔
1370
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1371
            columns: Default::default(),
1✔
1372
            time: None,
1✔
1373
            bbox: None,
1✔
1374
        };
1✔
1375

1✔
1376
        let ds = AddDataset {
1✔
1377
            name: None,
1✔
1378
            display_name: "OgrDataset".to_string(),
1✔
1379
            description: "My Ogr dataset".to_string(),
1✔
1380
            source_operator: "OgrSource".to_string(),
1✔
1381
            symbology: None,
1✔
1382
            provenance: None,
1✔
1383
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1384
        };
1✔
1385

1✔
1386
        let meta = StaticMetaData {
1✔
1387
            loading_info: OgrSourceDataset {
1✔
1388
                file_name: Default::default(),
1✔
1389
                layer_name: String::new(),
1✔
1390
                data_type: None,
1✔
1391
                time: Default::default(),
1✔
1392
                default_geometry: None,
1✔
1393
                columns: None,
1✔
1394
                force_ogr_time_filter: false,
1✔
1395
                force_ogr_spatial_filter: false,
1✔
1396
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1397
                sql_query: None,
1✔
1398
                attribute_query: None,
1✔
1399
                cache_ttl: CacheTtlSeconds::default(),
1✔
1400
            },
1✔
1401
            result_descriptor: descriptor.clone(),
1✔
1402
            phantom: Default::default(),
1✔
1403
        };
1✔
1404

1405
        let id = db1.add_dataset(ds, meta.into()).await.unwrap().id;
167✔
1406

1✔
1407
        assert!(db1.load_dataset(&id).await.is_ok());
15✔
1408

1409
        assert!(db2.load_dataset(&id).await.is_err());
11✔
1410

1411
        db1.add_permission(session2.user.id.into(), id, Permission::Read)
1✔
1412
            .await
8✔
1413
            .unwrap();
1✔
1414

1✔
1415
        assert!(db2.load_dataset(&id).await.is_ok());
11✔
1416
    }
1✔
1417

1418
    #[ge_context::test]
3✔
1419
    async fn it_secures_meta_data(app_ctx: ProPostgresContext<NoTls>) {
1✔
1420
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
15✔
1421
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
15✔
1422

1✔
1423
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1424
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1425

1✔
1426
        let descriptor = VectorResultDescriptor {
1✔
1427
            data_type: VectorDataType::Data,
1✔
1428
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1429
            columns: Default::default(),
1✔
1430
            time: None,
1✔
1431
            bbox: None,
1✔
1432
        };
1✔
1433

1✔
1434
        let ds = AddDataset {
1✔
1435
            name: None,
1✔
1436
            display_name: "OgrDataset".to_string(),
1✔
1437
            description: "My Ogr dataset".to_string(),
1✔
1438
            source_operator: "OgrSource".to_string(),
1✔
1439
            symbology: None,
1✔
1440
            provenance: None,
1✔
1441
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1442
        };
1✔
1443

1✔
1444
        let meta = StaticMetaData {
1✔
1445
            loading_info: OgrSourceDataset {
1✔
1446
                file_name: Default::default(),
1✔
1447
                layer_name: String::new(),
1✔
1448
                data_type: None,
1✔
1449
                time: Default::default(),
1✔
1450
                default_geometry: None,
1✔
1451
                columns: None,
1✔
1452
                force_ogr_time_filter: false,
1✔
1453
                force_ogr_spatial_filter: false,
1✔
1454
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1455
                sql_query: None,
1✔
1456
                attribute_query: None,
1✔
1457
                cache_ttl: CacheTtlSeconds::default(),
1✔
1458
            },
1✔
1459
            result_descriptor: descriptor.clone(),
1✔
1460
            phantom: Default::default(),
1✔
1461
        };
1✔
1462

1463
        let id = db1.add_dataset(ds, meta.into()).await.unwrap().id;
179✔
1464

1465
        let meta: geoengine_operators::util::Result<
1✔
1466
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1467
        > = db1.meta_data(&id.into()).await;
22✔
1468

1469
        assert!(meta.is_ok());
1✔
1470

1471
        let meta: geoengine_operators::util::Result<
1✔
1472
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1473
        > = db2.meta_data(&id.into()).await;
4✔
1474

1475
        assert!(meta.is_err());
1✔
1476

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

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

1485
        assert!(meta.is_ok());
1✔
1486
    }
1✔
1487

1488
    #[allow(clippy::too_many_lines)]
1489
    #[ge_context::test]
3✔
1490
    async fn it_loads_all_meta_data_types(app_ctx: ProPostgresContext<NoTls>) {
1✔
1491
        let session = app_ctx.create_anonymous_session().await.unwrap();
3✔
1492

1✔
1493
        let db = app_ctx.session_context(session.clone()).db();
1✔
1494

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

1✔
1503
        let raster_descriptor = RasterResultDescriptor {
1✔
1504
            data_type: RasterDataType::U8,
1✔
1505
            spatial_reference: SpatialReferenceOption::Unreferenced,
1✔
1506
            time: None,
1✔
1507
            bbox: None,
1✔
1508
            resolution: None,
1✔
1509
            bands: RasterBandDescriptors::new_single_band(),
1✔
1510
        };
1✔
1511

1✔
1512
        let vector_ds = AddDataset {
1✔
1513
            name: None,
1✔
1514
            display_name: "OgrDataset".to_string(),
1✔
1515
            description: "My Ogr dataset".to_string(),
1✔
1516
            source_operator: "OgrSource".to_string(),
1✔
1517
            symbology: None,
1✔
1518
            provenance: None,
1✔
1519
            tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
1520
        };
1✔
1521

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

1✔
1532
        let gdal_params = GdalDatasetParameters {
1✔
1533
            file_path: Default::default(),
1✔
1534
            rasterband_channel: 0,
1✔
1535
            geo_transform: GdalDatasetGeoTransform {
1✔
1536
                origin_coordinate: Default::default(),
1✔
1537
                x_pixel_size: 0.0,
1✔
1538
                y_pixel_size: 0.0,
1✔
1539
            },
1✔
1540
            width: 0,
1✔
1541
            height: 0,
1✔
1542
            file_not_found_handling: FileNotFoundHandling::NoData,
1✔
1543
            no_data_value: None,
1✔
1544
            properties_mapping: None,
1✔
1545
            gdal_open_options: None,
1✔
1546
            gdal_config_options: None,
1✔
1547
            allow_alphaband_as_mask: false,
1✔
1548
            retry: None,
1✔
1549
        };
1✔
1550

1✔
1551
        let meta = StaticMetaData {
1✔
1552
            loading_info: OgrSourceDataset {
1✔
1553
                file_name: Default::default(),
1✔
1554
                layer_name: String::new(),
1✔
1555
                data_type: None,
1✔
1556
                time: Default::default(),
1✔
1557
                default_geometry: None,
1✔
1558
                columns: None,
1✔
1559
                force_ogr_time_filter: false,
1✔
1560
                force_ogr_spatial_filter: false,
1✔
1561
                on_error: OgrSourceErrorSpec::Ignore,
1✔
1562
                sql_query: None,
1✔
1563
                attribute_query: None,
1✔
1564
                cache_ttl: CacheTtlSeconds::default(),
1✔
1565
            },
1✔
1566
            result_descriptor: vector_descriptor.clone(),
1✔
1567
            phantom: Default::default(),
1✔
1568
        };
1✔
1569

1570
        let id = db.add_dataset(vector_ds, meta.into()).await.unwrap().id;
151✔
1571

1572
        let meta: geoengine_operators::util::Result<
1✔
1573
            Box<dyn MetaData<OgrSourceDataset, VectorResultDescriptor, VectorQueryRectangle>>,
1✔
1574
        > = db.meta_data(&id.into()).await;
22✔
1575

1576
        assert!(meta.is_ok());
1✔
1577

1578
        let meta = GdalMetaDataRegular {
1✔
1579
            result_descriptor: raster_descriptor.clone(),
1✔
1580
            params: gdal_params.clone(),
1✔
1581
            time_placeholders: Default::default(),
1✔
1582
            data_time: Default::default(),
1✔
1583
            step: TimeStep {
1✔
1584
                granularity: TimeGranularity::Millis,
1✔
1585
                step: 0,
1✔
1586
            },
1✔
1587
            cache_ttl: CacheTtlSeconds::default(),
1✔
1588
        };
1✔
1589

1590
        let id = db
1✔
1591
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1592
            .await
7✔
1593
            .unwrap()
1✔
1594
            .id;
1595

1596
        let meta: geoengine_operators::util::Result<
1✔
1597
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1598
        > = db.meta_data(&id.into()).await;
16✔
1599

1600
        assert!(meta.is_ok());
1✔
1601

1602
        let meta = GdalMetaDataStatic {
1✔
1603
            time: None,
1✔
1604
            params: gdal_params.clone(),
1✔
1605
            result_descriptor: raster_descriptor.clone(),
1✔
1606
            cache_ttl: CacheTtlSeconds::default(),
1✔
1607
        };
1✔
1608

1609
        let id = db
1✔
1610
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1611
            .await
7✔
1612
            .unwrap()
1✔
1613
            .id;
1614

1615
        let meta: geoengine_operators::util::Result<
1✔
1616
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1617
        > = db.meta_data(&id.into()).await;
16✔
1618

1619
        assert!(meta.is_ok());
1✔
1620

1621
        let meta = GdalMetaDataList {
1✔
1622
            result_descriptor: raster_descriptor.clone(),
1✔
1623
            params: vec![],
1✔
1624
        };
1✔
1625

1626
        let id = db
1✔
1627
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1628
            .await
7✔
1629
            .unwrap()
1✔
1630
            .id;
1631

1632
        let meta: geoengine_operators::util::Result<
1✔
1633
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1634
        > = db.meta_data(&id.into()).await;
16✔
1635

1636
        assert!(meta.is_ok());
1✔
1637

1638
        let meta = GdalMetadataNetCdfCf {
1✔
1639
            result_descriptor: raster_descriptor.clone(),
1✔
1640
            params: gdal_params.clone(),
1✔
1641
            start: TimeInstance::MIN,
1✔
1642
            end: TimeInstance::MAX,
1✔
1643
            step: TimeStep {
1✔
1644
                granularity: TimeGranularity::Millis,
1✔
1645
                step: 0,
1✔
1646
            },
1✔
1647
            band_offset: 0,
1✔
1648
            cache_ttl: CacheTtlSeconds::default(),
1✔
1649
        };
1✔
1650

1651
        let id = db
1✔
1652
            .add_dataset(raster_ds.clone(), meta.into())
1✔
1653
            .await
7✔
1654
            .unwrap()
1✔
1655
            .id;
1656

1657
        let meta: geoengine_operators::util::Result<
1✔
1658
            Box<dyn MetaData<GdalLoadingInfo, RasterResultDescriptor, RasterQueryRectangle>>,
1✔
1659
        > = db.meta_data(&id.into()).await;
16✔
1660

1661
        assert!(meta.is_ok());
1✔
1662
    }
1✔
1663

1664
    #[ge_context::test]
3✔
1665
    async fn it_secures_uploads(app_ctx: ProPostgresContext<NoTls>) {
1✔
1666
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
15✔
1667
        let session2 = app_ctx.create_anonymous_session().await.unwrap();
14✔
1668

1✔
1669
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
1670
        let db2 = app_ctx.session_context(session2.clone()).db();
1✔
1671

1✔
1672
        let upload_id = UploadId::new();
1✔
1673

1✔
1674
        let upload = Upload {
1✔
1675
            id: upload_id,
1✔
1676
            files: vec![FileUpload {
1✔
1677
                id: FileId::new(),
1✔
1678
                name: "test.bin".to_owned(),
1✔
1679
                byte_size: 1024,
1✔
1680
            }],
1✔
1681
        };
1✔
1682

1✔
1683
        db1.create_upload(upload).await.unwrap();
12✔
1684

1✔
1685
        assert!(db1.load_upload(upload_id).await.is_ok());
3✔
1686

1687
        assert!(db2.load_upload(upload_id).await.is_err());
3✔
1688
    }
1✔
1689

1690
    #[allow(clippy::too_many_lines)]
1691
    #[ge_context::test]
3✔
1692
    async fn it_collects_layers(app_ctx: ProPostgresContext<NoTls>) {
1✔
1693
        let session = admin_login(&app_ctx).await;
3✔
1694

1695
        let layer_db = app_ctx.session_context(session).db();
1✔
1696

1✔
1697
        let workflow = Workflow {
1✔
1698
            operator: TypedOperator::Vector(
1✔
1699
                MockPointSource {
1✔
1700
                    params: MockPointSourceParams {
1✔
1701
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1702
                    },
1✔
1703
                }
1✔
1704
                .boxed(),
1✔
1705
            ),
1✔
1706
        };
1✔
1707

1708
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1709

1710
        let layer1 = layer_db
1✔
1711
            .add_layer(
1✔
1712
                AddLayer {
1✔
1713
                    name: "Layer1".to_string(),
1✔
1714
                    description: "Layer 1".to_string(),
1✔
1715
                    symbology: None,
1✔
1716
                    workflow: workflow.clone(),
1✔
1717
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1718
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1719
                },
1✔
1720
                &root_collection_id,
1✔
1721
            )
1✔
1722
            .await
1✔
1723
            .unwrap();
1✔
1724

1725
        assert_eq!(
1✔
1726
            layer_db.load_layer(&layer1).await.unwrap(),
1✔
1727
            crate::layers::layer::Layer {
1✔
1728
                id: ProviderLayerId {
1✔
1729
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1730
                    layer_id: layer1.clone(),
1✔
1731
                },
1✔
1732
                name: "Layer1".to_string(),
1✔
1733
                description: "Layer 1".to_string(),
1✔
1734
                symbology: None,
1✔
1735
                workflow: workflow.clone(),
1✔
1736
                metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1737
                properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1738
            }
1✔
1739
        );
1740

1741
        let collection1_id = layer_db
1✔
1742
            .add_layer_collection(
1✔
1743
                AddLayerCollection {
1✔
1744
                    name: "Collection1".to_string(),
1✔
1745
                    description: "Collection 1".to_string(),
1✔
1746
                    properties: Default::default(),
1✔
1747
                },
1✔
1748
                &root_collection_id,
1✔
1749
            )
1✔
1750
            .await
1✔
1751
            .unwrap();
1✔
1752

1753
        let layer2 = layer_db
1✔
1754
            .add_layer(
1✔
1755
                AddLayer {
1✔
1756
                    name: "Layer2".to_string(),
1✔
1757
                    description: "Layer 2".to_string(),
1✔
1758
                    symbology: None,
1✔
1759
                    workflow: workflow.clone(),
1✔
1760
                    metadata: Default::default(),
1✔
1761
                    properties: Default::default(),
1✔
1762
                },
1✔
1763
                &collection1_id,
1✔
1764
            )
1✔
1765
            .await
1✔
1766
            .unwrap();
1✔
1767

1768
        let collection2_id = layer_db
1✔
1769
            .add_layer_collection(
1✔
1770
                AddLayerCollection {
1✔
1771
                    name: "Collection2".to_string(),
1✔
1772
                    description: "Collection 2".to_string(),
1✔
1773
                    properties: Default::default(),
1✔
1774
                },
1✔
1775
                &collection1_id,
1✔
1776
            )
1✔
1777
            .await
1✔
1778
            .unwrap();
1✔
1779

1✔
1780
        layer_db
1✔
1781
            .add_collection_to_parent(&collection2_id, &collection1_id)
1✔
UNCOV
1782
            .await
×
1783
            .unwrap();
1✔
1784

1785
        let root_collection = layer_db
1✔
1786
            .load_layer_collection(
1✔
1787
                &root_collection_id,
1✔
1788
                LayerCollectionListOptions {
1✔
1789
                    offset: 0,
1✔
1790
                    limit: 20,
1✔
1791
                },
1✔
1792
            )
1✔
UNCOV
1793
            .await
×
1794
            .unwrap();
1✔
1795

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

1839
        let collection1 = layer_db
1✔
1840
            .load_layer_collection(
1✔
1841
                &collection1_id,
1✔
1842
                LayerCollectionListOptions {
1✔
1843
                    offset: 0,
1✔
1844
                    limit: 20,
1✔
1845
                },
1✔
1846
            )
1✔
1847
            .await
9✔
1848
            .unwrap();
1✔
1849

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

1885
    #[allow(clippy::too_many_lines)]
1886
    #[ge_context::test]
3✔
1887
    async fn it_searches_layers(app_ctx: ProPostgresContext<NoTls>) {
1✔
1888
        let session = admin_login(&app_ctx).await;
7✔
1889

1890
        let layer_db = app_ctx.session_context(session).db();
1✔
1891

1✔
1892
        let workflow = Workflow {
1✔
1893
            operator: TypedOperator::Vector(
1✔
1894
                MockPointSource {
1✔
1895
                    params: MockPointSourceParams {
1✔
1896
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
1897
                    },
1✔
1898
                }
1✔
1899
                .boxed(),
1✔
1900
            ),
1✔
1901
        };
1✔
1902

1903
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
1904

1905
        let layer1 = layer_db
1✔
1906
            .add_layer(
1✔
1907
                AddLayer {
1✔
1908
                    name: "Layer1".to_string(),
1✔
1909
                    description: "Layer 1".to_string(),
1✔
1910
                    symbology: None,
1✔
1911
                    workflow: workflow.clone(),
1✔
1912
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
1913
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
1914
                },
1✔
1915
                &root_collection_id,
1✔
1916
            )
1✔
1917
            .await
55✔
1918
            .unwrap();
1✔
1919

1920
        let collection1_id = layer_db
1✔
1921
            .add_layer_collection(
1✔
1922
                AddLayerCollection {
1✔
1923
                    name: "Collection1".to_string(),
1✔
1924
                    description: "Collection 1".to_string(),
1✔
1925
                    properties: Default::default(),
1✔
1926
                },
1✔
1927
                &root_collection_id,
1✔
1928
            )
1✔
1929
            .await
9✔
1930
            .unwrap();
1✔
1931

1932
        let layer2 = layer_db
1✔
1933
            .add_layer(
1✔
1934
                AddLayer {
1✔
1935
                    name: "Layer2".to_string(),
1✔
1936
                    description: "Layer 2".to_string(),
1✔
1937
                    symbology: None,
1✔
1938
                    workflow: workflow.clone(),
1✔
1939
                    metadata: Default::default(),
1✔
1940
                    properties: Default::default(),
1✔
1941
                },
1✔
1942
                &collection1_id,
1✔
1943
            )
1✔
1944
            .await
11✔
1945
            .unwrap();
1✔
1946

1947
        let collection2_id = layer_db
1✔
1948
            .add_layer_collection(
1✔
1949
                AddLayerCollection {
1✔
1950
                    name: "Collection2".to_string(),
1✔
1951
                    description: "Collection 2".to_string(),
1✔
1952
                    properties: Default::default(),
1✔
1953
                },
1✔
1954
                &collection1_id,
1✔
1955
            )
1✔
1956
            .await
9✔
1957
            .unwrap();
1✔
1958

1959
        let root_collection_all = layer_db
1✔
1960
            .search(
1✔
1961
                &root_collection_id,
1✔
1962
                SearchParameters {
1✔
1963
                    search_type: SearchType::Fulltext,
1✔
1964
                    search_string: String::new(),
1✔
1965
                    limit: 10,
1✔
1966
                    offset: 0,
1✔
1967
                },
1✔
1968
            )
1✔
1969
            .await
9✔
1970
            .unwrap();
1✔
1971

1✔
1972
        assert_eq!(
1✔
1973
            root_collection_all,
1✔
1974
            LayerCollection {
1✔
1975
                id: ProviderLayerCollectionId {
1✔
1976
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
1977
                    collection_id: root_collection_id.clone(),
1✔
1978
                },
1✔
1979
                name: "Layers".to_string(),
1✔
1980
                description: "All available Geo Engine layers".to_string(),
1✔
1981
                items: vec![
1✔
1982
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1983
                        id: ProviderLayerCollectionId {
1✔
1984
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1985
                            collection_id: collection1_id.clone(),
1✔
1986
                        },
1✔
1987
                        name: "Collection1".to_string(),
1✔
1988
                        description: "Collection 1".to_string(),
1✔
1989
                        properties: Default::default(),
1✔
1990
                    }),
1✔
1991
                    CollectionItem::Collection(LayerCollectionListing {
1✔
1992
                        id: ProviderLayerCollectionId {
1✔
1993
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
1994
                            collection_id: collection2_id.clone(),
1✔
1995
                        },
1✔
1996
                        name: "Collection2".to_string(),
1✔
1997
                        description: "Collection 2".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: LayerCollectionId(
1✔
2004
                                "ffb2dd9e-f5ad-427c-b7f1-c9a0c7a0ae3f".to_string()
1✔
2005
                            ),
1✔
2006
                        },
1✔
2007
                        name: "Unsorted".to_string(),
1✔
2008
                        description: "Unsorted Layers".to_string(),
1✔
2009
                        properties: Default::default(),
1✔
2010
                    }),
1✔
2011
                    CollectionItem::Layer(LayerListing {
1✔
2012
                        id: ProviderLayerId {
1✔
2013
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2014
                            layer_id: layer1.clone(),
1✔
2015
                        },
1✔
2016
                        name: "Layer1".to_string(),
1✔
2017
                        description: "Layer 1".to_string(),
1✔
2018
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2019
                    }),
1✔
2020
                    CollectionItem::Layer(LayerListing {
1✔
2021
                        id: ProviderLayerId {
1✔
2022
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2023
                            layer_id: layer2.clone(),
1✔
2024
                        },
1✔
2025
                        name: "Layer2".to_string(),
1✔
2026
                        description: "Layer 2".to_string(),
1✔
2027
                        properties: vec![],
1✔
2028
                    }),
1✔
2029
                ],
1✔
2030
                entry_label: None,
1✔
2031
                properties: vec![],
1✔
2032
            }
1✔
2033
        );
1✔
2034

2035
        let root_collection_filtered = layer_db
1✔
2036
            .search(
1✔
2037
                &root_collection_id,
1✔
2038
                SearchParameters {
1✔
2039
                    search_type: SearchType::Fulltext,
1✔
2040
                    search_string: "lection".to_string(),
1✔
2041
                    limit: 10,
1✔
2042
                    offset: 0,
1✔
2043
                },
1✔
2044
            )
1✔
2045
            .await
9✔
2046
            .unwrap();
1✔
2047

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

2082
        let collection1_all = layer_db
1✔
2083
            .search(
1✔
2084
                &collection1_id,
1✔
2085
                SearchParameters {
1✔
2086
                    search_type: SearchType::Fulltext,
1✔
2087
                    search_string: String::new(),
1✔
2088
                    limit: 10,
1✔
2089
                    offset: 0,
1✔
2090
                },
1✔
2091
            )
1✔
2092
            .await
9✔
2093
            .unwrap();
1✔
2094

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

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

1✔
2142
        assert_eq!(
1✔
2143
            collection1_filtered_fulltext,
1✔
2144
            LayerCollection {
1✔
2145
                id: ProviderLayerCollectionId {
1✔
2146
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2147
                    collection_id: collection1_id.clone(),
1✔
2148
                },
1✔
2149
                name: "Collection1".to_string(),
1✔
2150
                description: "Collection 1".to_string(),
1✔
2151
                items: vec![CollectionItem::Layer(LayerListing {
1✔
2152
                    id: ProviderLayerId {
1✔
2153
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
2154
                        layer_id: layer2.clone(),
1✔
2155
                    },
1✔
2156
                    name: "Layer2".to_string(),
1✔
2157
                    description: "Layer 2".to_string(),
1✔
2158
                    properties: vec![],
1✔
2159
                }),],
1✔
2160
                entry_label: None,
1✔
2161
                properties: vec![],
1✔
2162
            }
1✔
2163
        );
1✔
2164

2165
        let collection1_filtered_prefix = layer_db
1✔
2166
            .search(
1✔
2167
                &collection1_id,
1✔
2168
                SearchParameters {
1✔
2169
                    search_type: SearchType::Prefix,
1✔
2170
                    search_string: "ay".to_string(),
1✔
2171
                    limit: 10,
1✔
2172
                    offset: 0,
1✔
2173
                },
1✔
2174
            )
1✔
2175
            .await
9✔
2176
            .unwrap();
1✔
2177

1✔
2178
        assert_eq!(
1✔
2179
            collection1_filtered_prefix,
1✔
2180
            LayerCollection {
1✔
2181
                id: ProviderLayerCollectionId {
1✔
2182
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2183
                    collection_id: collection1_id.clone(),
1✔
2184
                },
1✔
2185
                name: "Collection1".to_string(),
1✔
2186
                description: "Collection 1".to_string(),
1✔
2187
                items: vec![],
1✔
2188
                entry_label: None,
1✔
2189
                properties: vec![],
1✔
2190
            }
1✔
2191
        );
1✔
2192

2193
        let collection1_filtered_prefix2 = layer_db
1✔
2194
            .search(
1✔
2195
                &collection1_id,
1✔
2196
                SearchParameters {
1✔
2197
                    search_type: SearchType::Prefix,
1✔
2198
                    search_string: "Lay".to_string(),
1✔
2199
                    limit: 10,
1✔
2200
                    offset: 0,
1✔
2201
                },
1✔
2202
            )
1✔
2203
            .await
9✔
2204
            .unwrap();
1✔
2205

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

2230
    #[allow(clippy::too_many_lines)]
2231
    #[ge_context::test]
3✔
2232
    async fn it_searches_layers_with_permissions(app_ctx: ProPostgresContext<NoTls>) {
1✔
2233
        let admin_session = admin_login(&app_ctx).await;
11✔
2234
        let admin_layer_db = app_ctx.session_context(admin_session).db();
1✔
2235

2236
        let user_session = app_ctx.create_anonymous_session().await.unwrap();
12✔
2237
        let user_layer_db = app_ctx.session_context(user_session.clone()).db();
1✔
2238

1✔
2239
        let workflow = Workflow {
1✔
2240
            operator: TypedOperator::Vector(
1✔
2241
                MockPointSource {
1✔
2242
                    params: MockPointSourceParams {
1✔
2243
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2244
                    },
1✔
2245
                }
1✔
2246
                .boxed(),
1✔
2247
            ),
1✔
2248
        };
1✔
2249

2250
        let root_collection_id = admin_layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2251

2252
        let layer1 = admin_layer_db
1✔
2253
            .add_layer(
1✔
2254
                AddLayer {
1✔
2255
                    name: "Layer1".to_string(),
1✔
2256
                    description: "Layer 1".to_string(),
1✔
2257
                    symbology: None,
1✔
2258
                    workflow: workflow.clone(),
1✔
2259
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
2260
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2261
                },
1✔
2262
                &root_collection_id,
1✔
2263
            )
1✔
2264
            .await
45✔
2265
            .unwrap();
1✔
2266

2267
        let collection1_id = admin_layer_db
1✔
2268
            .add_layer_collection(
1✔
2269
                AddLayerCollection {
1✔
2270
                    name: "Collection1".to_string(),
1✔
2271
                    description: "Collection 1".to_string(),
1✔
2272
                    properties: Default::default(),
1✔
2273
                },
1✔
2274
                &root_collection_id,
1✔
2275
            )
1✔
2276
            .await
11✔
2277
            .unwrap();
1✔
2278

2279
        let layer2 = admin_layer_db
1✔
2280
            .add_layer(
1✔
2281
                AddLayer {
1✔
2282
                    name: "Layer2".to_string(),
1✔
2283
                    description: "Layer 2".to_string(),
1✔
2284
                    symbology: None,
1✔
2285
                    workflow: workflow.clone(),
1✔
2286
                    metadata: Default::default(),
1✔
2287
                    properties: Default::default(),
1✔
2288
                },
1✔
2289
                &collection1_id,
1✔
2290
            )
1✔
2291
            .await
13✔
2292
            .unwrap();
1✔
2293

2294
        let collection2_id = admin_layer_db
1✔
2295
            .add_layer_collection(
1✔
2296
                AddLayerCollection {
1✔
2297
                    name: "Collection2".to_string(),
1✔
2298
                    description: "Collection 2".to_string(),
1✔
2299
                    properties: Default::default(),
1✔
2300
                },
1✔
2301
                &collection1_id,
1✔
2302
            )
1✔
2303
            .await
11✔
2304
            .unwrap();
1✔
2305

2306
        let collection3_id = admin_layer_db
1✔
2307
            .add_layer_collection(
1✔
2308
                AddLayerCollection {
1✔
2309
                    name: "Collection3".to_string(),
1✔
2310
                    description: "Collection 3".to_string(),
1✔
2311
                    properties: Default::default(),
1✔
2312
                },
1✔
2313
                &collection1_id,
1✔
2314
            )
1✔
2315
            .await
11✔
2316
            .unwrap();
1✔
2317

2318
        let layer3 = admin_layer_db
1✔
2319
            .add_layer(
1✔
2320
                AddLayer {
1✔
2321
                    name: "Layer3".to_string(),
1✔
2322
                    description: "Layer 3".to_string(),
1✔
2323
                    symbology: None,
1✔
2324
                    workflow: workflow.clone(),
1✔
2325
                    metadata: Default::default(),
1✔
2326
                    properties: Default::default(),
1✔
2327
                },
1✔
2328
                &collection2_id,
1✔
2329
            )
1✔
2330
            .await
13✔
2331
            .unwrap();
1✔
2332

1✔
2333
        // Grant user permissions for collection1, collection2, layer1 and layer2
1✔
2334
        admin_layer_db
1✔
2335
            .add_permission(
1✔
2336
                user_session.user.id.into(),
1✔
2337
                collection1_id.clone(),
1✔
2338
                Permission::Read,
1✔
2339
            )
1✔
2340
            .await
7✔
2341
            .unwrap();
1✔
2342

1✔
2343
        admin_layer_db
1✔
2344
            .add_permission(
1✔
2345
                user_session.user.id.into(),
1✔
2346
                collection2_id.clone(),
1✔
2347
                Permission::Read,
1✔
2348
            )
1✔
2349
            .await
7✔
2350
            .unwrap();
1✔
2351

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

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

2370
        // Ensure admin sees everything we added
2371
        let admin_root_collection_all = admin_layer_db
1✔
2372
            .search(
1✔
2373
                &root_collection_id,
1✔
2374
                SearchParameters {
1✔
2375
                    search_type: SearchType::Fulltext,
1✔
2376
                    search_string: String::new(),
1✔
2377
                    limit: 10,
1✔
2378
                    offset: 0,
1✔
2379
                },
1✔
2380
            )
1✔
2381
            .await
9✔
2382
            .unwrap();
1✔
2383

1✔
2384
        assert_eq!(
1✔
2385
            admin_root_collection_all,
1✔
2386
            LayerCollection {
1✔
2387
                id: ProviderLayerCollectionId {
1✔
2388
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2389
                    collection_id: root_collection_id.clone(),
1✔
2390
                },
1✔
2391
                name: "Layers".to_string(),
1✔
2392
                description: "All available Geo Engine layers".to_string(),
1✔
2393
                items: vec![
1✔
2394
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2395
                        id: ProviderLayerCollectionId {
1✔
2396
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2397
                            collection_id: collection1_id.clone(),
1✔
2398
                        },
1✔
2399
                        name: "Collection1".to_string(),
1✔
2400
                        description: "Collection 1".to_string(),
1✔
2401
                        properties: Default::default(),
1✔
2402
                    }),
1✔
2403
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2404
                        id: ProviderLayerCollectionId {
1✔
2405
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2406
                            collection_id: collection2_id.clone(),
1✔
2407
                        },
1✔
2408
                        name: "Collection2".to_string(),
1✔
2409
                        description: "Collection 2".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: collection3_id.clone(),
1✔
2416
                        },
1✔
2417
                        name: "Collection3".to_string(),
1✔
2418
                        description: "Collection 3".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: LayerCollectionId(
1✔
2425
                                "ffb2dd9e-f5ad-427c-b7f1-c9a0c7a0ae3f".to_string()
1✔
2426
                            ),
1✔
2427
                        },
1✔
2428
                        name: "Unsorted".to_string(),
1✔
2429
                        description: "Unsorted Layers".to_string(),
1✔
2430
                        properties: Default::default(),
1✔
2431
                    }),
1✔
2432
                    CollectionItem::Layer(LayerListing {
1✔
2433
                        id: ProviderLayerId {
1✔
2434
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2435
                            layer_id: layer1.clone(),
1✔
2436
                        },
1✔
2437
                        name: "Layer1".to_string(),
1✔
2438
                        description: "Layer 1".to_string(),
1✔
2439
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2440
                    }),
1✔
2441
                    CollectionItem::Layer(LayerListing {
1✔
2442
                        id: ProviderLayerId {
1✔
2443
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2444
                            layer_id: layer2.clone(),
1✔
2445
                        },
1✔
2446
                        name: "Layer2".to_string(),
1✔
2447
                        description: "Layer 2".to_string(),
1✔
2448
                        properties: vec![],
1✔
2449
                    }),
1✔
2450
                    CollectionItem::Layer(LayerListing {
1✔
2451
                        id: ProviderLayerId {
1✔
2452
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2453
                            layer_id: layer3.clone(),
1✔
2454
                        },
1✔
2455
                        name: "Layer3".to_string(),
1✔
2456
                        description: "Layer 3".to_string(),
1✔
2457
                        properties: vec![],
1✔
2458
                    }),
1✔
2459
                ],
1✔
2460
                entry_label: None,
1✔
2461
                properties: vec![],
1✔
2462
            }
1✔
2463
        );
1✔
2464

2465
        let root_collection_all = user_layer_db
1✔
2466
            .search(
1✔
2467
                &root_collection_id,
1✔
2468
                SearchParameters {
1✔
2469
                    search_type: SearchType::Fulltext,
1✔
2470
                    search_string: String::new(),
1✔
2471
                    limit: 10,
1✔
2472
                    offset: 0,
1✔
2473
                },
1✔
2474
            )
1✔
2475
            .await
9✔
2476
            .unwrap();
1✔
2477

1✔
2478
        assert_eq!(
1✔
2479
            root_collection_all,
1✔
2480
            LayerCollection {
1✔
2481
                id: ProviderLayerCollectionId {
1✔
2482
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2483
                    collection_id: root_collection_id.clone(),
1✔
2484
                },
1✔
2485
                name: "Layers".to_string(),
1✔
2486
                description: "All available Geo Engine layers".to_string(),
1✔
2487
                items: vec![
1✔
2488
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2489
                        id: ProviderLayerCollectionId {
1✔
2490
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2491
                            collection_id: collection1_id.clone(),
1✔
2492
                        },
1✔
2493
                        name: "Collection1".to_string(),
1✔
2494
                        description: "Collection 1".to_string(),
1✔
2495
                        properties: Default::default(),
1✔
2496
                    }),
1✔
2497
                    CollectionItem::Collection(LayerCollectionListing {
1✔
2498
                        id: ProviderLayerCollectionId {
1✔
2499
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2500
                            collection_id: collection2_id.clone(),
1✔
2501
                        },
1✔
2502
                        name: "Collection2".to_string(),
1✔
2503
                        description: "Collection 2".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: LayerCollectionId(
1✔
2510
                                "ffb2dd9e-f5ad-427c-b7f1-c9a0c7a0ae3f".to_string()
1✔
2511
                            ),
1✔
2512
                        },
1✔
2513
                        name: "Unsorted".to_string(),
1✔
2514
                        description: "Unsorted Layers".to_string(),
1✔
2515
                        properties: Default::default(),
1✔
2516
                    }),
1✔
2517
                    CollectionItem::Layer(LayerListing {
1✔
2518
                        id: ProviderLayerId {
1✔
2519
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2520
                            layer_id: layer1.clone(),
1✔
2521
                        },
1✔
2522
                        name: "Layer1".to_string(),
1✔
2523
                        description: "Layer 1".to_string(),
1✔
2524
                        properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2525
                    }),
1✔
2526
                    CollectionItem::Layer(LayerListing {
1✔
2527
                        id: ProviderLayerId {
1✔
2528
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
2529
                            layer_id: layer2.clone(),
1✔
2530
                        },
1✔
2531
                        name: "Layer2".to_string(),
1✔
2532
                        description: "Layer 2".to_string(),
1✔
2533
                        properties: vec![],
1✔
2534
                    }),
1✔
2535
                ],
1✔
2536
                entry_label: None,
1✔
2537
                properties: vec![],
1✔
2538
            }
1✔
2539
        );
1✔
2540

2541
        let root_collection_filtered = user_layer_db
1✔
2542
            .search(
1✔
2543
                &root_collection_id,
1✔
2544
                SearchParameters {
1✔
2545
                    search_type: SearchType::Fulltext,
1✔
2546
                    search_string: "lection".to_string(),
1✔
2547
                    limit: 10,
1✔
2548
                    offset: 0,
1✔
2549
                },
1✔
2550
            )
1✔
2551
            .await
10✔
2552
            .unwrap();
1✔
2553

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

2588
        let collection1_all = user_layer_db
1✔
2589
            .search(
1✔
2590
                &collection1_id,
1✔
2591
                SearchParameters {
1✔
2592
                    search_type: SearchType::Fulltext,
1✔
2593
                    search_string: String::new(),
1✔
2594
                    limit: 10,
1✔
2595
                    offset: 0,
1✔
2596
                },
1✔
2597
            )
1✔
2598
            .await
9✔
2599
            .unwrap();
1✔
2600

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

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

1✔
2648
        assert_eq!(
1✔
2649
            collection1_filtered_fulltext,
1✔
2650
            LayerCollection {
1✔
2651
                id: ProviderLayerCollectionId {
1✔
2652
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2653
                    collection_id: collection1_id.clone(),
1✔
2654
                },
1✔
2655
                name: "Collection1".to_string(),
1✔
2656
                description: "Collection 1".to_string(),
1✔
2657
                items: vec![CollectionItem::Layer(LayerListing {
1✔
2658
                    id: ProviderLayerId {
1✔
2659
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
2660
                        layer_id: layer2.clone(),
1✔
2661
                    },
1✔
2662
                    name: "Layer2".to_string(),
1✔
2663
                    description: "Layer 2".to_string(),
1✔
2664
                    properties: vec![],
1✔
2665
                }),],
1✔
2666
                entry_label: None,
1✔
2667
                properties: vec![],
1✔
2668
            }
1✔
2669
        );
1✔
2670

2671
        let collection1_filtered_prefix = user_layer_db
1✔
2672
            .search(
1✔
2673
                &collection1_id,
1✔
2674
                SearchParameters {
1✔
2675
                    search_type: SearchType::Prefix,
1✔
2676
                    search_string: "ay".to_string(),
1✔
2677
                    limit: 10,
1✔
2678
                    offset: 0,
1✔
2679
                },
1✔
2680
            )
1✔
2681
            .await
9✔
2682
            .unwrap();
1✔
2683

1✔
2684
        assert_eq!(
1✔
2685
            collection1_filtered_prefix,
1✔
2686
            LayerCollection {
1✔
2687
                id: ProviderLayerCollectionId {
1✔
2688
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
2689
                    collection_id: collection1_id.clone(),
1✔
2690
                },
1✔
2691
                name: "Collection1".to_string(),
1✔
2692
                description: "Collection 1".to_string(),
1✔
2693
                items: vec![],
1✔
2694
                entry_label: None,
1✔
2695
                properties: vec![],
1✔
2696
            }
1✔
2697
        );
1✔
2698

2699
        let collection1_filtered_prefix2 = user_layer_db
1✔
2700
            .search(
1✔
2701
                &collection1_id,
1✔
2702
                SearchParameters {
1✔
2703
                    search_type: SearchType::Prefix,
1✔
2704
                    search_string: "Lay".to_string(),
1✔
2705
                    limit: 10,
1✔
2706
                    offset: 0,
1✔
2707
                },
1✔
2708
            )
1✔
2709
            .await
9✔
2710
            .unwrap();
1✔
2711

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

2736
    #[allow(clippy::too_many_lines)]
2737
    #[ge_context::test]
3✔
2738
    async fn it_autocompletes_layers(app_ctx: ProPostgresContext<NoTls>) {
1✔
2739
        let session = admin_login(&app_ctx).await;
11✔
2740

2741
        let layer_db = app_ctx.session_context(session).db();
1✔
2742

1✔
2743
        let workflow = Workflow {
1✔
2744
            operator: TypedOperator::Vector(
1✔
2745
                MockPointSource {
1✔
2746
                    params: MockPointSourceParams {
1✔
2747
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2748
                    },
1✔
2749
                }
1✔
2750
                .boxed(),
1✔
2751
            ),
1✔
2752
        };
1✔
2753

2754
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2755

2756
        let _layer1 = layer_db
1✔
2757
            .add_layer(
1✔
2758
                AddLayer {
1✔
2759
                    name: "Layer1".to_string(),
1✔
2760
                    description: "Layer 1".to_string(),
1✔
2761
                    symbology: None,
1✔
2762
                    workflow: workflow.clone(),
1✔
2763
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
2764
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2765
                },
1✔
2766
                &root_collection_id,
1✔
2767
            )
1✔
2768
            .await
55✔
2769
            .unwrap();
1✔
2770

2771
        let collection1_id = layer_db
1✔
2772
            .add_layer_collection(
1✔
2773
                AddLayerCollection {
1✔
2774
                    name: "Collection1".to_string(),
1✔
2775
                    description: "Collection 1".to_string(),
1✔
2776
                    properties: Default::default(),
1✔
2777
                },
1✔
2778
                &root_collection_id,
1✔
2779
            )
1✔
2780
            .await
11✔
2781
            .unwrap();
1✔
2782

2783
        let _layer2 = layer_db
1✔
2784
            .add_layer(
1✔
2785
                AddLayer {
1✔
2786
                    name: "Layer2".to_string(),
1✔
2787
                    description: "Layer 2".to_string(),
1✔
2788
                    symbology: None,
1✔
2789
                    workflow: workflow.clone(),
1✔
2790
                    metadata: Default::default(),
1✔
2791
                    properties: Default::default(),
1✔
2792
                },
1✔
2793
                &collection1_id,
1✔
2794
            )
1✔
2795
            .await
13✔
2796
            .unwrap();
1✔
2797

2798
        let _collection2_id = layer_db
1✔
2799
            .add_layer_collection(
1✔
2800
                AddLayerCollection {
1✔
2801
                    name: "Collection2".to_string(),
1✔
2802
                    description: "Collection 2".to_string(),
1✔
2803
                    properties: Default::default(),
1✔
2804
                },
1✔
2805
                &collection1_id,
1✔
2806
            )
1✔
2807
            .await
11✔
2808
            .unwrap();
1✔
2809

2810
        let root_collection_all = layer_db
1✔
2811
            .autocomplete_search(
1✔
2812
                &root_collection_id,
1✔
2813
                SearchParameters {
1✔
2814
                    search_type: SearchType::Fulltext,
1✔
2815
                    search_string: String::new(),
1✔
2816
                    limit: 10,
1✔
2817
                    offset: 0,
1✔
2818
                },
1✔
2819
            )
1✔
2820
            .await
7✔
2821
            .unwrap();
1✔
2822

1✔
2823
        assert_eq!(
1✔
2824
            root_collection_all,
1✔
2825
            vec![
1✔
2826
                "Collection1".to_string(),
1✔
2827
                "Collection2".to_string(),
1✔
2828
                "Layer1".to_string(),
1✔
2829
                "Layer2".to_string(),
1✔
2830
                "Unsorted".to_string(),
1✔
2831
            ]
1✔
2832
        );
1✔
2833

2834
        let root_collection_filtered = layer_db
1✔
2835
            .autocomplete_search(
1✔
2836
                &root_collection_id,
1✔
2837
                SearchParameters {
1✔
2838
                    search_type: SearchType::Fulltext,
1✔
2839
                    search_string: "lection".to_string(),
1✔
2840
                    limit: 10,
1✔
2841
                    offset: 0,
1✔
2842
                },
1✔
2843
            )
1✔
2844
            .await
7✔
2845
            .unwrap();
1✔
2846

1✔
2847
        assert_eq!(
1✔
2848
            root_collection_filtered,
1✔
2849
            vec!["Collection1".to_string(), "Collection2".to_string(),]
1✔
2850
        );
1✔
2851

2852
        let collection1_all = layer_db
1✔
2853
            .autocomplete_search(
1✔
2854
                &collection1_id,
1✔
2855
                SearchParameters {
1✔
2856
                    search_type: SearchType::Fulltext,
1✔
2857
                    search_string: String::new(),
1✔
2858
                    limit: 10,
1✔
2859
                    offset: 0,
1✔
2860
                },
1✔
2861
            )
1✔
2862
            .await
7✔
2863
            .unwrap();
1✔
2864

1✔
2865
        assert_eq!(
1✔
2866
            collection1_all,
1✔
2867
            vec!["Collection2".to_string(), "Layer2".to_string(),]
1✔
2868
        );
1✔
2869

2870
        let collection1_filtered_fulltext = layer_db
1✔
2871
            .autocomplete_search(
1✔
2872
                &collection1_id,
1✔
2873
                SearchParameters {
1✔
2874
                    search_type: SearchType::Fulltext,
1✔
2875
                    search_string: "ay".to_string(),
1✔
2876
                    limit: 10,
1✔
2877
                    offset: 0,
1✔
2878
                },
1✔
2879
            )
1✔
2880
            .await
7✔
2881
            .unwrap();
1✔
2882

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

2885
        let collection1_filtered_prefix = layer_db
1✔
2886
            .autocomplete_search(
1✔
2887
                &collection1_id,
1✔
2888
                SearchParameters {
1✔
2889
                    search_type: SearchType::Prefix,
1✔
2890
                    search_string: "ay".to_string(),
1✔
2891
                    limit: 10,
1✔
2892
                    offset: 0,
1✔
2893
                },
1✔
2894
            )
1✔
2895
            .await
7✔
2896
            .unwrap();
1✔
2897

1✔
2898
        assert_eq!(collection1_filtered_prefix, Vec::<String>::new());
1✔
2899

2900
        let collection1_filtered_prefix2 = layer_db
1✔
2901
            .autocomplete_search(
1✔
2902
                &collection1_id,
1✔
2903
                SearchParameters {
1✔
2904
                    search_type: SearchType::Prefix,
1✔
2905
                    search_string: "Lay".to_string(),
1✔
2906
                    limit: 10,
1✔
2907
                    offset: 0,
1✔
2908
                },
1✔
2909
            )
1✔
2910
            .await
7✔
2911
            .unwrap();
1✔
2912

1✔
2913
        assert_eq!(collection1_filtered_prefix2, vec!["Layer2".to_string(),]);
1✔
2914
    }
1✔
2915

2916
    #[allow(clippy::too_many_lines)]
2917
    #[ge_context::test]
3✔
2918
    async fn it_autocompletes_layers_with_permissions(app_ctx: ProPostgresContext<NoTls>) {
1✔
2919
        let admin_session = admin_login(&app_ctx).await;
10✔
2920
        let admin_layer_db = app_ctx.session_context(admin_session).db();
1✔
2921

2922
        let user_session = app_ctx.create_anonymous_session().await.unwrap();
15✔
2923
        let user_layer_db = app_ctx.session_context(user_session.clone()).db();
1✔
2924

1✔
2925
        let workflow = Workflow {
1✔
2926
            operator: TypedOperator::Vector(
1✔
2927
                MockPointSource {
1✔
2928
                    params: MockPointSourceParams {
1✔
2929
                        points: vec![Coordinate2D::new(1., 2.); 3],
1✔
2930
                    },
1✔
2931
                }
1✔
2932
                .boxed(),
1✔
2933
            ),
1✔
2934
        };
1✔
2935

2936
        let root_collection_id = admin_layer_db.get_root_layer_collection_id().await.unwrap();
1✔
2937

2938
        let layer1 = admin_layer_db
1✔
2939
            .add_layer(
1✔
2940
                AddLayer {
1✔
2941
                    name: "Layer1".to_string(),
1✔
2942
                    description: "Layer 1".to_string(),
1✔
2943
                    symbology: None,
1✔
2944
                    workflow: workflow.clone(),
1✔
2945
                    metadata: [("meta".to_string(), "datum".to_string())].into(),
1✔
2946
                    properties: vec![("proper".to_string(), "tee".to_string()).into()],
1✔
2947
                },
1✔
2948
                &root_collection_id,
1✔
2949
            )
1✔
2950
            .await
23✔
2951
            .unwrap();
1✔
2952

2953
        let collection1_id = admin_layer_db
1✔
2954
            .add_layer_collection(
1✔
2955
                AddLayerCollection {
1✔
2956
                    name: "Collection1".to_string(),
1✔
2957
                    description: "Collection 1".to_string(),
1✔
2958
                    properties: Default::default(),
1✔
2959
                },
1✔
2960
                &root_collection_id,
1✔
2961
            )
1✔
2962
            .await
11✔
2963
            .unwrap();
1✔
2964

2965
        let layer2 = admin_layer_db
1✔
2966
            .add_layer(
1✔
2967
                AddLayer {
1✔
2968
                    name: "Layer2".to_string(),
1✔
2969
                    description: "Layer 2".to_string(),
1✔
2970
                    symbology: None,
1✔
2971
                    workflow: workflow.clone(),
1✔
2972
                    metadata: Default::default(),
1✔
2973
                    properties: Default::default(),
1✔
2974
                },
1✔
2975
                &collection1_id,
1✔
2976
            )
1✔
2977
            .await
13✔
2978
            .unwrap();
1✔
2979

2980
        let collection2_id = admin_layer_db
1✔
2981
            .add_layer_collection(
1✔
2982
                AddLayerCollection {
1✔
2983
                    name: "Collection2".to_string(),
1✔
2984
                    description: "Collection 2".to_string(),
1✔
2985
                    properties: Default::default(),
1✔
2986
                },
1✔
2987
                &collection1_id,
1✔
2988
            )
1✔
2989
            .await
11✔
2990
            .unwrap();
1✔
2991

2992
        let _collection3_id = admin_layer_db
1✔
2993
            .add_layer_collection(
1✔
2994
                AddLayerCollection {
1✔
2995
                    name: "Collection3".to_string(),
1✔
2996
                    description: "Collection 3".to_string(),
1✔
2997
                    properties: Default::default(),
1✔
2998
                },
1✔
2999
                &collection1_id,
1✔
3000
            )
1✔
3001
            .await
11✔
3002
            .unwrap();
1✔
3003

3004
        let _layer3 = admin_layer_db
1✔
3005
            .add_layer(
1✔
3006
                AddLayer {
1✔
3007
                    name: "Layer3".to_string(),
1✔
3008
                    description: "Layer 3".to_string(),
1✔
3009
                    symbology: None,
1✔
3010
                    workflow: workflow.clone(),
1✔
3011
                    metadata: Default::default(),
1✔
3012
                    properties: Default::default(),
1✔
3013
                },
1✔
3014
                &collection2_id,
1✔
3015
            )
1✔
3016
            .await
13✔
3017
            .unwrap();
1✔
3018

1✔
3019
        // Grant user permissions for collection1, collection2, layer1 and layer2
1✔
3020
        admin_layer_db
1✔
3021
            .add_permission(
1✔
3022
                user_session.user.id.into(),
1✔
3023
                collection1_id.clone(),
1✔
3024
                Permission::Read,
1✔
3025
            )
1✔
3026
            .await
7✔
3027
            .unwrap();
1✔
3028

1✔
3029
        admin_layer_db
1✔
3030
            .add_permission(
1✔
3031
                user_session.user.id.into(),
1✔
3032
                collection2_id.clone(),
1✔
3033
                Permission::Read,
1✔
3034
            )
1✔
3035
            .await
7✔
3036
            .unwrap();
1✔
3037

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

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

3056
        // Ensure admin sees everything we added
3057
        let admin_root_collection_all = admin_layer_db
1✔
3058
            .autocomplete_search(
1✔
3059
                &root_collection_id,
1✔
3060
                SearchParameters {
1✔
3061
                    search_type: SearchType::Fulltext,
1✔
3062
                    search_string: String::new(),
1✔
3063
                    limit: 10,
1✔
3064
                    offset: 0,
1✔
3065
                },
1✔
3066
            )
1✔
3067
            .await
7✔
3068
            .unwrap();
1✔
3069

1✔
3070
        assert_eq!(
1✔
3071
            admin_root_collection_all,
1✔
3072
            vec![
1✔
3073
                "Collection1".to_string(),
1✔
3074
                "Collection2".to_string(),
1✔
3075
                "Collection3".to_string(),
1✔
3076
                "Layer1".to_string(),
1✔
3077
                "Layer2".to_string(),
1✔
3078
                "Layer3".to_string(),
1✔
3079
                "Unsorted".to_string(),
1✔
3080
            ]
1✔
3081
        );
1✔
3082

3083
        let root_collection_all = user_layer_db
1✔
3084
            .autocomplete_search(
1✔
3085
                &root_collection_id,
1✔
3086
                SearchParameters {
1✔
3087
                    search_type: SearchType::Fulltext,
1✔
3088
                    search_string: String::new(),
1✔
3089
                    limit: 10,
1✔
3090
                    offset: 0,
1✔
3091
                },
1✔
3092
            )
1✔
3093
            .await
7✔
3094
            .unwrap();
1✔
3095

1✔
3096
        assert_eq!(
1✔
3097
            root_collection_all,
1✔
3098
            vec![
1✔
3099
                "Collection1".to_string(),
1✔
3100
                "Collection2".to_string(),
1✔
3101
                "Layer1".to_string(),
1✔
3102
                "Layer2".to_string(),
1✔
3103
                "Unsorted".to_string(),
1✔
3104
            ]
1✔
3105
        );
1✔
3106

3107
        let root_collection_filtered = user_layer_db
1✔
3108
            .autocomplete_search(
1✔
3109
                &root_collection_id,
1✔
3110
                SearchParameters {
1✔
3111
                    search_type: SearchType::Fulltext,
1✔
3112
                    search_string: "lection".to_string(),
1✔
3113
                    limit: 10,
1✔
3114
                    offset: 0,
1✔
3115
                },
1✔
3116
            )
1✔
3117
            .await
7✔
3118
            .unwrap();
1✔
3119

1✔
3120
        assert_eq!(
1✔
3121
            root_collection_filtered,
1✔
3122
            vec!["Collection1".to_string(), "Collection2".to_string(),]
1✔
3123
        );
1✔
3124

3125
        let collection1_all = user_layer_db
1✔
3126
            .autocomplete_search(
1✔
3127
                &collection1_id,
1✔
3128
                SearchParameters {
1✔
3129
                    search_type: SearchType::Fulltext,
1✔
3130
                    search_string: String::new(),
1✔
3131
                    limit: 10,
1✔
3132
                    offset: 0,
1✔
3133
                },
1✔
3134
            )
1✔
3135
            .await
7✔
3136
            .unwrap();
1✔
3137

1✔
3138
        assert_eq!(
1✔
3139
            collection1_all,
1✔
3140
            vec!["Collection2".to_string(), "Layer2".to_string(),]
1✔
3141
        );
1✔
3142

3143
        let collection1_filtered_fulltext = user_layer_db
1✔
3144
            .autocomplete_search(
1✔
3145
                &collection1_id,
1✔
3146
                SearchParameters {
1✔
3147
                    search_type: SearchType::Fulltext,
1✔
3148
                    search_string: "ay".to_string(),
1✔
3149
                    limit: 10,
1✔
3150
                    offset: 0,
1✔
3151
                },
1✔
3152
            )
1✔
3153
            .await
7✔
3154
            .unwrap();
1✔
3155

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

3158
        let collection1_filtered_prefix = user_layer_db
1✔
3159
            .autocomplete_search(
1✔
3160
                &collection1_id,
1✔
3161
                SearchParameters {
1✔
3162
                    search_type: SearchType::Prefix,
1✔
3163
                    search_string: "ay".to_string(),
1✔
3164
                    limit: 10,
1✔
3165
                    offset: 0,
1✔
3166
                },
1✔
3167
            )
1✔
3168
            .await
7✔
3169
            .unwrap();
1✔
3170

1✔
3171
        assert_eq!(collection1_filtered_prefix, Vec::<String>::new());
1✔
3172

3173
        let collection1_filtered_prefix2 = user_layer_db
1✔
3174
            .autocomplete_search(
1✔
3175
                &collection1_id,
1✔
3176
                SearchParameters {
1✔
3177
                    search_type: SearchType::Prefix,
1✔
3178
                    search_string: "Lay".to_string(),
1✔
3179
                    limit: 10,
1✔
3180
                    offset: 0,
1✔
3181
                },
1✔
3182
            )
1✔
3183
            .await
7✔
3184
            .unwrap();
1✔
3185

1✔
3186
        assert_eq!(collection1_filtered_prefix2, vec!["Layer2".to_string(),]);
1✔
3187
    }
1✔
3188

3189
    #[allow(clippy::too_many_lines)]
3190
    #[ge_context::test]
3✔
3191
    async fn it_reports_search_capabilities(app_ctx: ProPostgresContext<NoTls>) {
1✔
3192
        let session = admin_login(&app_ctx).await;
11✔
3193

3194
        let layer_db = app_ctx.session_context(session).db();
1✔
3195

1✔
3196
        let capabilities = layer_db.capabilities().search;
1✔
3197

3198
        let root_collection_id = layer_db.get_root_layer_collection_id().await.unwrap();
1✔
3199

1✔
3200
        if capabilities.search_types.fulltext {
1✔
3201
            assert!(layer_db
1✔
3202
                .search(
1✔
3203
                    &root_collection_id,
1✔
3204
                    SearchParameters {
1✔
3205
                        search_type: SearchType::Fulltext,
1✔
3206
                        search_string: String::new(),
1✔
3207
                        limit: 10,
1✔
3208
                        offset: 0,
1✔
3209
                    },
1✔
3210
                )
1✔
3211
                .await
18✔
3212
                .is_ok());
1✔
3213

3214
            if capabilities.autocomplete {
1✔
3215
                assert!(layer_db
1✔
3216
                    .autocomplete_search(
1✔
3217
                        &root_collection_id,
1✔
3218
                        SearchParameters {
1✔
3219
                            search_type: SearchType::Fulltext,
1✔
3220
                            search_string: String::new(),
1✔
3221
                            limit: 10,
1✔
3222
                            offset: 0,
1✔
3223
                        },
1✔
3224
                    )
1✔
3225
                    .await
7✔
3226
                    .is_ok());
1✔
3227
            } else {
3228
                assert!(layer_db
×
3229
                    .autocomplete_search(
×
3230
                        &root_collection_id,
×
3231
                        SearchParameters {
×
3232
                            search_type: SearchType::Fulltext,
×
3233
                            search_string: String::new(),
×
3234
                            limit: 10,
×
3235
                            offset: 0,
×
3236
                        },
×
3237
                    )
×
3238
                    .await
×
3239
                    .is_err());
×
3240
            }
3241
        }
×
3242
        if capabilities.search_types.prefix {
1✔
3243
            assert!(layer_db
1✔
3244
                .search(
1✔
3245
                    &root_collection_id,
1✔
3246
                    SearchParameters {
1✔
3247
                        search_type: SearchType::Prefix,
1✔
3248
                        search_string: String::new(),
1✔
3249
                        limit: 10,
1✔
3250
                        offset: 0,
1✔
3251
                    },
1✔
3252
                )
1✔
3253
                .await
9✔
3254
                .is_ok());
1✔
3255

3256
            if capabilities.autocomplete {
1✔
3257
                assert!(layer_db
1✔
3258
                    .autocomplete_search(
1✔
3259
                        &root_collection_id,
1✔
3260
                        SearchParameters {
1✔
3261
                            search_type: SearchType::Prefix,
1✔
3262
                            search_string: String::new(),
1✔
3263
                            limit: 10,
1✔
3264
                            offset: 0,
1✔
3265
                        },
1✔
3266
                    )
1✔
3267
                    .await
7✔
3268
                    .is_ok());
1✔
3269
            } else {
3270
                assert!(layer_db
×
3271
                    .autocomplete_search(
×
3272
                        &root_collection_id,
×
3273
                        SearchParameters {
×
3274
                            search_type: SearchType::Prefix,
×
3275
                            search_string: String::new(),
×
3276
                            limit: 10,
×
3277
                            offset: 0,
×
3278
                        },
×
3279
                    )
×
3280
                    .await
×
3281
                    .is_err());
×
3282
            }
3283
        }
×
3284
    }
1✔
3285

3286
    #[ge_context::test]
3✔
3287
    async fn it_tracks_used_quota_in_postgres(app_ctx: ProPostgresContext<NoTls>) {
1✔
3288
        let _user = app_ctx
1✔
3289
            .register_user(UserRegistration {
1✔
3290
                email: "foo@example.com".to_string(),
1✔
3291
                password: "secret1234".to_string(),
1✔
3292
                real_name: "Foo Bar".to_string(),
1✔
3293
            })
1✔
3294
            .await
11✔
3295
            .unwrap();
1✔
3296

3297
        let session = app_ctx
1✔
3298
            .login(UserCredentials {
1✔
3299
                email: "foo@example.com".to_string(),
1✔
3300
                password: "secret1234".to_string(),
1✔
3301
            })
1✔
3302
            .await
11✔
3303
            .unwrap();
1✔
3304

3305
        let admin_session = admin_login(&app_ctx).await;
11✔
3306

3307
        let quota = initialize_quota_tracking(
1✔
3308
            QuotaTrackingMode::Check,
1✔
3309
            app_ctx.session_context(admin_session).db(),
1✔
3310
            0,
1✔
3311
            60,
1✔
3312
        );
1✔
3313

1✔
3314
        let tracking = quota.create_quota_tracking(&session, ComputationContext::new());
1✔
3315

1✔
3316
        tracking.work_unit_done();
1✔
3317
        tracking.work_unit_done();
1✔
3318

1✔
3319
        let db = app_ctx.session_context(session).db();
1✔
3320

1✔
3321
        // wait for quota to be recorded
1✔
3322
        let mut success = false;
1✔
3323
        for _ in 0..10 {
2✔
3324
            let used = db.quota_used().await.unwrap();
7✔
3325
            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
2✔
3326

3327
            if used == 2 {
2✔
3328
                success = true;
1✔
3329
                break;
1✔
3330
            }
1✔
3331
        }
3332

3333
        assert!(success);
1✔
3334
    }
1✔
3335

3336
    #[ge_context::test]
3✔
3337
    async fn it_tracks_available_quota(app_ctx: ProPostgresContext<NoTls>) {
1✔
3338
        let user = app_ctx
1✔
3339
            .register_user(UserRegistration {
1✔
3340
                email: "foo@example.com".to_string(),
1✔
3341
                password: "secret1234".to_string(),
1✔
3342
                real_name: "Foo Bar".to_string(),
1✔
3343
            })
1✔
3344
            .await
11✔
3345
            .unwrap();
1✔
3346

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

3355
        let admin_session = admin_login(&app_ctx).await;
11✔
3356

3357
        app_ctx
1✔
3358
            .session_context(admin_session.clone())
1✔
3359
            .db()
1✔
3360
            .update_quota_available_by_user(&user, 1)
1✔
3361
            .await
3✔
3362
            .unwrap();
1✔
3363

1✔
3364
        let quota = initialize_quota_tracking(
1✔
3365
            QuotaTrackingMode::Check,
1✔
3366
            app_ctx.session_context(admin_session).db(),
1✔
3367
            0,
1✔
3368
            60,
1✔
3369
        );
1✔
3370

1✔
3371
        let tracking = quota.create_quota_tracking(&session, ComputationContext::new());
1✔
3372

1✔
3373
        tracking.work_unit_done();
1✔
3374
        tracking.work_unit_done();
1✔
3375

1✔
3376
        let db = app_ctx.session_context(session).db();
1✔
3377

1✔
3378
        // wait for quota to be recorded
1✔
3379
        let mut success = false;
1✔
3380
        for _ in 0..10 {
3✔
3381
            let available = db.quota_available().await.unwrap();
10✔
3382
            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
3✔
3383

3384
            if available == -1 {
3✔
3385
                success = true;
1✔
3386
                break;
1✔
3387
            }
2✔
3388
        }
3389

3390
        assert!(success);
1✔
3391
    }
1✔
3392

3393
    #[ge_context::test]
3✔
3394
    async fn it_updates_quota_in_postgres(app_ctx: ProPostgresContext<NoTls>) {
1✔
3395
        let user = app_ctx
1✔
3396
            .register_user(UserRegistration {
1✔
3397
                email: "foo@example.com".to_string(),
1✔
3398
                password: "secret1234".to_string(),
1✔
3399
                real_name: "Foo Bar".to_string(),
1✔
3400
            })
1✔
3401
            .await
11✔
3402
            .unwrap();
1✔
3403

3404
        let session = app_ctx
1✔
3405
            .login(UserCredentials {
1✔
3406
                email: "foo@example.com".to_string(),
1✔
3407
                password: "secret1234".to_string(),
1✔
3408
            })
1✔
3409
            .await
11✔
3410
            .unwrap();
1✔
3411

1✔
3412
        let db = app_ctx.session_context(session.clone()).db();
1✔
3413
        let admin_db = app_ctx.session_context(UserSession::admin_session()).db();
1✔
3414

3415
        assert_eq!(
1✔
3416
            db.quota_available().await.unwrap(),
3✔
3417
            crate::util::config::get_config_element::<crate::pro::util::config::Quota>()
1✔
3418
                .unwrap()
1✔
3419
                .initial_credits
3420
        );
3421

3422
        assert_eq!(
1✔
3423
            admin_db.quota_available_by_user(&user).await.unwrap(),
3✔
3424
            crate::util::config::get_config_element::<crate::pro::util::config::Quota>()
1✔
3425
                .unwrap()
1✔
3426
                .initial_credits
3427
        );
3428

3429
        admin_db
1✔
3430
            .update_quota_available_by_user(&user, 123)
1✔
3431
            .await
3✔
3432
            .unwrap();
1✔
3433

3434
        assert_eq!(db.quota_available().await.unwrap(), 123);
3✔
3435

3436
        assert_eq!(admin_db.quota_available_by_user(&user).await.unwrap(), 123);
3✔
3437
    }
1✔
3438

3439
    #[allow(clippy::too_many_lines)]
3440
    #[ge_context::test]
3✔
3441
    async fn it_removes_layer_collections(app_ctx: ProPostgresContext<NoTls>) {
1✔
3442
        let session = admin_login(&app_ctx).await;
11✔
3443

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

1✔
3446
        let layer = AddLayer {
1✔
3447
            name: "layer".to_string(),
1✔
3448
            description: "description".to_string(),
1✔
3449
            workflow: Workflow {
1✔
3450
                operator: TypedOperator::Vector(
1✔
3451
                    MockPointSource {
1✔
3452
                        params: MockPointSourceParams {
1✔
3453
                            points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3454
                        },
1✔
3455
                    }
1✔
3456
                    .boxed(),
1✔
3457
                ),
1✔
3458
            },
1✔
3459
            symbology: None,
1✔
3460
            metadata: Default::default(),
1✔
3461
            properties: Default::default(),
1✔
3462
        };
1✔
3463

3464
        let root_collection = &layer_db.get_root_layer_collection_id().await.unwrap();
1✔
3465

1✔
3466
        let collection = AddLayerCollection {
1✔
3467
            name: "top collection".to_string(),
1✔
3468
            description: "description".to_string(),
1✔
3469
            properties: Default::default(),
1✔
3470
        };
1✔
3471

3472
        let top_c_id = layer_db
1✔
3473
            .add_layer_collection(collection, root_collection)
1✔
3474
            .await
11✔
3475
            .unwrap();
1✔
3476

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

1✔
3479
        let collection = AddLayerCollection {
1✔
3480
            name: "empty collection".to_string(),
1✔
3481
            description: "description".to_string(),
1✔
3482
            properties: Default::default(),
1✔
3483
        };
1✔
3484

3485
        let empty_c_id = layer_db
1✔
3486
            .add_layer_collection(collection, &top_c_id)
1✔
3487
            .await
9✔
3488
            .unwrap();
1✔
3489

3490
        let items = layer_db
1✔
3491
            .load_layer_collection(
1✔
3492
                &top_c_id,
1✔
3493
                LayerCollectionListOptions {
1✔
3494
                    offset: 0,
1✔
3495
                    limit: 20,
1✔
3496
                },
1✔
3497
            )
1✔
3498
            .await
9✔
3499
            .unwrap();
1✔
3500

1✔
3501
        assert_eq!(
1✔
3502
            items,
1✔
3503
            LayerCollection {
1✔
3504
                id: ProviderLayerCollectionId {
1✔
3505
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
3506
                    collection_id: top_c_id.clone(),
1✔
3507
                },
1✔
3508
                name: "top collection".to_string(),
1✔
3509
                description: "description".to_string(),
1✔
3510
                items: vec![
1✔
3511
                    CollectionItem::Collection(LayerCollectionListing {
1✔
3512
                        id: ProviderLayerCollectionId {
1✔
3513
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
3514
                            collection_id: empty_c_id.clone(),
1✔
3515
                        },
1✔
3516
                        name: "empty collection".to_string(),
1✔
3517
                        description: "description".to_string(),
1✔
3518
                        properties: Default::default(),
1✔
3519
                    }),
1✔
3520
                    CollectionItem::Layer(LayerListing {
1✔
3521
                        id: ProviderLayerId {
1✔
3522
                            provider_id: INTERNAL_PROVIDER_ID,
1✔
3523
                            layer_id: l_id.clone(),
1✔
3524
                        },
1✔
3525
                        name: "layer".to_string(),
1✔
3526
                        description: "description".to_string(),
1✔
3527
                        properties: vec![],
1✔
3528
                    })
1✔
3529
                ],
1✔
3530
                entry_label: None,
1✔
3531
                properties: vec![],
1✔
3532
            }
1✔
3533
        );
1✔
3534

3535
        // remove empty collection
3536
        layer_db.remove_layer_collection(&empty_c_id).await.unwrap();
11✔
3537

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

1✔
3549
        assert_eq!(
1✔
3550
            items,
1✔
3551
            LayerCollection {
1✔
3552
                id: ProviderLayerCollectionId {
1✔
3553
                    provider_id: INTERNAL_PROVIDER_ID,
1✔
3554
                    collection_id: top_c_id.clone(),
1✔
3555
                },
1✔
3556
                name: "top collection".to_string(),
1✔
3557
                description: "description".to_string(),
1✔
3558
                items: vec![CollectionItem::Layer(LayerListing {
1✔
3559
                    id: ProviderLayerId {
1✔
3560
                        provider_id: INTERNAL_PROVIDER_ID,
1✔
3561
                        layer_id: l_id.clone(),
1✔
3562
                    },
1✔
3563
                    name: "layer".to_string(),
1✔
3564
                    description: "description".to_string(),
1✔
3565
                    properties: vec![],
1✔
3566
                })],
1✔
3567
                entry_label: None,
1✔
3568
                properties: vec![],
1✔
3569
            }
1✔
3570
        );
1✔
3571

3572
        // remove top (not root) collection
3573
        layer_db.remove_layer_collection(&top_c_id).await.unwrap();
11✔
3574

1✔
3575
        layer_db
1✔
3576
            .load_layer_collection(
1✔
3577
                &top_c_id,
1✔
3578
                LayerCollectionListOptions {
1✔
3579
                    offset: 0,
1✔
3580
                    limit: 20,
1✔
3581
                },
1✔
3582
            )
1✔
3583
            .await
4✔
3584
            .unwrap_err();
1✔
3585

1✔
3586
        // should be deleted automatically
1✔
3587
        layer_db.load_layer(&l_id).await.unwrap_err();
4✔
3588

1✔
3589
        // it is not allowed to remove the root collection
1✔
3590
        layer_db
1✔
3591
            .remove_layer_collection(root_collection)
1✔
3592
            .await
4✔
3593
            .unwrap_err();
1✔
3594
        layer_db
1✔
3595
            .load_layer_collection(
1✔
3596
                root_collection,
1✔
3597
                LayerCollectionListOptions {
1✔
3598
                    offset: 0,
1✔
3599
                    limit: 20,
1✔
3600
                },
1✔
3601
            )
1✔
3602
            .await
9✔
3603
            .unwrap();
1✔
3604
    }
1✔
3605

3606
    #[ge_context::test]
3✔
3607
    #[allow(clippy::too_many_lines)]
3608
    async fn it_removes_collections_from_collections(app_ctx: ProPostgresContext<NoTls>) {
1✔
3609
        let session = admin_login(&app_ctx).await;
2✔
3610

3611
        let db = app_ctx.session_context(session).db();
1✔
3612

3613
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
3614

3615
        let mid_collection_id = db
1✔
3616
            .add_layer_collection(
1✔
3617
                AddLayerCollection {
1✔
3618
                    name: "mid collection".to_string(),
1✔
3619
                    description: "description".to_string(),
1✔
3620
                    properties: Default::default(),
1✔
3621
                },
1✔
3622
                root_collection_id,
1✔
3623
            )
1✔
3624
            .await
1✔
3625
            .unwrap();
1✔
3626

3627
        let bottom_collection_id = db
1✔
3628
            .add_layer_collection(
1✔
3629
                AddLayerCollection {
1✔
3630
                    name: "bottom collection".to_string(),
1✔
3631
                    description: "description".to_string(),
1✔
3632
                    properties: Default::default(),
1✔
3633
                },
1✔
3634
                &mid_collection_id,
1✔
3635
            )
1✔
3636
            .await
1✔
3637
            .unwrap();
1✔
3638

3639
        let layer_id = db
1✔
3640
            .add_layer(
1✔
3641
                AddLayer {
1✔
3642
                    name: "layer".to_string(),
1✔
3643
                    description: "description".to_string(),
1✔
3644
                    workflow: Workflow {
1✔
3645
                        operator: TypedOperator::Vector(
1✔
3646
                            MockPointSource {
1✔
3647
                                params: MockPointSourceParams {
1✔
3648
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3649
                                },
1✔
3650
                            }
1✔
3651
                            .boxed(),
1✔
3652
                        ),
1✔
3653
                    },
1✔
3654
                    symbology: None,
1✔
3655
                    metadata: Default::default(),
1✔
3656
                    properties: Default::default(),
1✔
3657
                },
1✔
3658
                &mid_collection_id,
1✔
3659
            )
1✔
3660
            .await
12✔
3661
            .unwrap();
1✔
3662

1✔
3663
        // removing the mid collection…
1✔
3664
        db.remove_layer_collection_from_parent(&mid_collection_id, root_collection_id)
1✔
3665
            .await
13✔
3666
            .unwrap();
1✔
3667

1✔
3668
        // …should remove itself
1✔
3669
        db.load_layer_collection(&mid_collection_id, LayerCollectionListOptions::default())
1✔
3670
            .await
4✔
3671
            .unwrap_err();
1✔
3672

1✔
3673
        // …should remove the bottom collection
1✔
3674
        db.load_layer_collection(&bottom_collection_id, LayerCollectionListOptions::default())
1✔
3675
            .await
4✔
3676
            .unwrap_err();
1✔
3677

1✔
3678
        // … and should remove the layer of the bottom collection
1✔
3679
        db.load_layer(&layer_id).await.unwrap_err();
4✔
3680

1✔
3681
        // the root collection is still there
1✔
3682
        db.load_layer_collection(root_collection_id, LayerCollectionListOptions::default())
1✔
3683
            .await
9✔
3684
            .unwrap();
1✔
3685
    }
1✔
3686

3687
    #[ge_context::test]
3✔
3688
    #[allow(clippy::too_many_lines)]
3689
    async fn it_removes_layers_from_collections(app_ctx: ProPostgresContext<NoTls>) {
1✔
3690
        let session = admin_login(&app_ctx).await;
6✔
3691

3692
        let db = app_ctx.session_context(session).db();
1✔
3693

3694
        let root_collection = &db.get_root_layer_collection_id().await.unwrap();
1✔
3695

3696
        let another_collection = db
1✔
3697
            .add_layer_collection(
1✔
3698
                AddLayerCollection {
1✔
3699
                    name: "top collection".to_string(),
1✔
3700
                    description: "description".to_string(),
1✔
3701
                    properties: Default::default(),
1✔
3702
                },
1✔
3703
                root_collection,
1✔
3704
            )
1✔
3705
            .await
2✔
3706
            .unwrap();
1✔
3707

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

3732
        let layer_in_two_collections = db
1✔
3733
            .add_layer(
1✔
3734
                AddLayer {
1✔
3735
                    name: "layer 2".to_string(),
1✔
3736
                    description: "description".to_string(),
1✔
3737
                    workflow: Workflow {
1✔
3738
                        operator: TypedOperator::Vector(
1✔
3739
                            MockPointSource {
1✔
3740
                                params: MockPointSourceParams {
1✔
3741
                                    points: vec![Coordinate2D::new(1., 2.); 3],
1✔
3742
                                },
1✔
3743
                            }
1✔
3744
                            .boxed(),
1✔
3745
                        ),
1✔
3746
                    },
1✔
3747
                    symbology: None,
1✔
3748
                    metadata: Default::default(),
1✔
3749
                    properties: Default::default(),
1✔
3750
                },
1✔
3751
                &another_collection,
1✔
3752
            )
1✔
3753
            .await
13✔
3754
            .unwrap();
1✔
3755

1✔
3756
        db.load_layer(&layer_in_two_collections).await.unwrap();
7✔
3757

1✔
3758
        db.add_layer_to_collection(&layer_in_two_collections, root_collection)
1✔
3759
            .await
7✔
3760
            .unwrap();
1✔
3761

1✔
3762
        // remove first layer --> should be deleted entirely
1✔
3763

1✔
3764
        db.remove_layer_from_collection(&layer_in_one_collection, &another_collection)
1✔
3765
            .await
9✔
3766
            .unwrap();
1✔
3767

3768
        let number_of_layer_in_collection = db
1✔
3769
            .load_layer_collection(
1✔
3770
                &another_collection,
1✔
3771
                LayerCollectionListOptions {
1✔
3772
                    offset: 0,
1✔
3773
                    limit: 20,
1✔
3774
                },
1✔
3775
            )
1✔
3776
            .await
9✔
3777
            .unwrap()
1✔
3778
            .items
1✔
3779
            .len();
1✔
3780
        assert_eq!(
1✔
3781
            number_of_layer_in_collection,
1✔
3782
            1 /* only the other collection should be here */
1✔
3783
        );
1✔
3784

3785
        db.load_layer(&layer_in_one_collection).await.unwrap_err();
4✔
3786

1✔
3787
        // remove second layer --> should only be gone in collection
1✔
3788

1✔
3789
        db.remove_layer_from_collection(&layer_in_two_collections, &another_collection)
1✔
3790
            .await
9✔
3791
            .unwrap();
1✔
3792

3793
        let number_of_layer_in_collection = db
1✔
3794
            .load_layer_collection(
1✔
3795
                &another_collection,
1✔
3796
                LayerCollectionListOptions {
1✔
3797
                    offset: 0,
1✔
3798
                    limit: 20,
1✔
3799
                },
1✔
3800
            )
1✔
3801
            .await
9✔
3802
            .unwrap()
1✔
3803
            .items
1✔
3804
            .len();
1✔
3805
        assert_eq!(
1✔
3806
            number_of_layer_in_collection,
1✔
3807
            0 /* both layers were deleted */
1✔
3808
        );
1✔
3809

3810
        db.load_layer(&layer_in_two_collections).await.unwrap();
7✔
3811
    }
1✔
3812

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

1✔
3847
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
3848
            OgrSourceDataset,
1✔
3849
            VectorResultDescriptor,
1✔
3850
            VectorQueryRectangle,
1✔
3851
        > {
1✔
3852
            loading_info: loading_info.clone(),
1✔
3853
            result_descriptor: VectorResultDescriptor {
1✔
3854
                data_type: VectorDataType::MultiPoint,
1✔
3855
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3856
                columns: [(
1✔
3857
                    "foo".to_owned(),
1✔
3858
                    VectorColumnInfo {
1✔
3859
                        data_type: FeatureDataType::Float,
1✔
3860
                        measurement: Measurement::Unitless,
1✔
3861
                    },
1✔
3862
                )]
1✔
3863
                .into_iter()
1✔
3864
                .collect(),
1✔
3865
                time: None,
1✔
3866
                bbox: None,
1✔
3867
            },
1✔
3868
            phantom: Default::default(),
1✔
3869
        });
1✔
3870

3871
        let session = app_ctx.create_anonymous_session().await.unwrap();
13✔
3872

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

1✔
3875
        let db = app_ctx.session_context(session.clone()).db();
1✔
3876
        let dataset_id = db
1✔
3877
            .add_dataset(
1✔
3878
                AddDataset {
1✔
3879
                    name: Some(dataset_name),
1✔
3880
                    display_name: "Ogr Test".to_owned(),
1✔
3881
                    description: "desc".to_owned(),
1✔
3882
                    source_operator: "OgrSource".to_owned(),
1✔
3883
                    symbology: None,
1✔
3884
                    provenance: Some(vec![Provenance {
1✔
3885
                        citation: "citation".to_owned(),
1✔
3886
                        license: "license".to_owned(),
1✔
3887
                        uri: "uri".to_owned(),
1✔
3888
                    }]),
1✔
3889
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
3890
                },
1✔
3891
                meta_data,
1✔
3892
            )
1✔
3893
            .await
178✔
3894
            .unwrap()
1✔
3895
            .id;
1✔
3896

1✔
3897
        assert!(db.load_dataset(&dataset_id).await.is_ok());
15✔
3898

3899
        db.delete_dataset(dataset_id).await.unwrap();
14✔
3900

1✔
3901
        assert!(db.load_dataset(&dataset_id).await.is_err());
11✔
3902
    }
1✔
3903

3904
    #[ge_context::test]
3✔
3905
    #[allow(clippy::too_many_lines)]
3906
    async fn it_deletes_admin_dataset(app_ctx: ProPostgresContext<NoTls>) {
1✔
3907
        let dataset_name = DatasetName::new(None, "my_dataset");
1✔
3908

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

1✔
3940
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
3941
            OgrSourceDataset,
1✔
3942
            VectorResultDescriptor,
1✔
3943
            VectorQueryRectangle,
1✔
3944
        > {
1✔
3945
            loading_info: loading_info.clone(),
1✔
3946
            result_descriptor: VectorResultDescriptor {
1✔
3947
                data_type: VectorDataType::MultiPoint,
1✔
3948
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
3949
                columns: [(
1✔
3950
                    "foo".to_owned(),
1✔
3951
                    VectorColumnInfo {
1✔
3952
                        data_type: FeatureDataType::Float,
1✔
3953
                        measurement: Measurement::Unitless,
1✔
3954
                    },
1✔
3955
                )]
1✔
3956
                .into_iter()
1✔
3957
                .collect(),
1✔
3958
                time: None,
1✔
3959
                bbox: None,
1✔
3960
            },
1✔
3961
            phantom: Default::default(),
1✔
3962
        });
1✔
3963

3964
        let session = admin_login(&app_ctx).await;
2✔
3965

3966
        let db = app_ctx.session_context(session).db();
1✔
3967
        let dataset_id = db
1✔
3968
            .add_dataset(
1✔
3969
                AddDataset {
1✔
3970
                    name: Some(dataset_name),
1✔
3971
                    display_name: "Ogr Test".to_owned(),
1✔
3972
                    description: "desc".to_owned(),
1✔
3973
                    source_operator: "OgrSource".to_owned(),
1✔
3974
                    symbology: None,
1✔
3975
                    provenance: Some(vec![Provenance {
1✔
3976
                        citation: "citation".to_owned(),
1✔
3977
                        license: "license".to_owned(),
1✔
3978
                        uri: "uri".to_owned(),
1✔
3979
                    }]),
1✔
3980
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
3981
                },
1✔
3982
                meta_data,
1✔
3983
            )
1✔
3984
            .await
130✔
3985
            .unwrap()
1✔
3986
            .id;
1✔
3987

1✔
3988
        assert!(db.load_dataset(&dataset_id).await.is_ok());
15✔
3989

3990
        db.delete_dataset(dataset_id).await.unwrap();
14✔
3991

1✔
3992
        assert!(db.load_dataset(&dataset_id).await.is_err());
11✔
3993
    }
1✔
3994

3995
    #[ge_context::test]
3✔
3996
    async fn test_missing_layer_dataset_in_collection_listing(app_ctx: ProPostgresContext<NoTls>) {
1✔
3997
        let session = admin_login(&app_ctx).await;
5✔
3998
        let db = app_ctx.session_context(session).db();
1✔
3999

4000
        let root_collection_id = &db.get_root_layer_collection_id().await.unwrap();
1✔
4001

4002
        let top_collection_id = db
1✔
4003
            .add_layer_collection(
1✔
4004
                AddLayerCollection {
1✔
4005
                    name: "top collection".to_string(),
1✔
4006
                    description: "description".to_string(),
1✔
4007
                    properties: Default::default(),
1✔
4008
                },
1✔
4009
                root_collection_id,
1✔
4010
            )
1✔
4011
            .await
1✔
4012
            .unwrap();
1✔
4013

1✔
4014
        let faux_layer = LayerId("faux".to_string());
1✔
4015

1✔
4016
        // this should fail
1✔
4017
        db.add_layer_to_collection(&faux_layer, &top_collection_id)
1✔
UNCOV
4018
            .await
×
4019
            .unwrap_err();
1✔
4020

4021
        let root_collection_layers = db
1✔
4022
            .load_layer_collection(
1✔
4023
                &top_collection_id,
1✔
4024
                LayerCollectionListOptions {
1✔
4025
                    offset: 0,
1✔
4026
                    limit: 20,
1✔
4027
                },
1✔
4028
            )
1✔
UNCOV
4029
            .await
×
4030
            .unwrap();
1✔
4031

1✔
4032
        assert_eq!(
1✔
4033
            root_collection_layers,
1✔
4034
            LayerCollection {
1✔
4035
                id: ProviderLayerCollectionId {
1✔
4036
                    provider_id: DataProviderId(
1✔
4037
                        "ce5e84db-cbf9-48a2-9a32-d4b7cc56ea74".try_into().unwrap()
1✔
4038
                    ),
1✔
4039
                    collection_id: top_collection_id.clone(),
1✔
4040
                },
1✔
4041
                name: "top collection".to_string(),
1✔
4042
                description: "description".to_string(),
1✔
4043
                items: vec![],
1✔
4044
                entry_label: None,
1✔
4045
                properties: vec![],
1✔
4046
            }
1✔
4047
        );
1✔
4048
    }
1✔
4049

4050
    #[allow(clippy::too_many_lines)]
4051
    #[ge_context::test]
3✔
4052
    async fn it_restricts_layer_permissions(app_ctx: ProPostgresContext<NoTls>) {
1✔
4053
        let admin_session = admin_login(&app_ctx).await;
2✔
4054
        let session1 = app_ctx.create_anonymous_session().await.unwrap();
1✔
4055

1✔
4056
        let admin_db = app_ctx.session_context(admin_session.clone()).db();
1✔
4057
        let db1 = app_ctx.session_context(session1.clone()).db();
1✔
4058

4059
        let root = admin_db.get_root_layer_collection_id().await.unwrap();
1✔
4060

4061
        // add new collection as admin
4062
        let new_collection_id = admin_db
1✔
4063
            .add_layer_collection(
1✔
4064
                AddLayerCollection {
1✔
4065
                    name: "admin collection".to_string(),
1✔
4066
                    description: String::new(),
1✔
4067
                    properties: Default::default(),
1✔
4068
                },
1✔
4069
                &root,
1✔
4070
            )
1✔
4071
            .await
12✔
4072
            .unwrap();
1✔
4073

4074
        // load as regular user, not visible
4075
        let collection = db1
1✔
4076
            .load_layer_collection(
1✔
4077
                &root,
1✔
4078
                LayerCollectionListOptions {
1✔
4079
                    offset: 0,
1✔
4080
                    limit: 10,
1✔
4081
                },
1✔
4082
            )
1✔
4083
            .await
1✔
4084
            .unwrap();
1✔
4085
        assert!(!collection.items.iter().any(|c| match c {
1✔
4086
            CollectionItem::Collection(c) => c.id.collection_id == new_collection_id,
1✔
4087
            CollectionItem::Layer(_) => false,
×
4088
        }));
1✔
4089

4090
        // give user read permission
4091
        admin_db
1✔
4092
            .add_permission(
1✔
4093
                session1.user.id.into(),
1✔
4094
                new_collection_id.clone(),
1✔
4095
                Permission::Read,
1✔
4096
            )
1✔
4097
            .await
1✔
4098
            .unwrap();
1✔
4099

4100
        // now visible
4101
        let collection = db1
1✔
4102
            .load_layer_collection(
1✔
4103
                &root,
1✔
4104
                LayerCollectionListOptions {
1✔
4105
                    offset: 0,
1✔
4106
                    limit: 10,
1✔
4107
                },
1✔
4108
            )
1✔
UNCOV
4109
            .await
×
4110
            .unwrap();
1✔
4111

1✔
4112
        assert!(collection.items.iter().any(|c| match c {
1✔
4113
            CollectionItem::Collection(c) => c.id.collection_id == new_collection_id,
1✔
4114
            CollectionItem::Layer(_) => false,
×
4115
        }));
1✔
4116
    }
1✔
4117

4118
    #[allow(clippy::too_many_lines)]
4119
    #[ge_context::test]
3✔
4120
    async fn it_handles_user_roles(app_ctx: ProPostgresContext<NoTls>) {
1✔
4121
        let admin_session = admin_login(&app_ctx).await;
9✔
4122
        let user_id = app_ctx
1✔
4123
            .register_user(UserRegistration {
1✔
4124
                email: "foo@example.com".to_string(),
1✔
4125
                password: "secret123".to_string(),
1✔
4126
                real_name: "Foo Bar".to_string(),
1✔
4127
            })
1✔
4128
            .await
11✔
4129
            .unwrap();
1✔
4130

1✔
4131
        let admin_db = app_ctx.session_context(admin_session.clone()).db();
1✔
4132

4133
        // create a new role
4134
        let role_id = admin_db.add_role("foo").await.unwrap();
5✔
4135

4136
        let user_session = app_ctx
1✔
4137
            .login(UserCredentials {
1✔
4138
                email: "foo@example.com".to_string(),
1✔
4139
                password: "secret123".to_string(),
1✔
4140
            })
1✔
4141
            .await
11✔
4142
            .unwrap();
1✔
4143

1✔
4144
        // user does not have the role yet
1✔
4145

1✔
4146
        assert!(!user_session.roles.contains(&role_id));
1✔
4147

4148
        //user can query their role descriptions (user role and registered user)
4149
        assert_eq!(user_session.roles.len(), 2);
1✔
4150

4151
        let expected_user_role_description = RoleDescription {
1✔
4152
            role: Role {
1✔
4153
                id: RoleId::from(user_id),
1✔
4154
                name: "foo@example.com".to_string(),
1✔
4155
            },
1✔
4156
            individual: true,
1✔
4157
        };
1✔
4158
        let expected_registered_role_description = RoleDescription {
1✔
4159
            role: Role {
1✔
4160
                id: Role::registered_user_role_id(),
1✔
4161
                name: "user".to_string(),
1✔
4162
            },
1✔
4163
            individual: false,
1✔
4164
        };
1✔
4165

4166
        let user_role_descriptions = app_ctx
1✔
4167
            .session_context(user_session.clone())
1✔
4168
            .db()
1✔
4169
            .get_role_descriptions(&user_id)
1✔
4170
            .await
3✔
4171
            .unwrap();
1✔
4172
        assert_eq!(
1✔
4173
            vec![
1✔
4174
                expected_user_role_description.clone(),
1✔
4175
                expected_registered_role_description.clone(),
1✔
4176
            ],
1✔
4177
            user_role_descriptions
1✔
4178
        );
1✔
4179

4180
        // we assign the role to the user
4181
        admin_db.assign_role(&role_id, &user_id).await.unwrap();
5✔
4182

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

1✔
4191
        // should be present now
1✔
4192
        assert!(user_session.roles.contains(&role_id));
1✔
4193

4194
        //user can query their role descriptions (now an additional foo role)
4195
        let expected_foo_role_description = RoleDescription {
1✔
4196
            role: Role {
1✔
4197
                id: role_id,
1✔
4198
                name: "foo".to_string(),
1✔
4199
            },
1✔
4200
            individual: false,
1✔
4201
        };
1✔
4202

4203
        let user_role_descriptions = app_ctx
1✔
4204
            .session_context(user_session.clone())
1✔
4205
            .db()
1✔
4206
            .get_role_descriptions(&user_id)
1✔
4207
            .await
3✔
4208
            .unwrap();
1✔
4209
        assert_eq!(
1✔
4210
            vec![
1✔
4211
                expected_foo_role_description,
1✔
4212
                expected_user_role_description.clone(),
1✔
4213
                expected_registered_role_description.clone(),
1✔
4214
            ],
1✔
4215
            user_role_descriptions
1✔
4216
        );
1✔
4217

4218
        // we revoke it
4219
        admin_db.revoke_role(&role_id, &user_id).await.unwrap();
5✔
4220

4221
        let user_session = app_ctx
1✔
4222
            .login(UserCredentials {
1✔
4223
                email: "foo@example.com".to_string(),
1✔
4224
                password: "secret123".to_string(),
1✔
4225
            })
1✔
4226
            .await
11✔
4227
            .unwrap();
1✔
4228

1✔
4229
        // the role is gone now
1✔
4230
        assert!(!user_session.roles.contains(&role_id));
1✔
4231

4232
        //user can query their role descriptions (user role and registered user)
4233
        let user_role_descriptions = app_ctx
1✔
4234
            .session_context(user_session.clone())
1✔
4235
            .db()
1✔
4236
            .get_role_descriptions(&user_id)
1✔
4237
            .await
3✔
4238
            .unwrap();
1✔
4239
        assert_eq!(
1✔
4240
            vec![
1✔
4241
                expected_user_role_description.clone(),
1✔
4242
                expected_registered_role_description.clone(),
1✔
4243
            ],
1✔
4244
            user_role_descriptions
1✔
4245
        );
1✔
4246

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

4249
        admin_db.assign_role(&role_id, &user_id).await.unwrap();
5✔
4250

1✔
4251
        admin_db.remove_role(&role_id).await.unwrap();
5✔
4252

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

1✔
4261
        assert!(!user_session.roles.contains(&role_id));
1✔
4262

4263
        //user can query their role descriptions (user role and registered user)
4264
        let user_role_descriptions = app_ctx
1✔
4265
            .session_context(user_session.clone())
1✔
4266
            .db()
1✔
4267
            .get_role_descriptions(&user_id)
1✔
4268
            .await
3✔
4269
            .unwrap();
1✔
4270
        assert_eq!(
1✔
4271
            vec![
1✔
4272
                expected_user_role_description,
1✔
4273
                expected_registered_role_description.clone(),
1✔
4274
            ],
1✔
4275
            user_role_descriptions
1✔
4276
        );
1✔
4277
    }
1✔
4278

4279
    #[allow(clippy::too_many_lines)]
4280
    #[ge_context::test]
3✔
4281
    async fn it_updates_project_layer_symbology(app_ctx: ProPostgresContext<NoTls>) {
1✔
4282
        let session = app_ctx.create_anonymous_session().await.unwrap();
14✔
4283

4284
        let (_, workflow_id) = register_ndvi_workflow_helper(&app_ctx).await;
197✔
4285

4286
        let db = app_ctx.session_context(session.clone()).db();
1✔
4287

1✔
4288
        let create_project: CreateProject = serde_json::from_value(json!({
1✔
4289
            "name": "Default",
1✔
4290
            "description": "Default project",
1✔
4291
            "bounds": {
1✔
4292
                "boundingBox": {
1✔
4293
                    "lowerLeftCoordinate": {
1✔
4294
                        "x": -180,
1✔
4295
                        "y": -90
1✔
4296
                    },
1✔
4297
                    "upperRightCoordinate": {
1✔
4298
                        "x": 180,
1✔
4299
                        "y": 90
1✔
4300
                    }
1✔
4301
                },
1✔
4302
                "spatialReference": "EPSG:4326",
1✔
4303
                "timeInterval": {
1✔
4304
                    "start": 1_396_353_600_000i64,
1✔
4305
                    "end": 1_396_353_600_000i64
1✔
4306
                }
1✔
4307
            },
1✔
4308
            "timeStep": {
1✔
4309
                "step": 1,
1✔
4310
                "granularity": "months"
1✔
4311
            }
1✔
4312
        }))
1✔
4313
        .unwrap();
1✔
4314

4315
        let project_id = db.create_project(create_project).await.unwrap();
13✔
4316

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

1✔
4351
        db.update_project(update).await.unwrap();
79✔
4352

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

1✔
4429
        db.update_project(update).await.unwrap();
77✔
4430

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

1✔
4507
        db.update_project(update).await.unwrap();
22✔
4508

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

4585
        // run two updates concurrently
4586
        let (r0, r1) = join!(db.update_project(update.clone()), db.update_project(update));
1✔
4587

4588
        assert!(r0.is_ok());
1✔
4589
        assert!(r1.is_ok());
1✔
4590
    }
1✔
4591

4592
    #[ge_context::test]
3✔
4593
    #[allow(clippy::too_many_lines)]
4594
    async fn it_resolves_dataset_names_to_ids(app_ctx: ProPostgresContext<NoTls>) {
1✔
4595
        let admin_session = UserSession::admin_session();
1✔
4596
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
4597

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

1✔
4629
        let meta_data = MetaDataDefinition::OgrMetaData(StaticMetaData::<
1✔
4630
            OgrSourceDataset,
1✔
4631
            VectorResultDescriptor,
1✔
4632
            VectorQueryRectangle,
1✔
4633
        > {
1✔
4634
            loading_info: loading_info.clone(),
1✔
4635
            result_descriptor: VectorResultDescriptor {
1✔
4636
                data_type: VectorDataType::MultiPoint,
1✔
4637
                spatial_reference: SpatialReference::epsg_4326().into(),
1✔
4638
                columns: [(
1✔
4639
                    "foo".to_owned(),
1✔
4640
                    VectorColumnInfo {
1✔
4641
                        data_type: FeatureDataType::Float,
1✔
4642
                        measurement: Measurement::Unitless,
1✔
4643
                    },
1✔
4644
                )]
1✔
4645
                .into_iter()
1✔
4646
                .collect(),
1✔
4647
                time: None,
1✔
4648
                bbox: None,
1✔
4649
            },
1✔
4650
            phantom: Default::default(),
1✔
4651
        });
1✔
4652

4653
        let DatasetIdAndName {
4654
            id: dataset_id1,
1✔
4655
            name: dataset_name1,
1✔
4656
        } = db
1✔
4657
            .add_dataset(
1✔
4658
                AddDataset {
1✔
4659
                    name: Some(DatasetName::new(None, "my_dataset".to_owned())),
1✔
4660
                    display_name: "Ogr Test".to_owned(),
1✔
4661
                    description: "desc".to_owned(),
1✔
4662
                    source_operator: "OgrSource".to_owned(),
1✔
4663
                    symbology: None,
1✔
4664
                    provenance: Some(vec![Provenance {
1✔
4665
                        citation: "citation".to_owned(),
1✔
4666
                        license: "license".to_owned(),
1✔
4667
                        uri: "uri".to_owned(),
1✔
4668
                    }]),
1✔
4669
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
4670
                },
1✔
4671
                meta_data.clone(),
1✔
4672
            )
1✔
4673
            .await
178✔
4674
            .unwrap();
1✔
4675

4676
        let DatasetIdAndName {
4677
            id: dataset_id2,
1✔
4678
            name: dataset_name2,
1✔
4679
        } = db
1✔
4680
            .add_dataset(
1✔
4681
                AddDataset {
1✔
4682
                    name: Some(DatasetName::new(
1✔
4683
                        Some(admin_session.user.id.to_string()),
1✔
4684
                        "my_dataset".to_owned(),
1✔
4685
                    )),
1✔
4686
                    display_name: "Ogr Test".to_owned(),
1✔
4687
                    description: "desc".to_owned(),
1✔
4688
                    source_operator: "OgrSource".to_owned(),
1✔
4689
                    symbology: None,
1✔
4690
                    provenance: Some(vec![Provenance {
1✔
4691
                        citation: "citation".to_owned(),
1✔
4692
                        license: "license".to_owned(),
1✔
4693
                        uri: "uri".to_owned(),
1✔
4694
                    }]),
1✔
4695
                    tags: Some(vec!["upload".to_owned(), "test".to_owned()]),
1✔
4696
                },
1✔
4697
                meta_data,
1✔
4698
            )
1✔
4699
            .await
7✔
4700
            .unwrap();
1✔
4701

4702
        assert_eq!(
1✔
4703
            db.resolve_dataset_name_to_id(&dataset_name1)
1✔
4704
                .await
15✔
4705
                .unwrap()
1✔
4706
                .unwrap(),
1✔
4707
            dataset_id1
4708
        );
4709
        assert_eq!(
1✔
4710
            db.resolve_dataset_name_to_id(&dataset_name2)
1✔
4711
                .await
11✔
4712
                .unwrap()
1✔
4713
                .unwrap(),
1✔
4714
            dataset_id2
4715
        );
4716
    }
1✔
4717

4718
    #[ge_context::test]
3✔
4719
    #[allow(clippy::too_many_lines)]
4720
    async fn it_bulk_updates_quota(app_ctx: ProPostgresContext<NoTls>) {
1✔
4721
        let admin_session = UserSession::admin_session();
1✔
4722
        let db = app_ctx.session_context(admin_session.clone()).db();
1✔
4723

4724
        let user1 = app_ctx
1✔
4725
            .register_user(UserRegistration {
1✔
4726
                email: "user1@example.com".into(),
1✔
4727
                password: "12345678".into(),
1✔
4728
                real_name: "User1".into(),
1✔
4729
            })
1✔
4730
            .await
11✔
4731
            .unwrap();
1✔
4732

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

1✔
4742
        // single item in bulk
1✔
4743
        db.bulk_increment_quota_used([(user1, 1)]).await.unwrap();
3✔
4744

4745
        assert_eq!(db.quota_used_by_user(&user1).await.unwrap(), 1);
2✔
4746

4747
        // multiple items in bulk
4748
        db.bulk_increment_quota_used([(user1, 1), (user2, 3)])
1✔
4749
            .await
2✔
4750
            .unwrap();
1✔
4751

4752
        assert_eq!(db.quota_used_by_user(&user1).await.unwrap(), 2);
2✔
4753
        assert_eq!(db.quota_used_by_user(&user2).await.unwrap(), 3);
2✔
4754
    }
1✔
4755

4756
    async fn it_handles_oidc_tokens(app_ctx: ProPostgresContext<NoTls>) {
2✔
4757
        let external_user_claims = UserClaims {
2✔
4758
            external_id: SubjectIdentifier::new("Foo bar Id".into()),
2✔
4759
            email: "foo@bar.de".into(),
2✔
4760
            real_name: "Foo Bar".into(),
2✔
4761
        };
2✔
4762
        let tokens = OidcTokens {
2✔
4763
            access: AccessToken::new("FIRST_ACCESS_TOKEN".into()),
2✔
4764
            refresh: Some(RefreshToken::new("FIRST_REFRESH_TOKEN".into())),
2✔
4765
            expires_in: Duration::seconds(2),
2✔
4766
        };
2✔
4767

4768
        let login_result = app_ctx
2✔
4769
            .login_external(external_user_claims.clone(), tokens)
2✔
4770
            .await;
22✔
4771
        assert!(login_result.is_ok());
2✔
4772

4773
        let session_id = login_result.unwrap().id;
2✔
4774

4775
        let access_token = app_ctx.get_access_token(session_id).await.unwrap();
5✔
4776

2✔
4777
        assert_eq!(
2✔
4778
            "FIRST_ACCESS_TOKEN".to_string(),
2✔
4779
            access_token.secret().to_owned()
2✔
4780
        );
2✔
4781

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

4785
        let access_token = app_ctx.get_access_token(session_id).await.unwrap();
23✔
4786

2✔
4787
        assert_eq!(
2✔
4788
            "SECOND_ACCESS_TOKEN".to_string(),
2✔
4789
            access_token.secret().to_owned()
2✔
4790
        );
2✔
4791
    }
2✔
4792

4793
    pub fn oidc_only_refresh() -> (Server, impl Fn() -> OidcManager) {
1✔
4794
        let mock_refresh_server_config = MockRefreshServerConfig {
1✔
4795
            expected_discoveries: 1,
1✔
4796
            token_duration: std::time::Duration::from_secs(2),
1✔
4797
            creates_first_token: false,
1✔
4798
            first_access_token: "FIRST_ACCESS_TOKEN".to_string(),
1✔
4799
            first_refresh_token: "FIRST_REFRESH_TOKEN".to_string(),
1✔
4800
            second_access_token: "SECOND_ACCESS_TOKEN".to_string(),
1✔
4801
            second_refresh_token: "SECOND_REFRESH_TOKEN".to_string(),
1✔
4802
            client_side_password: None,
1✔
4803
        };
1✔
4804

1✔
4805
        let (server, oidc_manager) = mock_refresh_server(mock_refresh_server_config);
1✔
4806

1✔
4807
        (server, move || {
1✔
4808
            OidcManager::from_oidc_with_static_tokens(oidc_manager.clone())
1✔
4809
        })
1✔
4810
    }
1✔
4811

4812
    #[ge_context::test(oidc_db = "oidc_only_refresh")]
3✔
4813
    async fn it_handles_oidc_tokens_without_encryption(app_ctx: ProPostgresContext<NoTls>) {
1✔
4814
        it_handles_oidc_tokens(app_ctx).await;
12✔
4815
    }
1✔
4816

4817
    pub fn oidc_only_refresh_with_encryption() -> (Server, impl Fn() -> OidcManager) {
1✔
4818
        let mock_refresh_server_config = MockRefreshServerConfig {
1✔
4819
            expected_discoveries: 1,
1✔
4820
            token_duration: std::time::Duration::from_secs(2),
1✔
4821
            creates_first_token: false,
1✔
4822
            first_access_token: "FIRST_ACCESS_TOKEN".to_string(),
1✔
4823
            first_refresh_token: "FIRST_REFRESH_TOKEN".to_string(),
1✔
4824
            second_access_token: "SECOND_ACCESS_TOKEN".to_string(),
1✔
4825
            second_refresh_token: "SECOND_REFRESH_TOKEN".to_string(),
1✔
4826
            client_side_password: Some("password123".to_string()),
1✔
4827
        };
1✔
4828

1✔
4829
        let (server, oidc_manager) = mock_refresh_server(mock_refresh_server_config);
1✔
4830

1✔
4831
        (server, move || {
1✔
4832
            OidcManager::from_oidc_with_static_tokens(oidc_manager.clone())
1✔
4833
        })
1✔
4834
    }
1✔
4835

4836
    #[ge_context::test(oidc_db = "oidc_only_refresh_with_encryption")]
3✔
4837
    async fn it_handles_oidc_tokens_with_encryption(app_ctx: ProPostgresContext<NoTls>) {
1✔
4838
        it_handles_oidc_tokens(app_ctx).await;
40✔
4839
    }
1✔
4840
}
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