@@ -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