1use std::collections::BTreeMap;
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::{crate_index, crate_meta, cratesio_crate, krate, session, user};
29use sea_orm::sea_query::Expr;
30use sea_orm::{
31 ActiveModelTrait, ActiveValue, ColumnTrait, EntityTrait, ExprTrait, QueryFilter, Set,
32};
33
34use super::{DB_DATE_FORMAT, Database, parse_db_version};
35use crate::CrateMeta;
36use crate::error::DbError;
37use crate::provider::{DbProvider, DbResult};
38
39#[must_use]
44pub fn default_created() -> DateTime<Utc> {
45 Utc.with_ymd_and_hms(2020, 10, 7, 13, 18, 0).unwrap()
46}
47
48pub async fn add_multiple_versions(
72 db: &Database,
73 name: &str,
74 owner: &str,
75 versions: &[&str],
76) -> DbResult<Vec<i64>> {
77 let created = default_created();
78 let mut ids = Vec::with_capacity(versions.len());
79 for version in versions {
80 let id = test_add_crate(db, name, owner, &parse_db_version(version)?, &created).await?;
81 ids.push(id);
82 }
83 Ok(ids)
84}
85
86pub struct TestCrateBuilder<'a> {
110 db: &'a Database,
111 name: Option<&'a str>,
112 owner: &'a str,
113 version: &'a str,
114 created: Option<DateTime<Utc>>,
115 downloads: Option<i64>,
116}
117
118impl<'a> TestCrateBuilder<'a> {
119 #[must_use]
127 pub fn new(db: &'a Database) -> Self {
128 Self {
129 db,
130 name: None,
131 owner: "admin",
132 version: "1.0.0",
133 created: None,
134 downloads: None,
135 }
136 }
137
138 #[must_use]
140 pub fn name(mut self, name: &'a str) -> Self {
141 self.name = Some(name);
142 self
143 }
144
145 #[must_use]
147 pub fn owner(mut self, owner: &'a str) -> Self {
148 self.owner = owner;
149 self
150 }
151
152 #[must_use]
154 pub fn version(mut self, version: &'a str) -> Self {
155 self.version = version;
156 self
157 }
158
159 #[must_use]
161 pub fn created(mut self, created: DateTime<Utc>) -> Self {
162 self.created = Some(created);
163 self
164 }
165
166 #[must_use]
168 pub fn downloads(mut self, downloads: i64) -> Self {
169 self.downloads = Some(downloads);
170 self
171 }
172
173 pub async fn build(self) -> DbResult<i64> {
186 let name = self.name.ok_or_else(|| {
187 DbError::InvalidCrateName("TestCrateBuilder: name is required".to_string())
188 })?;
189 let created = self.created.unwrap_or_else(default_created);
190 let version = Version::try_from(self.version)
191 .map_err(|_| DbError::InvalidVersion(self.version.to_string()))?;
192
193 if let Some(downloads) = self.downloads {
194 test_add_crate_with_downloads(
195 self.db,
196 name,
197 self.owner,
198 &version,
199 &created,
200 Some(downloads),
201 )
202 .await
203 } else {
204 test_add_crate(self.db, name, self.owner, &version, &created).await
205 }
206 }
207}
208
209pub async fn test_add_cached_crate_with_downloads(
211 db: &Database,
212 name: &str,
213 version: &str,
214 downloads: u64,
215) -> DbResult<()> {
216 let _ = test_add_cached_crate(db, name, version).await?;
217
218 let krate = cratesio_crate::Entity::find()
219 .filter(cratesio_crate::Column::Name.eq(name))
220 .one(&db.db_con)
221 .await?
222 .ok_or_else(|| DbError::CrateNotFound(name.to_string()))?;
223
224 let total_downloads = krate.total_downloads as u64;
225
226 let mut krate: cratesio_crate::ActiveModel = krate.into();
227 krate.total_downloads = Set((total_downloads + downloads) as i64);
228 krate.update(&db.db_con).await?;
229
230 Ok(())
231}
232
233pub async fn test_add_cached_crate(db: &Database, name: &str, version: &str) -> DbResult<Prefetch> {
235 let etag = "etag";
236 let last_modified = "last_modified";
237 let description = Some(String::from("description"));
238 let indices = vec![IndexMetadata {
239 name: name.to_string(),
240 vers: version.to_string(),
241 deps: vec![],
242 cksum: "cksum".to_string(),
243 features: BTreeMap::new(),
244 features2: None,
245 pubtime: None,
246 yanked: false,
247 links: None,
248 v: Some(1),
249 }];
250
251 db.add_cratesio_prefetch_data(
252 &OriginalName::from_unchecked(name.to_string()),
253 etag,
254 last_modified,
255 description,
256 &indices,
257 )
258 .await
259}
260
261pub async fn test_add_crate(
263 db: &Database,
264 name: &str,
265 owner: &str,
266 version: &Version,
267 created: &DateTime<Utc>,
268) -> DbResult<i64> {
269 let pm = PublishMetadata {
270 name: name.to_string(),
271 vers: version.to_string(),
272 ..PublishMetadata::default()
273 };
274 let user = user::Entity::find()
275 .filter(user::Column::Name.eq(owner))
276 .one(&db.db_con)
277 .await?;
278 if user.is_none() {
279 db.add_user(name, "pwd", "salt", false, false).await?;
280 }
281
282 db.add_crate(&pm, "cksum", created, owner).await
283}
284
285pub async fn test_add_crate_with_downloads(
287 db: &Database,
288 name: &str,
289 owner: &str,
290 version: &Version,
291 created: &DateTime<Utc>,
292 downloads: Option<i64>,
293) -> DbResult<i64> {
294 let pm = PublishMetadata {
295 name: name.to_string(),
296 vers: version.to_string(),
297 ..PublishMetadata::default()
298 };
299 let user = user::Entity::find()
300 .filter(user::Column::Name.eq(owner))
301 .one(&db.db_con)
302 .await?;
303 if user.is_none() {
304 db.add_user(name, "pwd", "salt", false, false).await?;
305 }
306
307 db.add_crate(&pm, "cksum", created, owner).await?;
308 let (cm, krate) = crate_meta::Entity::find()
309 .find_also_related(krate::Entity)
310 .filter(krate::Column::Name.eq(name))
311 .filter(crate_meta::Column::Version.eq(version))
312 .one(&db.db_con)
313 .await?
314 .ok_or_else(|| DbError::CrateNotFound(name.to_string()))?;
315 let mut cm: crate_meta::ActiveModel = cm.into();
316
317 let current_downloads = krate.as_ref().unwrap().total_downloads;
318 let crate_id = krate.as_ref().unwrap().id;
319
320 let mut krate: krate::ActiveModel = krate.unwrap().into();
321 krate.total_downloads = Set(current_downloads + downloads.unwrap_or(0));
322 krate.update(&db.db_con).await?;
323 cm.downloads = Set(downloads.unwrap_or_default());
324 cm.update(&db.db_con).await?;
325 Ok(crate_id)
326}
327
328pub async fn test_add_crate_meta(
330 db: &Database,
331 crate_id: i64,
332 version: &str,
333 created: &DateTime<Utc>,
334 downloads: Option<i64>,
335) -> DbResult<()> {
336 let cm = crate_meta::ActiveModel {
337 id: ActiveValue::default(),
338 version: Set(version.to_string()),
339 created: Set(created.to_string()),
340 downloads: Set(downloads.unwrap_or_default()),
341 crate_fk: Set(crate_id),
342 ..Default::default()
343 };
344
345 cm.insert(&db.db_con).await?;
346
347 Ok(())
348}
349
350pub async fn test_delete_crate_index(db: &Database, crate_id: i64) -> DbResult<()> {
352 crate_index::Entity::delete_many()
353 .filter(crate_index::Column::CrateFk.eq(crate_id))
354 .exec(&db.db_con)
355 .await?;
356 Ok(())
357}
358
359pub async fn clean_db(db: &Database, session_age: std::time::Duration) -> DbResult<()> {
361 let session_age = chrono::Duration::from_std(session_age).unwrap();
362 let now = std::ops::Add::add(Utc::now(), session_age)
363 .format(DB_DATE_FORMAT)
364 .to_string();
365
366 session::Entity::delete_many()
367 .filter(Expr::col(session::Column::Created).lt(now))
368 .exec(&db.db_con)
369 .await?;
370
371 Ok(())
372}
373
374pub async fn get_crate_meta_list(db: &Database, crate_id: i64) -> DbResult<Vec<CrateMeta>> {
376 let cm: Vec<(crate_meta::Model, Option<krate::Model>)> = crate_meta::Entity::find()
377 .find_also_related(krate::Entity)
378 .filter(crate_meta::Column::CrateFk.eq(crate_id))
379 .all(&db.db_con)
380 .await?;
381
382 let crate_metas: Vec<CrateMeta> = cm
383 .into_iter()
384 .map(|(m, c)| CrateMeta {
385 name: c.unwrap().name, id: m.id,
387 version: m.version,
388 created: m.created,
389 downloads: m.downloads,
390 crate_fk: m.crate_fk,
391 })
392 .collect();
393
394 Ok(crate_metas)
395}