1use std::collections::{BTreeMap, HashMap};
21
22use chrono::{DateTime, TimeZone, Utc};
23use kellnr_common::index_metadata::IndexMetadata;
24use kellnr_common::original_name::OriginalName;
25use kellnr_common::prefetch::Prefetch;
26use kellnr_common::publish_metadata::PublishMetadata;
27use kellnr_common::version::Version;
28use kellnr_entity::{
29 auth_token, crate_dependency, crate_index, crate_meta, cratesio_crate, krate, oauth2_state,
30 session, user,
31};
32use sea_orm::sea_query::Expr;
33use sea_orm::{
34 ActiveModelTrait, ActiveValue, ColumnTrait, EntityTrait, ExprTrait, QueryFilter, Set,
35};
36
37use super::{DB_DATE_FORMAT, Database, parse_db_version};
38use crate::CrateMeta;
39use crate::error::DbError;
40use crate::provider::{DbProvider, DbResult};
41
42#[must_use]
47pub fn default_created() -> DateTime<Utc> {
48 Utc.with_ymd_and_hms(2020, 10, 7, 13, 18, 0).unwrap()
49}
50
51pub async fn add_multiple_versions(
75 db: &Database,
76 name: &str,
77 owner: &str,
78 versions: &[&str],
79) -> DbResult<Vec<i64>> {
80 let created = default_created();
81 let mut ids = Vec::with_capacity(versions.len());
82 for version in versions {
83 let id = test_add_crate(db, name, owner, &parse_db_version(version)?, &created).await?;
84 ids.push(id);
85 }
86 Ok(ids)
87}
88
89pub struct TestCrateBuilder<'a> {
113 db: &'a Database,
114 name: Option<&'a str>,
115 owner: &'a str,
116 version: &'a str,
117 created: Option<DateTime<Utc>>,
118 downloads: Option<i64>,
119}
120
121impl<'a> TestCrateBuilder<'a> {
122 #[must_use]
130 pub fn new(db: &'a Database) -> Self {
131 Self {
132 db,
133 name: None,
134 owner: "admin",
135 version: "1.0.0",
136 created: None,
137 downloads: None,
138 }
139 }
140
141 #[must_use]
143 pub fn name(mut self, name: &'a str) -> Self {
144 self.name = Some(name);
145 self
146 }
147
148 #[must_use]
150 pub fn owner(mut self, owner: &'a str) -> Self {
151 self.owner = owner;
152 self
153 }
154
155 #[must_use]
157 pub fn version(mut self, version: &'a str) -> Self {
158 self.version = version;
159 self
160 }
161
162 #[must_use]
164 pub fn created(mut self, created: DateTime<Utc>) -> Self {
165 self.created = Some(created);
166 self
167 }
168
169 #[must_use]
171 pub fn downloads(mut self, downloads: i64) -> Self {
172 self.downloads = Some(downloads);
173 self
174 }
175
176 pub async fn build(self) -> DbResult<i64> {
189 let name = self.name.ok_or_else(|| {
190 DbError::InvalidCrateName("TestCrateBuilder: name is required".to_string())
191 })?;
192 let created = self.created.unwrap_or_else(default_created);
193 let version = Version::try_from(self.version)
194 .map_err(|_| DbError::InvalidVersion(self.version.to_string()))?;
195
196 if let Some(downloads) = self.downloads {
197 test_add_crate_with_downloads(
198 self.db,
199 name,
200 self.owner,
201 &version,
202 None,
203 &created,
204 Some(downloads),
205 )
206 .await
207 } else {
208 test_add_crate(self.db, name, self.owner, &version, &created).await
209 }
210 }
211}
212
213pub async fn test_add_cached_crate_with_downloads(
215 db: &Database,
216 name: &str,
217 version: &str,
218 downloads: u64,
219) -> DbResult<()> {
220 let _ = test_add_cached_crate(db, name, version).await?;
221
222 let krate = cratesio_crate::Entity::find()
223 .filter(cratesio_crate::Column::Name.eq(name))
224 .one(&db.db_con)
225 .await?
226 .ok_or_else(|| DbError::CrateNotFound(name.to_string()))?;
227
228 let total_downloads = krate.total_downloads as u64;
229
230 let mut krate: cratesio_crate::ActiveModel = krate.into();
231 krate.total_downloads = Set((total_downloads + downloads) as i64);
232 krate.update(&db.db_con).await?;
233
234 Ok(())
235}
236
237pub async fn test_add_cached_crate(db: &Database, name: &str, version: &str) -> DbResult<Prefetch> {
239 let etag = "etag";
240 let last_modified = "last_modified";
241 let description = Some(String::from("description"));
242 let indices = vec![IndexMetadata {
243 name: name.to_string(),
244 vers: version.to_string(),
245 deps: vec![],
246 cksum: "cksum".to_string(),
247 features: BTreeMap::new(),
248 features2: None,
249 pubtime: None,
250 yanked: false,
251 links: None,
252 v: Some(1),
253 rust_version: None,
254 }];
255
256 db.add_cratesio_prefetch_data(
257 &OriginalName::from_unchecked(name.to_string()),
258 etag,
259 last_modified,
260 description,
261 &indices,
262 &HashMap::new(),
263 )
264 .await
265}
266
267pub async fn test_add_crate(
269 db: &Database,
270 name: &str,
271 owner: &str,
272 version: &Version,
273 created: &DateTime<Utc>,
274) -> DbResult<i64> {
275 let pm = PublishMetadata {
276 name: name.to_string(),
277 vers: version.to_string(),
278 ..PublishMetadata::default()
279 };
280 let user = user::Entity::find()
281 .filter(user::Column::Name.eq(owner))
282 .one(&db.db_con)
283 .await?;
284 if user.is_none() {
285 db.add_user(name, "pwd", false, false).await?;
286 }
287
288 db.add_crate(&pm, "cksum", 1024, created, owner, None).await
289}
290
291pub async fn test_insert_crate_unchecked(db: &Database, name: &str) -> DbResult<i64> {
294 let krate = krate::ActiveModel {
295 id: ActiveValue::default(),
296 name: Set(name.to_lowercase()),
297 original_name: Set(name.to_string()),
298 max_version: Set("0.0.0".to_string()),
299 last_updated: Set(default_created().format(DB_DATE_FORMAT).to_string()),
300 total_downloads: Set(0),
301 homepage: Set(None),
302 description: Set(None),
303 repository: Set(None),
304 e_tag: Set(String::new()),
305 restricted_download: Set(false),
306 };
307 Ok(krate.insert(&db.db_con).await?.id)
308}
309
310pub async fn test_add_crate_with_downloads(
312 db: &Database,
313 name: &str,
314 owner: &str,
315 version: &Version,
316 description: Option<&str>,
317 created: &DateTime<Utc>,
318 downloads: Option<i64>,
319) -> DbResult<i64> {
320 let pm = PublishMetadata {
321 name: name.to_string(),
322 vers: version.to_string(),
323 description: description.map(ToString::to_string),
324 ..PublishMetadata::default()
325 };
326 let user = user::Entity::find()
327 .filter(user::Column::Name.eq(owner))
328 .one(&db.db_con)
329 .await?;
330 if user.is_none() {
331 db.add_user(name, "pwd", false, false).await?;
332 }
333
334 db.add_crate(&pm, "cksum", 1024, created, owner, None)
335 .await?;
336 let (cm, krate) = crate_meta::Entity::find()
337 .find_also_related(krate::Entity)
338 .filter(krate::Column::Name.eq(name))
339 .filter(crate_meta::Column::Version.eq(version))
340 .one(&db.db_con)
341 .await?
342 .ok_or_else(|| DbError::CrateNotFound(name.to_string()))?;
343 let mut cm: crate_meta::ActiveModel = cm.into();
344
345 let current_downloads = krate.as_ref().unwrap().total_downloads;
346 let crate_id = krate.as_ref().unwrap().id;
347
348 let mut krate: krate::ActiveModel = krate.unwrap().into();
349 krate.total_downloads = Set(current_downloads + downloads.unwrap_or(0));
350 krate.update(&db.db_con).await?;
351 cm.downloads = Set(downloads.unwrap_or_default());
352 cm.update(&db.db_con).await?;
353 Ok(crate_id)
354}
355
356pub async fn test_add_crate_meta(
358 db: &Database,
359 crate_id: i64,
360 version: &str,
361 created: &DateTime<Utc>,
362 downloads: Option<i64>,
363) -> DbResult<()> {
364 let cm = crate_meta::ActiveModel {
365 id: ActiveValue::default(),
366 version: Set(version.to_string()),
367 created: Set(created.to_string()),
368 downloads: Set(downloads.unwrap_or_default()),
369 crate_fk: Set(crate_id),
370 ..Default::default()
371 };
372
373 cm.insert(&db.db_con).await?;
374
375 Ok(())
376}
377
378pub async fn test_delete_crate_index(db: &Database, crate_id: i64) -> DbResult<()> {
380 crate_index::Entity::delete_many()
381 .filter(crate_index::Column::CrateFk.eq(crate_id))
382 .exec(&db.db_con)
383 .await?;
384 Ok(())
385}
386
387pub async fn test_refill_crate_dependencies(db: &Database) -> DbResult<()> {
390 crate_dependency::Entity::delete_many()
391 .exec(&db.db_con)
392 .await?;
393 kellnr_migration::fill_crate_dependencies(&db.db_con).await?;
394 Ok(())
395}
396
397pub async fn test_set_index_deps(
400 db: &Database,
401 crate_name: &str,
402 deps: serde_json::Value,
403) -> DbResult<()> {
404 crate_index::Entity::update_many()
405 .col_expr(crate_index::Column::Deps, Expr::value(deps))
406 .filter(crate_index::Column::Name.eq(crate_name))
407 .exec(&db.db_con)
408 .await?;
409 Ok(())
410}
411
412pub async fn clean_db(db: &Database, session_age: std::time::Duration) -> DbResult<()> {
414 let session_age = chrono::Duration::from_std(session_age).unwrap();
415 let now = std::ops::Add::add(Utc::now(), session_age)
416 .format(DB_DATE_FORMAT)
417 .to_string();
418
419 session::Entity::delete_many()
420 .filter(Expr::col(session::Column::Created).lt(now))
421 .exec(&db.db_con)
422 .await?;
423
424 Ok(())
425}
426
427pub async fn add_aged_session(
433 db: &Database,
434 user_name: &str,
435 token: &str,
436 age: std::time::Duration,
437) -> DbResult<()> {
438 let user = user::Entity::find()
439 .filter(user::Column::Name.eq(user_name))
440 .one(&db.db_con)
441 .await?
442 .ok_or_else(|| DbError::UserNotFound(user_name.to_owned()))?;
443
444 let age = chrono::Duration::from_std(age).expect("session age out of range");
445 let created = (Utc::now() - age).format(DB_DATE_FORMAT).to_string();
446 let s = session::ActiveModel {
447 token: Set(crate::password::hash_token(token)),
448 created: Set(created),
449 user_fk: Set(user.id),
450 ..Default::default()
451 };
452 s.insert(&db.db_con).await?;
453 Ok(())
454}
455
456pub async fn get_crate_meta_list(db: &Database, crate_id: i64) -> DbResult<Vec<CrateMeta>> {
458 let cm: Vec<(crate_meta::Model, Option<krate::Model>)> = crate_meta::Entity::find()
459 .find_also_related(krate::Entity)
460 .filter(crate_meta::Column::CrateFk.eq(crate_id))
461 .all(&db.db_con)
462 .await?;
463
464 let crate_metas: Vec<CrateMeta> = cm
465 .into_iter()
466 .map(|(m, c)| CrateMeta {
467 name: c.unwrap().name, id: m.id,
469 version: m.version,
470 created: m.created,
471 downloads: m.downloads,
472 crate_fk: m.crate_fk,
473 })
474 .collect();
475
476 Ok(crate_metas)
477}
478
479pub async fn set_legacy_pwd(db: &Database, user_name: &str, pwd: &str, salt: &str) -> DbResult<()> {
482 let mut u: user::ActiveModel = db.get_user_model(user_name).await?.into();
483 u.pwd = Set(crate::password::legacy_hash(pwd, salt));
484 u.salt = Set(salt.to_owned());
485 u.update(&db.db_con).await?;
486 Ok(())
487}
488
489pub async fn set_auth_token_last_used(db: &Database, name: &str, last_used: &str) -> DbResult<()> {
491 auth_token::Entity::update_many()
492 .col_expr(auth_token::Column::LastUsed, Expr::value(last_used))
493 .filter(auth_token::Column::Name.eq(name))
494 .exec(&db.db_con)
495 .await?;
496 Ok(())
497}
498
499pub async fn stored_session_tokens(db: &Database) -> DbResult<Vec<String>> {
501 Ok(session::Entity::find()
502 .all(&db.db_con)
503 .await?
504 .into_iter()
505 .map(|s| s.token)
506 .collect())
507}
508
509pub async fn add_aged_oauth2_state(
511 db: &Database,
512 state: &str,
513 age: std::time::Duration,
514) -> DbResult<()> {
515 let age = chrono::Duration::from_std(age).expect("state age out of range");
516 let s = oauth2_state::ActiveModel {
517 state: Set(state.to_owned()),
518 pkce_verifier: Set("verifier".to_owned()),
519 nonce: Set("nonce".to_owned()),
520 created: Set((Utc::now() - age).format(DB_DATE_FORMAT).to_string()),
521 ..Default::default()
522 };
523 s.insert(&db.db_con).await?;
524 Ok(())
525}