Skip to content

Commit 9f453bd

Browse files
Merge pull request Predictify-org#274 from Jagadeeshftw/event-test
feat: add comprehensive tests for event archiving and historical queries
2 parents 6e74598 + d473288 commit 9f453bd

File tree

1 file changed

+255
-0
lines changed
  • contracts/predictify-hybrid/src

1 file changed

+255
-0
lines changed

contracts/predictify-hybrid/src/test.rs

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3625,4 +3625,259 @@ fn test_event_history_entry_no_sensitive_data() {
36253625
assert!(e.end_time > 0);
36263626
assert!(e.category.len() > 0);
36273627
// created_at is from registry (ledger timestamp at creation); may be 0 in test env
3628+
}
3629+
3630+
// ===== COMPREHENSIVE EVENT ARCHIVE AND HISTORICAL QUERY TESTS (#272) =====
3631+
//
3632+
// Covers: archive marking (resolved + cancelled), query by status (Active/Resolved/Cancelled),
3633+
// time range and category filters, pagination and max result size, result correctness,
3634+
// and no leakage of private data (votes, stakes, admin).
3635+
3636+
/// Archive marking: cancelled market can be archived (not only resolved).
3637+
#[test]
3638+
fn test_archive_event_cancelled_market() {
3639+
let test = PredictifyTest::setup();
3640+
let client = PredictifyHybridClient::new(&test.env, &test.contract_id);
3641+
let market_id = test.create_test_market();
3642+
3643+
test.env.mock_all_auths();
3644+
let _ = client.cancel_event(&test.admin, &market_id, &None);
3645+
3646+
test.env.mock_all_auths();
3647+
client.archive_event(&test.admin, &market_id);
3648+
3649+
let is_archived = test.env.as_contract(&test.contract_id, || {
3650+
crate::event_archive::EventArchive::is_archived(&test.env, &market_id)
3651+
});
3652+
assert!(is_archived);
3653+
3654+
let (entries, _) = client.query_events_by_status(&MarketState::Cancelled, &0u32, &10u32);
3655+
let found = (0..entries.len()).any(|i| entries.get(i).unwrap().market_id == market_id);
3656+
assert!(found);
3657+
}
3658+
3659+
/// is_archived returns false for a market that was never archived.
3660+
#[test]
3661+
fn test_is_archived_returns_false_for_non_archived() {
3662+
let test = PredictifyTest::setup();
3663+
let market_id = test.create_test_market();
3664+
3665+
let is_archived = test.env.as_contract(&test.contract_id, || {
3666+
crate::event_archive::EventArchive::is_archived(&test.env, &market_id)
3667+
});
3668+
assert!(!is_archived);
3669+
}
3670+
3671+
/// After archive, query by status returns entry with archived_at set.
3672+
#[test]
3673+
fn test_archived_entry_has_archived_at_set() {
3674+
let test = PredictifyTest::setup();
3675+
let client = PredictifyHybridClient::new(&test.env, &test.contract_id);
3676+
let market_id = test.create_test_market();
3677+
3678+
test.env.mock_all_auths();
3679+
client.vote(&test.user, &market_id, &String::from_str(&test.env, "yes"), &10_0000000);
3680+
let market = test.env.as_contract(&test.contract_id, || {
3681+
test.env.storage().persistent().get::<Symbol, Market>(&market_id).unwrap()
3682+
});
3683+
test.env.ledger().set(LedgerInfo {
3684+
timestamp: market.end_time + 1,
3685+
protocol_version: 22,
3686+
sequence_number: test.env.ledger().sequence(),
3687+
network_id: Default::default(),
3688+
base_reserve: 10,
3689+
min_temp_entry_ttl: 1,
3690+
min_persistent_entry_ttl: 1,
3691+
max_entry_ttl: 10000,
3692+
});
3693+
test.env.mock_all_auths();
3694+
client.resolve_market_manual(&test.admin, &market_id, &String::from_str(&test.env, "yes"));
3695+
test.env.mock_all_auths();
3696+
client.archive_event(&test.admin, &market_id);
3697+
3698+
let (entries, _) = client.query_events_by_status(&MarketState::Resolved, &0u32, &10u32);
3699+
let entry = (0..entries.len())
3700+
.find(|i| entries.get(*i).unwrap().market_id == market_id)
3701+
.map(|i| entries.get(i).unwrap());
3702+
assert!(entry.is_some());
3703+
assert!(entry.unwrap().archived_at.is_some());
3704+
}
3705+
3706+
/// Query by status Active returns active markets.
3707+
#[test]
3708+
fn test_query_events_by_status_active() {
3709+
let test = PredictifyTest::setup();
3710+
let client = PredictifyHybridClient::new(&test.env, &test.contract_id);
3711+
let market_id = test.create_test_market();
3712+
3713+
let (entries, _) = client.query_events_by_status(&MarketState::Active, &0u32, &10u32);
3714+
let found = (0..entries.len()).any(|i| entries.get(i).unwrap().market_id == market_id);
3715+
assert!(found);
3716+
}
3717+
3718+
/// Query by status Cancelled returns cancelled markets.
3719+
#[test]
3720+
fn test_query_events_by_status_cancelled() {
3721+
let test = PredictifyTest::setup();
3722+
let client = PredictifyHybridClient::new(&test.env, &test.contract_id);
3723+
let market_id = test.create_test_market();
3724+
3725+
test.env.mock_all_auths();
3726+
let _ = client.cancel_event(&test.admin, &market_id, &None);
3727+
3728+
let (entries, _) = client.query_events_by_status(&MarketState::Cancelled, &0u32, &10u32);
3729+
let found = (0..entries.len()).any(|i| entries.get(i).unwrap().market_id == market_id);
3730+
assert!(found);
3731+
}
3732+
3733+
/// Time range: range that excludes all creation times returns empty (or no match).
3734+
#[test]
3735+
fn test_query_events_history_empty_time_range() {
3736+
let test = PredictifyTest::setup();
3737+
let client = PredictifyHybridClient::new(&test.env, &test.contract_id);
3738+
test.create_test_market();
3739+
let ts = test.env.ledger().timestamp();
3740+
// Range entirely in the future: no events created in that range
3741+
let from_ts = ts.saturating_add(86400 * 365);
3742+
let to_ts = ts.saturating_add(86400 * 366);
3743+
3744+
let (entries, next_cursor) = client.query_events_history(&from_ts, &to_ts, &0u32, &10u32);
3745+
assert!(entries.is_empty());
3746+
assert!(next_cursor > 0);
3747+
}
3748+
3749+
/// Time range: from_ts > to_ts yields no matches (filter excludes all).
3750+
#[test]
3751+
fn test_query_events_history_inverted_range() {
3752+
let test = PredictifyTest::setup();
3753+
let client = PredictifyHybridClient::new(&test.env, &test.contract_id);
3754+
test.create_test_market();
3755+
let ts = test.env.ledger().timestamp();
3756+
let from_ts = ts.saturating_add(1000);
3757+
let to_ts = ts.saturating_sub(1000);
3758+
3759+
let (entries, _) = client.query_events_history(&from_ts, &to_ts, &0u32, &10u32);
3760+
assert!(entries.is_empty());
3761+
}
3762+
3763+
/// Category filter: non-matching category returns empty for this contract's single market (BTC).
3764+
#[test]
3765+
fn test_query_events_by_category_non_matching() {
3766+
let test = PredictifyTest::setup();
3767+
let client = PredictifyHybridClient::new(&test.env, &test.contract_id);
3768+
test.create_test_market();
3769+
3770+
let (entries, _) = client.query_events_by_category(
3771+
&String::from_str(&test.env, "NONEXISTENT_FEED"),
3772+
&0u32,
3773+
&10u32,
3774+
);
3775+
assert!(entries.is_empty());
3776+
}
3777+
3778+
/// Pagination: cursor beyond registry length returns empty result.
3779+
#[test]
3780+
fn test_query_events_pagination_cursor_beyond_registry() {
3781+
let test = PredictifyTest::setup();
3782+
let client = PredictifyHybridClient::new(&test.env, &test.contract_id);
3783+
test.create_test_market();
3784+
let ts = test.env.ledger().timestamp();
3785+
let from_ts = ts.saturating_sub(3600);
3786+
let to_ts = ts.saturating_add(3600);
3787+
3788+
let (_, next_cursor) = client.query_events_history(&from_ts, &to_ts, &0u32, &10u32);
3789+
let (entries, _) = client.query_events_history(&from_ts, &to_ts, &next_cursor, &10u32);
3790+
assert!(entries.is_empty());
3791+
}
3792+
3793+
/// Max result size: limit requested above MAX_QUERY_LIMIT (30) is capped; at most 30 returned per page.
3794+
#[test]
3795+
fn test_query_events_history_respects_max_limit() {
3796+
let test = PredictifyTest::setup();
3797+
let client = PredictifyHybridClient::new(&test.env, &test.contract_id);
3798+
test.create_test_market();
3799+
let ts = test.env.ledger().timestamp();
3800+
let from_ts = ts.saturating_sub(86400 * 365);
3801+
let to_ts = ts.saturating_add(86400);
3802+
3803+
let limit_requested = 100u32;
3804+
assert!(limit_requested > crate::event_archive::MAX_QUERY_LIMIT);
3805+
let (entries, _) = client.query_events_history(&from_ts, &to_ts, &0u32, &limit_requested);
3806+
let len: u32 = entries.len();
3807+
assert!(len <= crate::event_archive::MAX_QUERY_LIMIT);
3808+
}
3809+
3810+
/// Result correctness: resolved market entry has winning_outcome, state Resolved, total_staked matches.
3811+
#[test]
3812+
fn test_query_result_correctness_resolved_market() {
3813+
let test = PredictifyTest::setup();
3814+
let client = PredictifyHybridClient::new(&test.env, &test.contract_id);
3815+
let market_id = test.create_test_market();
3816+
let stake = 50_0000000i128;
3817+
3818+
test.env.mock_all_auths();
3819+
client.vote(
3820+
&test.user,
3821+
&market_id,
3822+
&String::from_str(&test.env, "yes"),
3823+
&stake,
3824+
);
3825+
let market = test.env.as_contract(&test.contract_id, || {
3826+
test.env.storage().persistent().get::<Symbol, Market>(&market_id).unwrap()
3827+
});
3828+
test.env.ledger().set(LedgerInfo {
3829+
timestamp: market.end_time + 1,
3830+
protocol_version: 22,
3831+
sequence_number: test.env.ledger().sequence(),
3832+
network_id: Default::default(),
3833+
base_reserve: 10,
3834+
min_temp_entry_ttl: 1,
3835+
min_persistent_entry_ttl: 1,
3836+
max_entry_ttl: 10000,
3837+
});
3838+
test.env.mock_all_auths();
3839+
client.resolve_market_manual(&test.admin, &market_id, &String::from_str(&test.env, "yes"));
3840+
3841+
let (entries, _) = client.query_events_by_status(&MarketState::Resolved, &0u32, &10u32);
3842+
let entry = (0..entries.len())
3843+
.find(|i| entries.get(*i).unwrap().market_id == market_id)
3844+
.map(|i| entries.get(i).unwrap());
3845+
assert!(entry.is_some());
3846+
let e = entry.unwrap();
3847+
assert_eq!(e.state, MarketState::Resolved);
3848+
assert_eq!(e.winning_outcome, Some(String::from_str(&test.env, "yes")));
3849+
assert_eq!(e.total_staked, stake);
3850+
assert_eq!(e.question, String::from_str(&test.env, "Will BTC go above $25,000 by December 31?"));
3851+
assert!(e.outcomes.len() >= 2);
3852+
}
3853+
3854+
/// No private data: EventHistoryEntry does not expose votes, stakes, claimed, or admin.
3855+
/// We assert only the public fields exist and have expected shape (no way to get user data).
3856+
#[test]
3857+
fn test_event_history_entry_no_private_data_leakage() {
3858+
let test = PredictifyTest::setup();
3859+
let client = PredictifyHybridClient::new(&test.env, &test.contract_id);
3860+
let market_id = test.create_test_market();
3861+
test.env.mock_all_auths();
3862+
client.vote(
3863+
&test.user,
3864+
&market_id,
3865+
&String::from_str(&test.env, "yes"),
3866+
&100_0000000,
3867+
);
3868+
let ts = test.env.ledger().timestamp();
3869+
let from_ts = ts.saturating_sub(3600);
3870+
let to_ts = ts.saturating_add(3600);
3871+
3872+
let (entries, _) = client.query_events_history(&from_ts, &to_ts, &0u32, &5u32);
3873+
assert!(!entries.is_empty());
3874+
let e = entries.get(0).unwrap();
3875+
3876+
// Public fields only: market_id, question, outcomes, end_time, created_at, state,
3877+
// winning_outcome, total_staked, archived_at, category. No votes, stakes, claimed, admin.
3878+
assert!(e.question.len() > 0);
3879+
assert!(e.outcomes.len() >= 2);
3880+
assert!(e.end_time > 0);
3881+
assert!(e.category.len() > 0);
3882+
// EventHistoryEntry has no .votes, .stakes, .claimed, .admin - type guarantees no leakage
36283883
}

0 commit comments

Comments
 (0)