aven-core 0.1.36

Core library for the Aven local-first task manager
Documentation
use sqlx::{Connection, SqliteConnection};

#[tokio::test]
async fn ios_source_migration_preserves_existing_sources_and_schema_objects() {
    assert_source_migration(20260913060740, &["cli", "tui", "api", "unknown"]).await;
}

#[tokio::test]
async fn android_source_migration_preserves_existing_sources_and_schema_objects() {
    assert_source_migration(20260913105309, &["cli", "tui", "api", "ios", "unknown"]).await;
}

async fn assert_source_migration(version: i64, old_sources: &[&str]) {
    let mut connection = SqliteConnection::connect("sqlite::memory:").await.unwrap();
    let migrator = sqlx::migrate!("./migrations");
    let migration = migrator
        .iter()
        .find(|migration| migration.version == version)
        .unwrap();
    for previous in migrator
        .iter()
        .filter(|previous| previous.version < migration.version)
    {
        sqlx::raw_sql(previous.sql.clone())
            .execute(&mut connection)
            .await
            .unwrap();
    }
    for (index, source) in old_sources.iter().enumerate() {
        sqlx::query(
            "INSERT INTO tasks(id, title, description, project_id, status, priority,
             created_at, updated_at, source)
             VALUES (?, 'existing task', '', '0000000000000000', 'inbox', 'none',
             '2026-09-01T00:00:00Z', '2026-09-01T00:00:00Z', ?)",
        )
        .bind(format!("{index:016}"))
        .bind(source)
        .execute(&mut connection)
        .await
        .unwrap();
    }
    let objects_sql = "SELECT name, sql FROM sqlite_schema
                       WHERE type IN ('index', 'trigger') ORDER BY name";
    let before: Vec<(String, Option<String>)> = sqlx::query_as(objects_sql)
        .fetch_all(&mut connection)
        .await
        .unwrap();
    sqlx::raw_sql(migration.sql.clone())
        .execute(&mut connection)
        .await
        .unwrap();
    let after: Vec<(String, Option<String>)> = sqlx::query_as(objects_sql)
        .fetch_all(&mut connection)
        .await
        .unwrap();
    assert_eq!(before, after);
    let sources: Vec<String> = sqlx::query_scalar("SELECT source FROM tasks ORDER BY id")
        .fetch_all(&mut connection)
        .await
        .unwrap();
    assert_eq!(sources, old_sources);
    sqlx::query("UPDATE tasks SET source = 'ios' WHERE source = 'unknown'")
        .execute(&mut connection)
        .await
        .unwrap();
    assert!(
        sqlx::query("UPDATE tasks SET source = 'Invalid'")
            .execute(&mut connection)
            .await
            .is_err()
    );
}

#[test]
fn source_is_not_a_mutable_field() {
    assert!(aven_core::task_fields::TaskField::parse("source").is_none());
}

#[tokio::test]
async fn source_parser_and_database_accept_the_same_fixed_values() {
    use aven_core::choices::{TASK_SOURCES, TaskSource};

    let mut connection = SqliteConnection::connect("sqlite::memory:").await.unwrap();
    sqlx::migrate!("./migrations")
        .run(&mut connection)
        .await
        .unwrap();
    let mut values: Vec<String> = TASK_SOURCES.iter().map(|value| value.to_string()).collect();
    values.extend([
        "future-client".to_string(),
        "agent".to_string(),
        String::new(),
        "Android".to_string(),
        "android ".to_string(),
        "android\0suffix".to_string(),
    ]);
    for value in values {
        let parsed = TaskSource::parse(&value);
        let expected_valid = TASK_SOURCES.contains(&value.as_str());
        assert_eq!(parsed.is_ok(), expected_valid, "source={value:?}");
        let stored = sqlx::query(
            "INSERT OR REPLACE INTO tasks(id, title, description, project_id, status,
             priority, created_at, updated_at, source)
             VALUES ('0000000000000001', 'source', '', '0000000000000000',
             'inbox', 'none', '2026-09-13T00:00:00Z', '2026-09-13T00:00:00Z', ?)",
        )
        .bind(&value)
        .execute(&mut connection)
        .await;
        assert_eq!(parsed.is_ok(), stored.is_ok(), "source={value:?}");
        if let Ok(parsed) = parsed {
            let persisted: String = sqlx::query_scalar("SELECT source FROM tasks")
                .fetch_one(&mut connection)
                .await
                .unwrap();
            assert_eq!(persisted, value);
            assert_eq!(parsed.as_str(), value);
            assert_eq!(parsed.to_string(), value);
        }
    }
}