Skip to content

Commit 2775289

Browse files
committed
tests: Move integration tests from inline modules to src/tests/
Move integration tests from `#[cfg(test)] mod tests` blocks in source files to consolidated test modules under `src/tests/`. This improves code organization and prepares for separating test compilation from library compilation. Moved tests: - Route handlers → `src/tests/routes/` - Worker jobs → `src/tests/worker/` All tests continue to compile as part of the library. Snapshots have been migrated to match new test locations.
1 parent 6eb2b14 commit 2775289

File tree

59 files changed

+712
-729
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+712
-729
lines changed

src/controllers/krate/delete.rs

Lines changed: 1 addition & 360 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use minijinja::context;
2222
use serde::Deserialize;
2323
use tracing::error;
2424

25-
const DOWNLOADS_PER_MONTH_LIMIT: u64 = 1000;
25+
pub const DOWNLOADS_PER_MONTH_LIMIT: u64 = 1000;
2626
const AVAILABLE_AFTER: TimeDelta = TimeDelta::hours(24);
2727

2828
#[derive(Debug, Deserialize, FromRequestParts, utoipa::IntoParams)]
@@ -198,362 +198,3 @@ async fn has_rev_dep(conn: &mut AsyncPgConnection, crate_id: i32) -> QueryResult
198198

199199
Ok(rev_dep.is_some())
200200
}
201-
202-
#[cfg(test)]
203-
mod tests {
204-
use super::*;
205-
use crate::models::OwnerKind;
206-
use crate::tests::builders::{DependencyBuilder, PublishBuilder};
207-
use crate::tests::util::{RequestHelper, Response, TestApp};
208-
use axum::RequestPartsExt;
209-
use claims::{assert_none, assert_some};
210-
use crates_io_database::schema::crate_owners;
211-
use diesel_async::AsyncPgConnection;
212-
use http::{Request, StatusCode};
213-
use insta::assert_snapshot;
214-
use serde_json::json;
215-
216-
#[tokio::test]
217-
async fn test_query_params() -> anyhow::Result<()> {
218-
let check = async |uri| {
219-
let request = Request::builder().uri(uri).body(())?;
220-
let (mut parts, _) = request.into_parts();
221-
Ok::<_, anyhow::Error>(parts.extract::<DeleteQueryParams>().await?)
222-
};
223-
224-
let params = check("/api/v1/crates/foo").await?;
225-
assert_none!(params.message);
226-
227-
let params = check("/api/v1/crates/foo?").await?;
228-
assert_none!(params.message);
229-
230-
let params = check("/api/v1/crates/foo?message=").await?;
231-
assert_eq!(assert_some!(params.message), "");
232-
233-
let params = check("/api/v1/crates/foo?message=hello%20world").await?;
234-
assert_eq!(assert_some!(params.message), "hello world");
235-
236-
Ok(())
237-
}
238-
239-
#[tokio::test(flavor = "multi_thread")]
240-
async fn test_happy_path_new_crate() -> anyhow::Result<()> {
241-
let (app, anon, user) = TestApp::full().with_user().await;
242-
let mut conn = app.db_conn().await;
243-
let upstream = app.upstream_index();
244-
245-
publish_crate(&user, "foo").await;
246-
let crate_id = adjust_creation_date(&mut conn, "foo", 71).await?;
247-
248-
// Update downloads count so that it wouldn't be deletable if it wasn't new
249-
adjust_downloads(&mut conn, crate_id, DOWNLOADS_PER_MONTH_LIMIT * 100).await?;
250-
251-
assert_crate_exists(&anon, "foo", true).await;
252-
assert!(upstream.crate_exists("foo")?);
253-
assert_snapshot!(app.stored_files().await.join("\n"), @r"
254-
crates/foo/foo-1.0.0.crate
255-
index/3/f/foo
256-
rss/crates.xml
257-
rss/crates/foo.xml
258-
rss/updates.xml
259-
");
260-
261-
let response = delete_crate(&user, "foo").await;
262-
assert_snapshot!(response.status(), @"204 No Content");
263-
assert!(response.body().is_empty());
264-
265-
assert_snapshot!(app.emails_snapshot().await);
266-
267-
// Assert that the crate no longer exists
268-
assert_crate_exists(&anon, "foo", false).await;
269-
assert!(!upstream.crate_exists("foo")?);
270-
assert_snapshot!(app.stored_files().await.join("\n"), @r"
271-
rss/crates.xml
272-
rss/updates.xml
273-
");
274-
275-
Ok(())
276-
}
277-
278-
#[tokio::test(flavor = "multi_thread")]
279-
async fn test_happy_path_old_crate() -> anyhow::Result<()> {
280-
let (app, anon, user) = TestApp::full().with_user().await;
281-
let mut conn = app.db_conn().await;
282-
let upstream = app.upstream_index();
283-
284-
publish_crate(&user, "foo").await;
285-
let crate_id = adjust_creation_date(&mut conn, "foo", 73).await?;
286-
adjust_downloads(&mut conn, crate_id, DOWNLOADS_PER_MONTH_LIMIT).await?;
287-
288-
assert_crate_exists(&anon, "foo", true).await;
289-
assert!(upstream.crate_exists("foo")?);
290-
assert_snapshot!(app.stored_files().await.join("\n"), @r"
291-
crates/foo/foo-1.0.0.crate
292-
index/3/f/foo
293-
rss/crates.xml
294-
rss/crates/foo.xml
295-
rss/updates.xml
296-
");
297-
298-
let response = delete_crate(&user, "foo").await;
299-
assert_snapshot!(response.status(), @"204 No Content");
300-
assert!(response.body().is_empty());
301-
302-
assert_snapshot!(app.emails_snapshot().await);
303-
304-
// Assert that the crate no longer exists
305-
assert_crate_exists(&anon, "foo", false).await;
306-
assert!(!upstream.crate_exists("foo")?);
307-
assert_snapshot!(app.stored_files().await.join("\n"), @r"
308-
rss/crates.xml
309-
rss/updates.xml
310-
");
311-
312-
Ok(())
313-
}
314-
315-
#[tokio::test(flavor = "multi_thread")]
316-
async fn test_happy_path_really_old_crate() -> anyhow::Result<()> {
317-
let (app, anon, user) = TestApp::full().with_user().await;
318-
let mut conn = app.db_conn().await;
319-
let upstream = app.upstream_index();
320-
321-
publish_crate(&user, "foo").await;
322-
let crate_id = adjust_creation_date(&mut conn, "foo", 1000 * 24).await?;
323-
adjust_downloads(&mut conn, crate_id, 30 * DOWNLOADS_PER_MONTH_LIMIT).await?;
324-
325-
assert_crate_exists(&anon, "foo", true).await;
326-
assert!(upstream.crate_exists("foo")?);
327-
assert_snapshot!(app.stored_files().await.join("\n"), @r"
328-
crates/foo/foo-1.0.0.crate
329-
index/3/f/foo
330-
rss/crates.xml
331-
rss/crates/foo.xml
332-
rss/updates.xml
333-
");
334-
335-
let response = delete_crate(&user, "foo").await;
336-
assert_snapshot!(response.status(), @"204 No Content");
337-
assert!(response.body().is_empty());
338-
339-
assert_snapshot!(app.emails_snapshot().await);
340-
341-
// Assert that the crate no longer exists
342-
assert_crate_exists(&anon, "foo", false).await;
343-
assert!(!upstream.crate_exists("foo")?);
344-
assert_snapshot!(app.stored_files().await.join("\n"), @r"
345-
rss/crates.xml
346-
rss/updates.xml
347-
");
348-
349-
Ok(())
350-
}
351-
352-
#[tokio::test(flavor = "multi_thread")]
353-
async fn test_no_auth() -> anyhow::Result<()> {
354-
let (_app, anon, user) = TestApp::full().with_user().await;
355-
356-
publish_crate(&user, "foo").await;
357-
358-
let response = delete_crate(&anon, "foo").await;
359-
assert_snapshot!(response.status(), @"403 Forbidden");
360-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"this action requires authentication"}]}"#);
361-
362-
assert_crate_exists(&anon, "foo", true).await;
363-
364-
Ok(())
365-
}
366-
367-
#[tokio::test(flavor = "multi_thread")]
368-
async fn test_token_auth() -> anyhow::Result<()> {
369-
let (_app, anon, user, token) = TestApp::full().with_token().await;
370-
371-
publish_crate(&user, "foo").await;
372-
373-
let response = delete_crate(&token, "foo").await;
374-
assert_snapshot!(response.status(), @"403 Forbidden");
375-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"this action can only be performed on the crates.io website"}]}"#);
376-
377-
assert_crate_exists(&anon, "foo", true).await;
378-
379-
Ok(())
380-
}
381-
382-
#[tokio::test(flavor = "multi_thread")]
383-
async fn test_missing_crate() -> anyhow::Result<()> {
384-
let (_app, _anon, user) = TestApp::full().with_user().await;
385-
386-
let response = delete_crate(&user, "foo").await;
387-
assert_snapshot!(response.status(), @"404 Not Found");
388-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"crate `foo` does not exist"}]}"#);
389-
390-
Ok(())
391-
}
392-
393-
#[tokio::test(flavor = "multi_thread")]
394-
async fn test_not_owner() -> anyhow::Result<()> {
395-
let (app, anon, user) = TestApp::full().with_user().await;
396-
let user2 = app.db_new_user("bar").await;
397-
398-
publish_crate(&user, "foo").await;
399-
400-
let response = delete_crate(&user2, "foo").await;
401-
assert_snapshot!(response.status(), @"403 Forbidden");
402-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"only owners have permission to delete crates"}]}"#);
403-
404-
assert_crate_exists(&anon, "foo", true).await;
405-
406-
Ok(())
407-
}
408-
409-
#[tokio::test(flavor = "multi_thread")]
410-
async fn test_team_owner() -> anyhow::Result<()> {
411-
let (app, anon) = TestApp::full().empty().await;
412-
let user = app.db_new_user("user-org-owner").await;
413-
let user2 = app.db_new_user("user-one-team").await;
414-
415-
publish_crate(&user, "foo").await;
416-
417-
// Add team owner
418-
let body = json!({ "owners": ["github:test-org:all"] }).to_string();
419-
let response = user.put::<()>("/api/v1/crates/foo/owners", body).await;
420-
assert_snapshot!(response.status(), @"200 OK");
421-
422-
let response = delete_crate(&user2, "foo").await;
423-
assert_snapshot!(response.status(), @"403 Forbidden");
424-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"team members don't have permission to delete crates"}]}"#);
425-
426-
assert_crate_exists(&anon, "foo", true).await;
427-
428-
Ok(())
429-
}
430-
431-
#[tokio::test(flavor = "multi_thread")]
432-
async fn test_too_many_owners() -> anyhow::Result<()> {
433-
let (app, anon, user) = TestApp::full().with_user().await;
434-
let mut conn = app.db_conn().await;
435-
let user2 = app.db_new_user("bar").await;
436-
437-
publish_crate(&user, "foo").await;
438-
let crate_id = adjust_creation_date(&mut conn, "foo", 73).await?;
439-
440-
// Add another owner
441-
diesel::insert_into(crate_owners::table)
442-
.values((
443-
crate_owners::crate_id.eq(crate_id),
444-
crate_owners::owner_id.eq(user2.as_model().id),
445-
crate_owners::owner_kind.eq(OwnerKind::User),
446-
))
447-
.execute(&mut conn)
448-
.await?;
449-
450-
let response = delete_crate(&user, "foo").await;
451-
assert_snapshot!(response.status(), @"422 Unprocessable Entity");
452-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"only crates with a single owner can be deleted after 72 hours"}]}"#);
453-
454-
assert_crate_exists(&anon, "foo", true).await;
455-
456-
Ok(())
457-
}
458-
459-
#[tokio::test(flavor = "multi_thread")]
460-
async fn test_too_many_downloads() -> anyhow::Result<()> {
461-
let (app, anon, user) = TestApp::full().with_user().await;
462-
let mut conn = app.db_conn().await;
463-
464-
publish_crate(&user, "foo").await;
465-
let crate_id = adjust_creation_date(&mut conn, "foo", 73).await?;
466-
adjust_downloads(&mut conn, crate_id, DOWNLOADS_PER_MONTH_LIMIT + 1).await?;
467-
468-
let response = delete_crate(&user, "foo").await;
469-
assert_snapshot!(response.status(), @"422 Unprocessable Entity");
470-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"only crates with less than 1000 downloads per month can be deleted after 72 hours"}]}"#);
471-
472-
assert_crate_exists(&anon, "foo", true).await;
473-
474-
Ok(())
475-
}
476-
477-
#[tokio::test(flavor = "multi_thread")]
478-
async fn test_rev_deps() -> anyhow::Result<()> {
479-
let (_app, anon, user) = TestApp::full().with_user().await;
480-
481-
publish_crate(&user, "foo").await;
482-
483-
// Publish another crate
484-
let pb = PublishBuilder::new("bar", "1.0.0").dependency(DependencyBuilder::new("foo"));
485-
let response = user.publish_crate(pb).await;
486-
assert_snapshot!(response.status(), @"200 OK");
487-
488-
let response = delete_crate(&user, "foo").await;
489-
assert_snapshot!(response.status(), @"422 Unprocessable Entity");
490-
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"only crates without reverse dependencies can be deleted"}]}"#);
491-
492-
assert_crate_exists(&anon, "foo", true).await;
493-
494-
Ok(())
495-
}
496-
497-
// Publishes a crate with the given name and a single `v1.0.0` version.
498-
async fn publish_crate(user: &impl RequestHelper, name: &str) {
499-
let pb = PublishBuilder::new(name, "1.0.0");
500-
let response = user.publish_crate(pb).await;
501-
assert_eq!(response.status(), StatusCode::OK);
502-
}
503-
504-
/// Moves the `created_at` field of a crate by the given number of hours
505-
/// into the past and returns the ID of the crate.
506-
async fn adjust_creation_date(
507-
conn: &mut AsyncPgConnection,
508-
name: &str,
509-
hours: i64,
510-
) -> QueryResult<i32> {
511-
let created_at = Utc::now() - TimeDelta::hours(hours);
512-
let created_at = created_at.naive_utc();
513-
514-
diesel::update(crates::table)
515-
.filter(crates::name.eq(name))
516-
.set(crates::created_at.eq(created_at))
517-
.returning(crates::id)
518-
.get_result(conn)
519-
.await
520-
}
521-
522-
// Updates the download count of a crate.
523-
async fn adjust_downloads(
524-
conn: &mut AsyncPgConnection,
525-
crate_id: i32,
526-
downloads: u64,
527-
) -> QueryResult<()> {
528-
let downloads = downloads.to_i64().unwrap_or(i64::MAX);
529-
530-
diesel::update(crate_downloads::table)
531-
.filter(crate_downloads::crate_id.eq(crate_id))
532-
.set(crate_downloads::downloads.eq(downloads))
533-
.execute(conn)
534-
.await?;
535-
536-
Ok(())
537-
}
538-
539-
// Performs the `DELETE` request to delete the crate, and runs any pending
540-
// background jobs, then returns the response.
541-
async fn delete_crate(user: &impl RequestHelper, name: &str) -> Response<()> {
542-
let url = format!("/api/v1/crates/{name}");
543-
let response = user.delete::<()>(&url).await;
544-
user.app().run_pending_background_jobs().await;
545-
response
546-
}
547-
548-
// Asserts that the crate with the given name exists or not.
549-
async fn assert_crate_exists(user: &impl RequestHelper, name: &str, exists: bool) {
550-
let expected_status = match exists {
551-
true => StatusCode::OK,
552-
false => StatusCode::NOT_FOUND,
553-
};
554-
555-
let url = format!("/api/v1/crates/{name}");
556-
let response = user.get::<()>(&url).await;
557-
assert_eq!(response.status(), expected_status);
558-
}
559-
}

src/controllers/trustpub/github_configs/create/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ use diesel_async::RunQueryDsl;
1919
use http::request::Parts;
2020
use tracing::warn;
2121

22-
#[cfg(test)]
23-
mod tests;
24-
2522
/// Create a new Trusted Publishing configuration for GitHub Actions.
2623
#[utoipa::path(
2724
post,

src/controllers/trustpub/github_configs/delete/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ use http::StatusCode;
1414
use http::request::Parts;
1515
use tracing::warn;
1616

17-
#[cfg(test)]
18-
mod tests;
19-
2017
/// Delete Trusted Publishing configuration for GitHub Actions.
2118
#[utoipa::path(
2219
delete,

0 commit comments

Comments
 (0)