// 独立 SQL 校验持久队列和业务结果,不能只用函数返回值或随机 ID 长度认定正确。 async fn clear(scope: &Scope<'_>, registry: &Arc) { let mut database = DbContext::new(registry.clone()); let executor = database.on(JOB_BINDING, scope).await.unwrap(); executor .execute(scope, "DELETE FROM _dever_job", vec![]) .await .unwrap(); executor .execute(scope, "DELETE FROM bench_queue_model", vec![]) .await .unwrap(); } async fn check_rows(scope: &Scope<'_>, registry: &Arc, inputs: &[JobInput], handled: bool) { let mut database = DbContext::new(registry.clone()); let executor = database.on(JOB_BINDING, scope).await.unwrap(); let rows = executor.query(scope, "SELECT id,target,payload,schema,execution,task_key,state,attempt,retries,timeout_ms,token,lease_until,available_at,created_at FROM _dever_job ORDER BY task_key", vec![], inputs.len() + 1, |row| Ok(( row.get::<_, String>(0)?, row.get::<_, String>(1)?, row.get::<_, String>(2)?, row.get::<_, String>(3)?, row.get::<_, String>(4)?, row.get::<_, String>(5)?, row.get::<_, String>(6)?, row.get::<_, i64>(7)?, row.get::<_, i64>(8)?, row.get::<_, i64>(9)?, row.get::<_, Option>(10)?, row.get::<_, Option>(11)?, row.get::<_, i64>(12)?, row.get::<_, i64>(13)?, ))).await.unwrap(); assert_eq!(rows.len(), inputs.len()); let mut unique = std::collections::BTreeSet::new(); for (row, input) in rows.iter().zip(inputs) { assert!(unique.insert(&row.0)); assert_eq!(row.0.len(), 32); assert_eq!(row.1, TARGET); let payload: RustPayload = dever_runtime::wire::decode(&row.2).unwrap(); assert_eq!(payload.name, input.name); assert_eq!(payload.amount, input.amount); assert_eq!(row.3, SCHEMA); assert!(matches!( dever_runtime::wire::decode::(&row.4).unwrap(), Execution::System { tenant: None } )); assert_eq!(row.5, input.key); assert_eq!(row.6, if handled { "completed" } else { "pending" }); assert_eq!((row.7, row.8, row.9), (i64::from(handled), 2, 1000)); assert!(row.10.is_none() && row.11.is_none()); assert!(row.12 >= row.13 && row.13 > 0); } let results = executor .query( scope, "SELECT name,amount FROM bench_queue_model ORDER BY name", vec![], inputs.len() + 1, |row| Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)), ) .await .unwrap(); let expected = if handled { inputs .iter() .map(|input| (input.name.clone(), input.amount * 3 + 7)) .collect::>() } else { vec![] }; assert_eq!(results, expected); } fn check_jobs(runtime: &Runtime, registry: &Arc) { runtime .run(async |scope| { for generated in [true, false] { clear(scope, registry).await; let inputs = inputs(2); for input in &inputs { let id = submit(generated, input, scope, registry).await; assert_eq!(submit(generated, input, scope, registry).await, id); } check_rows(scope, registry, &inputs, false).await; for claim in claims(scope, registry, 2).await { execute(generated, claim, scope, registry).await.unwrap(); } check_rows(scope, registry, &inputs, true).await; clear(scope, registry).await; let input = JobInput { name: "rollback".into(), amount: -4, key: "rollback".into(), }; submit(generated, &input, scope, registry).await; let claim = claims(scope, registry, 1).await.pop().unwrap(); assert!(matches!( execute(generated, claim, scope, registry).await, Err(JobFailure::Handler) )); let mut database = DbContext::new(registry.clone()); let executor = database.on(JOB_BINDING, scope).await.unwrap(); assert_eq!( executor .query( scope, "SELECT COUNT(*) FROM bench_queue_model", vec![], 1, |row| Ok(row.get::<_, i64>(0)?) ) .await .unwrap(), [0] ); assert_eq!( executor .query(scope, "SELECT state FROM _dever_job", vec![], 1, |row| Ok( row.get::<_, String>(0)? )) .await .unwrap(), ["running"] ); drop(database); clear(scope, registry).await; submit(generated, &inputs[0], scope, registry).await; let claim = claims(scope, registry, 1).await.pop().unwrap(); let mut claim = Arc::try_unwrap(claim).unwrap(); claim.payload = r#"["array-is-not-a-record",1]"#.into(); assert!(matches!( execute(generated, Arc::new(claim), scope, registry).await, Err(JobFailure::Blocked) )); } }) .unwrap(); }