Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions src/bin/crates-admin/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,19 @@

let mut conn = AsyncConnectionWrapper::<AsyncPgConnection>::from(conn);

spawn_blocking(move || {
info!("Migrating the database");
info!("Migrating the database");
let mut conn = spawn_blocking(move || {

Check warning on line 49 in src/bin/crates-admin/migrate.rs

View check run for this annotation

Codecov / codecov/patch

src/bin/crates-admin/migrate.rs#L48-L49

Added lines #L48 - L49 were not covered by tests
HarnessWithOutput::write_to_stdout(&mut conn)
.run_pending_migrations(MIGRATIONS)
.map_err(|e| anyhow!(e))
.context("Failed to run migrations")?;

info!("Synchronizing crate categories");
crates_io::boot::categories::sync_with_connection(CATEGORIES_TOML, &mut conn)?;

Ok(())
Ok::<_, Error>(conn)

Check warning on line 55 in src/bin/crates-admin/migrate.rs

View check run for this annotation

Codecov / codecov/patch

src/bin/crates-admin/migrate.rs#L55

Added line #L55 was not covered by tests
})
.await
.await?;

Check warning on line 57 in src/bin/crates-admin/migrate.rs

View check run for this annotation

Codecov / codecov/patch

src/bin/crates-admin/migrate.rs#L57

Added line #L57 was not covered by tests

info!("Synchronizing crate categories");
crates_io::boot::categories::sync_with_connection(CATEGORIES_TOML, &mut conn).await?;

Check warning on line 60 in src/bin/crates-admin/migrate.rs

View check run for this annotation

Codecov / codecov/patch

src/bin/crates-admin/migrate.rs#L59-L60

Added lines #L59 - L60 were not covered by tests

Ok(())

Check warning on line 62 in src/bin/crates-admin/migrate.rs

View check run for this annotation

Codecov / codecov/patch

src/bin/crates-admin/migrate.rs#L62

Added line #L62 was not covered by tests
}
50 changes: 28 additions & 22 deletions src/boot/categories.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
// Sync available crate categories from `src/categories.toml`.
// Runs when the server is started.

use crate::util::diesel::prelude::*;
use crate::util::diesel::Conn;
use anyhow::{Context, Result};
use crates_io_database::schema::categories;
use diesel::pg::upsert::excluded;
use diesel::prelude::*;
use diesel_async::scoped_futures::ScopedFutureExt;
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};

#[derive(Debug)]
struct Category {
Expand Down Expand Up @@ -76,11 +79,7 @@ fn categories_from_toml(
Ok(result)
}

pub fn sync_with_connection(toml_str: &str, conn: &mut impl Conn) -> Result<()> {
use crate::schema::categories;
use diesel::pg::upsert::excluded;
use diesel::RunQueryDsl;

pub async fn sync_with_connection(toml_str: &str, conn: &mut AsyncPgConnection) -> Result<()> {
let toml: toml::value::Table =
toml::from_str(toml_str).context("Could not parse categories toml")?;

Expand All @@ -97,20 +96,27 @@ pub fn sync_with_connection(toml_str: &str, conn: &mut impl Conn) -> Result<()>
.collect::<Vec<_>>();

conn.transaction(|conn| {
let slugs: Vec<String> = diesel::insert_into(categories::table)
.values(&to_insert)
.on_conflict(categories::slug)
.do_update()
.set((
categories::category.eq(excluded(categories::category)),
categories::description.eq(excluded(categories::description)),
))
.returning(categories::slug)
.get_results(conn)?;

diesel::delete(categories::table)
.filter(categories::slug.ne_all(slugs))
.execute(conn)?;
Ok(())
async move {
let slugs: Vec<String> = diesel::insert_into(categories::table)
.values(&to_insert)
.on_conflict(categories::slug)
.do_update()
.set((
categories::category.eq(excluded(categories::category)),
categories::description.eq(excluded(categories::description)),
))
.returning(categories::slug)
.get_results(conn)
.await?;

diesel::delete(categories::table)
.filter(categories::slug.ne_all(slugs))
.execute(conn)
.await?;

Ok(())
}
.scope_boxed()
})
.await
}
58 changes: 34 additions & 24 deletions src/tests/categories.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::schema::categories;
use crates_io_test_db::TestDatabase;
use diesel::*;
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};

const ALGORITHMS: &str = r#"
[algorithms]
Expand Down Expand Up @@ -37,56 +38,65 @@ name = "Another"
description = "Another category ho hum"
"#;

fn select_slugs(conn: &mut PgConnection) -> Vec<String> {
async fn select_slugs(conn: &mut AsyncPgConnection) -> Vec<String> {
categories::table
.select(categories::slug)
.order(categories::slug)
.load(conn)
.await
.unwrap()
}

#[test]
fn sync_adds_new_categories() {
#[tokio::test]
async fn sync_adds_new_categories() {
let test_db = TestDatabase::new();
let mut conn = test_db.connect();
let mut conn = AsyncPgConnection::establish(test_db.url()).await.unwrap();

crate::boot::categories::sync_with_connection(ALGORITHMS_AND_SUCH, &mut conn).unwrap();
crate::boot::categories::sync_with_connection(ALGORITHMS_AND_SUCH, &mut conn)
.await
.unwrap();

let categories = select_slugs(&mut conn);
let categories = select_slugs(&mut conn).await;
assert_eq!(categories, vec!["algorithms", "algorithms::such"]);
}

#[test]
fn sync_removes_missing_categories() {
#[tokio::test]
async fn sync_removes_missing_categories() {
let test_db = TestDatabase::new();
let mut conn = test_db.connect();
let mut conn = AsyncPgConnection::establish(test_db.url()).await.unwrap();

crate::boot::categories::sync_with_connection(ALGORITHMS_AND_SUCH, &mut conn).unwrap();
crate::boot::categories::sync_with_connection(ALGORITHMS, &mut conn).unwrap();
crate::boot::categories::sync_with_connection(ALGORITHMS_AND_SUCH, &mut conn)
.await
.unwrap();
crate::boot::categories::sync_with_connection(ALGORITHMS, &mut conn)
.await
.unwrap();

let categories = select_slugs(&mut conn);
let categories = select_slugs(&mut conn).await;
assert_eq!(categories, vec!["algorithms"]);
}

#[test]
fn sync_adds_and_removes() {
#[tokio::test]
async fn sync_adds_and_removes() {
let test_db = TestDatabase::new();
let mut conn = test_db.connect();
let mut conn = AsyncPgConnection::establish(test_db.url()).await.unwrap();

crate::boot::categories::sync_with_connection(ALGORITHMS_AND_SUCH, &mut conn).unwrap();
crate::boot::categories::sync_with_connection(ALGORITHMS_AND_ANOTHER, &mut conn).unwrap();
crate::boot::categories::sync_with_connection(ALGORITHMS_AND_SUCH, &mut conn)
.await
.unwrap();
crate::boot::categories::sync_with_connection(ALGORITHMS_AND_ANOTHER, &mut conn)
.await
.unwrap();

let categories = select_slugs(&mut conn);
let categories = select_slugs(&mut conn).await;
assert_eq!(categories, vec!["algorithms", "another"]);
}

#[test]
fn test_real_categories() {
#[tokio::test]
async fn test_real_categories() {
let test_db = TestDatabase::new();
let mut conn = test_db.connect();
let mut conn = AsyncPgConnection::establish(test_db.url()).await.unwrap();

const TOML: &str = include_str!("../boot/categories.toml");
assert_ok!(crate::boot::categories::sync_with_connection(
TOML, &mut conn
));
assert_ok!(crate::boot::categories::sync_with_connection(TOML, &mut conn).await);
}