#![deny(clippy::disallowed_methods, clippy::disallowed_types)]
use std::time::Duration;
use dbos::sysdb::SystemDatabase;
use dbos::sysdb::postgres::{PostgresSystemDatabase, Settings};
use dbos::{Config, DBOS};
use dbos_test_support::{TestDatabase, test_database};
const APP_VERSION: &str = "1.0.0";
fn config(app_name: &str, db: &TestDatabase) -> Config {
Config {
migrate: false,
app_version: Some(APP_VERSION.to_owned()),
..Config::new(app_name, db.url())
}
}
async fn reader(db: &TestDatabase) -> PostgresSystemDatabase {
PostgresSystemDatabase::from_pool(db.pool().await, &Settings::default())
}
#[dbos_test_support::test]
async fn a_sleep_waits_and_is_checkpointed() {
let db = test_database().await;
let dbos = DBOS::new(config("sleep-app", &db));
let workflow = dbos
.register_workflow("naps", |()| async move {
dbos::sleep(Duration::from_millis(150)).await?;
Ok::<u32, dbos::Error>(1)
})
.unwrap();
dbos.launch().await.expect("launch failed");
let before = std::time::Instant::now();
workflow.run(()).await.expect("the workflow failed");
assert!(
before.elapsed() >= Duration::from_millis(140),
"the sleep did not wait: {:?}",
before.elapsed()
);
let reader = reader(&db).await;
let rows = reader
.list_workflows(&Default::default(), None)
.await
.expect("read failed");
let steps = reader
.list_workflow_steps(&rows[0].workflow_id, true, None, None, None)
.await
.expect("read failed");
assert_eq!(steps.len(), 1, "the sleep is a step");
assert_eq!(steps[0].step_name, "DBOS.sleep");
dbos.shutdown().await;
}
#[dbos_test_support::test]
async fn a_replayed_sleep_does_not_start_its_clock_again() {
let db = test_database().await;
let dbos = DBOS::new(config("replay-sleep-app", &db));
let workflow = dbos
.register_workflow("naps", |()| async move {
dbos::sleep(Duration::from_secs(3)).await?;
Ok::<u32, dbos::Error>(1)
})
.unwrap();
dbos.launch().await.expect("launch failed");
let id = "sleeps-once";
let options = dbos::RunOptions {
workflow_id: Some(id),
..Default::default()
};
workflow
.run_with((), options.clone())
.await
.expect("the workflow failed");
let before = std::time::Instant::now();
workflow
.run_with((), options)
.await
.expect("the replay failed");
assert!(
before.elapsed() < Duration::from_secs(1),
"the replay restarted the clock: {:?}",
before.elapsed()
);
dbos.shutdown().await;
}
#[dbos_test_support::test]
async fn a_sleep_outside_a_workflow_waits_plainly() {
let before = std::time::Instant::now();
dbos::sleep::<dbos::Error>(Duration::from_millis(50))
.await
.expect("the sleep failed");
assert!(before.elapsed() >= Duration::from_millis(45));
}
#[dbos_test_support::test]
async fn a_sleep_that_cannot_be_recorded_is_refused_and_spends_no_step_id() {
let db = test_database().await;
let dbos = DBOS::new(config("refused-sleep-app", &db));
let workflow = dbos
.register_workflow("oversleeps", |()| async move {
let refused = dbos::sleep::<dbos::Error>(Duration::MAX).await;
assert!(
matches!(refused, Err(dbos::Error::InvalidArgument { .. })),
"a sleep that cannot be recorded should be refused, got {refused:?}"
);
dbos::step("after", || async { Ok(()) }).await?;
Ok::<u32, dbos::Error>(1)
})
.unwrap();
dbos.launch().await.expect("launch failed");
let id = "the-refused-sleeper";
workflow
.run_with(
(),
dbos::RunOptions {
workflow_id: Some(id),
..Default::default()
},
)
.await
.expect("the workflow failed");
let steps = reader(&db)
.await
.list_workflow_steps(id, true, None, None, None)
.await
.expect("read failed");
let recorded: Vec<(i32, &str)> = steps
.iter()
.map(|s| (s.step_id, s.step_name.as_str()))
.collect();
assert_eq!(
recorded,
[(0, "after")],
"a refused sleep took the step id the step after it should have had",
);
dbos.shutdown().await;
}