use sqlx::migrate::{MigrateDatabase, Migrator}; use sqlx::{sqlite::SqlitePoolOptions, Sqlite, SqlitePool}; use std::collections::HashSet; use std::path::Path; async fn setup_db() -> SqlitePool { let url = "sqlite::memory:"; Sqlite::create_database(url).await.ok(); let pool = SqlitePoolOptions::new() .max_connections(20) .connect(url) .await .unwrap(); Migrator::new(Path::new("./migrations")) .await .unwrap() .run(&pool) .await .unwrap(); sqlx::query( "INSERT INTO pools (slug, name, cidr, kind, allowed_prefixes_json, enabled, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)" ) .bind("vlan") .bind("VLAN / zákaznické subnety") .bind("10.91.0.0/16") .bind("VLAN") .bind("[20,21,22,23,24,25,26,27,28,29,30]") .bind(1) .bind(chrono::Utc::now().to_rfc3339()) .execute(&pool) .await .unwrap(); pool } #[tokio::test] async fn concurrent_allocations_do_not_overlap() { let pool = setup_db().await; let mut handles = vec![]; for i in 0..5 { let p = pool.clone(); handles.push(tokio::spawn(async move { sub_aligator::handlers::allocations::allocate_in_tx( &p, 1, "10.91.0.0/16", &[24], 24, &format!("test allocation {}", i), "", "local", ) .await })); } let results: Vec<_> = futures::future::join_all(handles).await; let mut cidrs = HashSet::new(); for r in results { let Ok(Ok((allocation, _))) = r else { panic!("task failed") }; assert!( cidrs.insert(allocation.cidr.clone()), "duplicate CIDR allocated: {}", allocation.cidr ); } assert_eq!(cidrs.len(), 5); }