use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
transaction::Transaction,
};
mod common;
use common::*;
fn create_thread_for_update(
svm: &mut litesvm::LiteSVM,
authority: &Keypair,
payer: &Keypair,
id: &str,
trigger: Trigger,
) -> Pubkey {
let thread_id = ThreadId::Bytes(id.as_bytes().to_vec());
let (thread_pubkey, _) = thread_pda(&authority.pubkey(), id.as_bytes());
let ix = build_create_thread(
&authority.pubkey(),
&payer.pubkey(),
&thread_pubkey,
1_000_000,
thread_id,
trigger,
None,
None,
None,
);
let blockhash = svm.latest_blockhash();
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&payer.pubkey()),
&[payer, authority],
blockhash,
);
svm.send_transaction(tx).unwrap();
thread_pubkey
}
fn send_update(
svm: &mut litesvm::LiteSVM,
authority: &Keypair,
payer: &Keypair,
thread: &Pubkey,
params: ThreadUpdateParams,
) -> Result<(), litesvm::types::FailedTransactionMetadata> {
let ix = build_update_thread(&authority.pubkey(), thread, params);
let blockhash = svm.latest_blockhash();
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&payer.pubkey()),
&[payer, authority],
blockhash,
);
svm.send_transaction(tx).map(|_| ())
}
#[test]
fn test_thread_update_pause() {
let (mut svm, _admin, payer) = create_test_env();
let authority = Keypair::new();
svm.airdrop(&authority.pubkey(), DEFAULT_AIRDROP).unwrap();
let thread_pubkey = create_thread_for_update(
&mut svm,
&authority,
&payer,
"tu-pause",
Trigger::Immediate { jitter: 0 },
);
send_update(
&mut svm,
&authority,
&payer,
&thread_pubkey,
ThreadUpdateParams {
paused: Some(true),
..Default::default()
},
)
.unwrap();
let thread = deserialize_thread(&svm, &thread_pubkey);
assert!(thread.paused);
}
#[test]
fn test_thread_update_unpause() {
let (mut svm, _admin, payer) = create_test_env();
let authority = Keypair::new();
svm.airdrop(&authority.pubkey(), DEFAULT_AIRDROP).unwrap();
let thread_pubkey = create_thread_for_update(
&mut svm,
&authority,
&payer,
"tu-unpause",
Trigger::Immediate { jitter: 0 },
);
send_update(
&mut svm,
&authority,
&payer,
&thread_pubkey,
ThreadUpdateParams {
paused: Some(true),
..Default::default()
},
)
.unwrap();
assert!(deserialize_thread(&svm, &thread_pubkey).paused);
send_update(
&mut svm,
&authority,
&payer,
&thread_pubkey,
ThreadUpdateParams {
paused: Some(false),
..Default::default()
},
)
.unwrap();
assert!(!deserialize_thread(&svm, &thread_pubkey).paused);
}
#[test]
fn test_thread_update_trigger_immediate() {
let (mut svm, _admin, payer) = create_test_env();
let authority = Keypair::new();
svm.airdrop(&authority.pubkey(), DEFAULT_AIRDROP).unwrap();
let thread_pubkey = create_thread_for_update(
&mut svm,
&authority,
&payer,
"tu-imm",
Trigger::Interval {
seconds: 60,
skippable: false,
jitter: 0,
},
);
send_update(
&mut svm,
&authority,
&payer,
&thread_pubkey,
ThreadUpdateParams {
trigger: Some(Trigger::Immediate { jitter: 0 }),
..Default::default()
},
)
.unwrap();
let thread = deserialize_thread(&svm, &thread_pubkey);
match thread.trigger {
antegen_thread_program::state::Trigger::Immediate { .. } => {}
_ => panic!("Expected Immediate trigger"),
}
}
#[test]
fn test_thread_update_trigger_interval() {
let (mut svm, _admin, payer) = create_test_env();
let authority = Keypair::new();
svm.airdrop(&authority.pubkey(), DEFAULT_AIRDROP).unwrap();
let clock = get_clock(&svm);
let thread_pubkey = create_thread_for_update(
&mut svm,
&authority,
&payer,
"tu-int",
Trigger::Immediate { jitter: 0 },
);
send_update(
&mut svm,
&authority,
&payer,
&thread_pubkey,
ThreadUpdateParams {
trigger: Some(Trigger::Interval {
seconds: 120,
skippable: false,
jitter: 0,
}),
..Default::default()
},
)
.unwrap();
let thread = deserialize_thread(&svm, &thread_pubkey);
match thread.schedule {
antegen_thread_program::state::Schedule::Timed { next, .. } => {
assert!(next >= clock.unix_timestamp + 120);
}
_ => panic!("Expected Timed schedule"),
}
}
#[test]
fn test_thread_update_trigger_timestamp() {
let (mut svm, _admin, payer) = create_test_env();
let authority = Keypair::new();
svm.airdrop(&authority.pubkey(), DEFAULT_AIRDROP).unwrap();
let target_ts = 1800000000i64;
let thread_pubkey = create_thread_for_update(
&mut svm,
&authority,
&payer,
"tu-ts",
Trigger::Immediate { jitter: 0 },
);
send_update(
&mut svm,
&authority,
&payer,
&thread_pubkey,
ThreadUpdateParams {
trigger: Some(Trigger::Timestamp {
unix_ts: target_ts,
jitter: 0,
}),
..Default::default()
},
)
.unwrap();
let thread = deserialize_thread(&svm, &thread_pubkey);
match thread.schedule {
antegen_thread_program::state::Schedule::Timed { next, .. } => {
assert_eq!(next, target_ts);
}
_ => panic!("Expected Timed schedule"),
}
}
#[test]
fn test_thread_update_trigger_cron() {
let (mut svm, _admin, payer) = create_test_env();
let authority = Keypair::new();
svm.airdrop(&authority.pubkey(), DEFAULT_AIRDROP).unwrap();
let clock = get_clock(&svm);
let thread_pubkey = create_thread_for_update(
&mut svm,
&authority,
&payer,
"tu-cron",
Trigger::Immediate { jitter: 0 },
);
send_update(
&mut svm,
&authority,
&payer,
&thread_pubkey,
ThreadUpdateParams {
trigger: Some(Trigger::Cron {
schedule: "0 * * * * * *".to_string(),
skippable: false,
jitter: 0,
}),
..Default::default()
},
)
.unwrap();
let thread = deserialize_thread(&svm, &thread_pubkey);
match thread.schedule {
antegen_thread_program::state::Schedule::Timed { next, .. } => {
assert!(next > clock.unix_timestamp);
}
_ => panic!("Expected Timed schedule"),
}
}
#[test]
fn test_thread_update_trigger_slot() {
let (mut svm, _admin, payer) = create_test_env();
let authority = Keypair::new();
svm.airdrop(&authority.pubkey(), DEFAULT_AIRDROP).unwrap();
let thread_pubkey = create_thread_for_update(
&mut svm,
&authority,
&payer,
"tu-slot",
Trigger::Immediate { jitter: 0 },
);
send_update(
&mut svm,
&authority,
&payer,
&thread_pubkey,
ThreadUpdateParams {
trigger: Some(Trigger::Slot { slot: 1000 }),
..Default::default()
},
)
.unwrap();
let thread = deserialize_thread(&svm, &thread_pubkey);
match thread.schedule {
antegen_thread_program::state::Schedule::Block { next, .. } => {
assert_eq!(next, 1000);
}
_ => panic!("Expected Block schedule"),
}
}
#[test]
fn test_thread_update_authority_only() {
let (mut svm, _admin, payer) = create_test_env();
let authority = Keypair::new();
let bad_authority = Keypair::new();
svm.airdrop(&authority.pubkey(), DEFAULT_AIRDROP).unwrap();
svm.airdrop(&bad_authority.pubkey(), DEFAULT_AIRDROP)
.unwrap();
let thread_pubkey = create_thread_for_update(
&mut svm,
&authority,
&payer,
"tu-auth",
Trigger::Immediate { jitter: 0 },
);
let result = send_update(
&mut svm,
&bad_authority,
&payer,
&thread_pubkey,
ThreadUpdateParams {
paused: Some(true),
..Default::default()
},
);
assert!(result.is_err());
}
#[test]
fn test_thread_update_no_params() {
let (mut svm, _admin, payer) = create_test_env();
let authority = Keypair::new();
svm.airdrop(&authority.pubkey(), DEFAULT_AIRDROP).unwrap();
let thread_pubkey = create_thread_for_update(
&mut svm,
&authority,
&payer,
"tu-noop",
Trigger::Immediate { jitter: 0 },
);
let thread_before = deserialize_thread(&svm, &thread_pubkey);
send_update(
&mut svm,
&authority,
&payer,
&thread_pubkey,
ThreadUpdateParams::default(),
)
.unwrap();
let thread_after = deserialize_thread(&svm, &thread_pubkey);
assert_eq!(thread_before.paused, thread_after.paused);
}
#[test]
fn test_thread_update_both_params() {
let (mut svm, _admin, payer) = create_test_env();
let authority = Keypair::new();
svm.airdrop(&authority.pubkey(), DEFAULT_AIRDROP).unwrap();
let thread_pubkey = create_thread_for_update(
&mut svm,
&authority,
&payer,
"tu-both",
Trigger::Immediate { jitter: 0 },
);
send_update(
&mut svm,
&authority,
&payer,
&thread_pubkey,
ThreadUpdateParams {
paused: Some(true),
trigger: Some(Trigger::Interval {
seconds: 30,
skippable: false,
jitter: 0,
}),
},
)
.unwrap();
let thread = deserialize_thread(&svm, &thread_pubkey);
assert!(thread.paused);
match thread.trigger {
antegen_thread_program::state::Trigger::Interval { seconds, .. } => {
assert_eq!(seconds, 30);
}
_ => panic!("Expected Interval trigger"),
}
}
#[test]
fn test_thread_update_trigger_auto_unpauses() {
let (mut svm, _admin, payer) = create_test_env();
let authority = Keypair::new();
svm.airdrop(&authority.pubkey(), DEFAULT_AIRDROP).unwrap();
let thread_pubkey = create_thread_for_update(
&mut svm,
&authority,
&payer,
"tu-auto-unpause",
Trigger::Immediate { jitter: 0 },
);
send_update(
&mut svm,
&authority,
&payer,
&thread_pubkey,
ThreadUpdateParams {
paused: Some(true),
..Default::default()
},
)
.unwrap();
let thread = deserialize_thread(&svm, &thread_pubkey);
assert!(thread.paused, "Thread should be paused");
send_update(
&mut svm,
&authority,
&payer,
&thread_pubkey,
ThreadUpdateParams {
trigger: Some(Trigger::Timestamp {
unix_ts: 1900000000,
jitter: 0,
}),
..Default::default()
},
)
.unwrap();
let thread = deserialize_thread(&svm, &thread_pubkey);
assert!(
!thread.paused,
"Thread should auto-unpause when trigger is updated"
);
}
#[test]
fn test_thread_update_trigger_with_explicit_pause_stays_paused() {
let (mut svm, _admin, payer) = create_test_env();
let authority = Keypair::new();
svm.airdrop(&authority.pubkey(), DEFAULT_AIRDROP).unwrap();
let thread_pubkey = create_thread_for_update(
&mut svm,
&authority,
&payer,
"tu-stay-paused",
Trigger::Immediate { jitter: 0 },
);
send_update(
&mut svm,
&authority,
&payer,
&thread_pubkey,
ThreadUpdateParams {
paused: Some(true),
trigger: Some(Trigger::Timestamp {
unix_ts: 1900000000,
jitter: 0,
}),
},
)
.unwrap();
let thread = deserialize_thread(&svm, &thread_pubkey);
assert!(
thread.paused,
"Thread should stay paused when paused is explicitly set"
);
}