diff --git a/contract_/src/audition/season_and_audition.cairo b/contract_/src/audition/season_and_audition.cairo index f8b5a3f..685592f 100644 --- a/contract_/src/audition/season_and_audition.cairo +++ b/contract_/src/audition/season_and_audition.cairo @@ -26,7 +26,7 @@ pub struct Vote { pub audition_id: felt252, pub performer: felt252, pub voter: felt252, - pub weight: felt252, + pub weight: u64, } // Define the contract interface @@ -106,7 +106,7 @@ pub trait ISeasonAndAudition { audition_id: felt252, performer: felt252, voter: felt252, - weight: felt252, + weight: u64, ); fn get_vote( self: @TContractState, audition_id: felt252, performer: felt252, voter: felt252, @@ -122,6 +122,60 @@ pub trait ISeasonAndAudition { fn is_audition_paused(self: @TContractState, audition_id: felt252) -> bool; fn is_audition_ended(self: @TContractState, audition_id: felt252) -> bool; fn audition_exists(self: @TContractState, audition_id: felt252) -> bool; + + // Genre-based filtering + fn get_seasons_by_genre( + self: @TContractState, genre: felt252, max_results: u32, + ) -> Array; + fn get_auditions_by_genre( + self: @TContractState, genre: felt252, max_results: u32, + ) -> Array; + + // Time-based queries + fn get_active_auditions(self: @TContractState, current_timestamp: u64) -> Array; + fn get_auditions_in_time_range( + self: @TContractState, start_timestamp: u64, end_timestamp: u64, + ) -> Array; + + // Season-based queries + fn get_auditions_by_season(self: @TContractState, season_id: felt252) -> Array; + + // Analytics functions + fn get_audition_vote_count(self: @TContractState, audition_id: felt252) -> u32; + fn get_total_vote_weight_for_performer( + self: @TContractState, audition_id: felt252, performer: felt252, + ) -> u64; + + // Pagination and listing + fn get_seasons_by_ids(self: @TContractState, season_ids: Array) -> Array; + fn get_auditions_by_ids(self: @TContractState, audition_ids: Array) -> Array; + + // Complex filtering + fn get_auditions_by_criteria( + self: @TContractState, + audition_ids: Array, + genre: Option, + season_id: Option, + start_time: Option, + end_time: Option, + paused_state: Option, + ) -> Array; + + // Utility functions + fn is_audition_active( + self: @TContractState, audition_id: felt252, current_timestamp: u64, + ) -> bool; + fn get_audition_status( + self: @TContractState, audition_id: felt252, + ) -> felt252; // 0=not_started, 1=active, 2=ended, 3=paused, 404= Not found + fn count_votes_for_audition( + self: @TContractState, + audition_id: felt252, + voter_performer_pairs: Array<(felt252, felt252)>, + ) -> u32; + fn get_performer_history(self: @TContractState, performer: felt252) -> Array; + fn get_voter_history(self: @TContractState, voter: felt252) -> Array; + fn get_genre_audition_count(self: @TContractState, genre: felt252) -> u32; } #[starknet::contract] @@ -184,6 +238,36 @@ pub mod SeasonAndAudition { audition_winner_amounts: Map, /// price distributed status price_distributed: Map, + // For efficient querying - maintain lists of IDs + all_season_ids: Map, // index -> season_id + all_audition_ids: Map, // index -> audition_id + season_count: u32, + audition_count: u32, + // Vote counting cache + audition_vote_counts: Map, // audition_id -> vote_count + performer_total_weights: Map< + (felt252, felt252), u64, + >, // (audition_id, performer) -> total_weight + // Map from (genre, index) to season_id + seasons_by_genre_items: Map<(felt252, u32), felt252>, + // Map from genre to length of the list + seasons_by_genre_length: Map, + // Map from (genre, index) to audition_id + auditions_by_genre_items: Map<(felt252, u32), felt252>, + // Map from genre to length of the list + auditions_by_genre_length: Map, + // Map from (season_id, index) to audition_id + auditions_by_season_items: Map<(felt252, u32), felt252>, + // Map from season_id to length of the list + auditions_by_season_length: Map, + // Map from (performer, index) to audition_id + performer_audition_history_items: Map<(felt252, u32), felt252>, + // Map from performer to length of the list + performer_audition_history_length: Map, + // Map from (voter, index) to audition_id + voter_audition_history_items: Map<(felt252, u32), felt252>, + // Map from voter to length of the list + voter_audition_history_length: Map, } #[event] @@ -229,11 +313,19 @@ pub mod SeasonAndAudition { ) { self.ownable.assert_only_owner(); assert(!self.global_paused.read(), 'Contract is paused'); - self .seasons - .entry(season_id) - .write(Season { season_id, genre, name, start_timestamp, end_timestamp, paused }); + .write( + season_id, + Season { season_id, genre, name, start_timestamp, end_timestamp, paused }, + ); + let current_count = self.season_count.read(); + self.all_season_ids.write(current_count, season_id); + self.season_count.write(current_count + 1); + + let genre_length = self.seasons_by_genre_length.read(genre); + self.seasons_by_genre_items.write((genre, genre_length), season_id); + self.seasons_by_genre_length.write(genre, genre_length + 1); self .emit( @@ -296,6 +388,18 @@ pub mod SeasonAndAudition { audition_id, season_id, genre, name, start_timestamp, end_timestamp, paused, }, ); + let current_count = self.audition_count.read(); + self.all_audition_ids.write(current_count, audition_id); + self.audition_count.write(current_count + 1); + + // Append to auditions_by_genre_items using simulated list + let genre_length = self.auditions_by_genre_length.read(genre); + self.auditions_by_genre_items.write((genre, genre_length), audition_id); + self.auditions_by_genre_length.write(genre, genre_length + 1); + + let season_length = self.auditions_by_season_length.read(season_id); + self.auditions_by_season_items.write((season_id, season_length), audition_id); + self.auditions_by_season_length.write(season_id, season_length + 1); self .emit( @@ -512,7 +616,7 @@ pub mod SeasonAndAudition { audition_id: felt252, performer: felt252, voter: felt252, - weight: felt252, + weight: u64, ) { self.only_oracle(); assert(!self.global_paused.read(), 'Contract is paused'); @@ -526,7 +630,35 @@ pub mod SeasonAndAudition { self.votes.entry(vote_key).write(Vote { audition_id, performer, voter, weight }); - self.emit(Event::VoteRecorded(VoteRecorded { audition_id, performer, voter, weight })); + let current_count = self.audition_vote_counts.read(audition_id); + self.audition_vote_counts.write(audition_id, current_count + 1); + + let performer_key = (audition_id, performer); + let current_weight = self.performer_total_weights.read(performer_key); + self.performer_total_weights.write(performer_key, current_weight + weight); + + let performer_length = self.performer_audition_history_length.read(performer); + + if performer_length == 0 + || self + .performer_audition_history_items + .read((performer, performer_length - 1)) != audition_id { + self + .performer_audition_history_items + .write((performer, performer_length), audition_id); + self.performer_audition_history_length.write(performer, performer_length + 1); + } + + let voter_length = self.voter_audition_history_length.read(voter); + if voter_length == 0 + || self + .voter_audition_history_items + .read((voter, voter_length - 1)) != audition_id { + self.voter_audition_history_items.write((voter, voter_length), audition_id); + self.voter_audition_history_length.write(voter, voter_length + 1); + } + + self.emit(Event::VoteRecorded(VoteRecorded { audition_id, performer, voter, weight: weight.into() })); } fn get_vote( @@ -642,6 +774,327 @@ pub mod SeasonAndAudition { let audition = self.auditions.entry(audition_id).read(); audition.audition_id != 0 } + + // Genre-based filtering + fn get_seasons_by_genre( + self: @ContractState, genre: felt252, max_results: u32, + ) -> Array { + let mut result = ArrayTrait::new(); + let length = self + .seasons_by_genre_length + .read(genre); // Get the list length for this genre + + let mut i = 0; + while i < length && i < max_results { + let season_id = self + .seasons_by_genre_items + .read((genre, i)); // Read item at index i + if season_id != 0 { + let season = self.seasons.read(season_id); + result.append(season); + } + i += 1; + }; + + result + } + + + fn get_auditions_by_genre( + self: @ContractState, genre: felt252, max_results: u32, + ) -> Array { + let mut result = ArrayTrait::new(); + let length = self + .auditions_by_genre_length + .read(genre); // Get the list length for this genre + + let mut i = 0; + while i < length && i < max_results { + let audition_id = self + .auditions_by_genre_items + .read((genre, i)); // Read item at index i + if audition_id != 0 { + let audition = self.auditions.read(audition_id); + result.append(audition); + } + i += 1; + }; + + result + } + + + // Time-based queries + fn get_active_auditions(self: @ContractState, current_timestamp: u64) -> Array { + let mut result = ArrayTrait::new(); + let total_auditions = self.audition_count.read(); + + let mut i = 0; + while i < total_auditions { + let audition_id = self.all_audition_ids.read(i); + if audition_id != 0 { + let audition = self.auditions.read(audition_id); + if self.is_audition_active(audition_id, current_timestamp.into()) { + result.append(audition); + } + } + i += 1; + }; + + result + } + + fn get_auditions_in_time_range( + self: @ContractState, start_timestamp: u64, end_timestamp: u64, + ) -> Array { + assert(end_timestamp > start_timestamp, 'Start time > end time'); + let mut result = ArrayTrait::new(); + let total_auditions: u32 = self.audition_count.read().try_into().unwrap(); + + let mut i = 0_u32; + while i < total_auditions { + let audition_id = self.all_audition_ids.read(i); + if audition_id != 0 { + let audition = self.auditions.read(audition_id); + if audition.start_timestamp.try_into().unwrap() >= start_timestamp + && audition.end_timestamp.try_into().unwrap() <= end_timestamp { + result.append(audition); + } + } + i += 1; + }; + + result + } + + // Season-based queries + fn get_auditions_by_season(self: @ContractState, season_id: felt252) -> Array { + let mut result = ArrayTrait::new(); + let length = self + .auditions_by_season_length + .read(season_id); // Get the list length for this season + + let mut i = 0; + while i < length { + let audition_id = self + .auditions_by_season_items + .read((season_id, i)); // Read item at index i + if audition_id != 0 { + let audition = self.auditions.read(audition_id); + result.append(audition); + } + i += 1; + }; + + result + } + + // Analytics functions + fn get_audition_vote_count(self: @ContractState, audition_id: felt252) -> u32 { + self.audition_vote_counts.read(audition_id) + } + + fn get_total_vote_weight_for_performer( + self: @ContractState, audition_id: felt252, performer: felt252, + ) -> u64 { + self.performer_total_weights.read((audition_id, performer)) + } + + // Pagination and listing + fn get_seasons_by_ids(self: @ContractState, season_ids: Array) -> Array { + let mut result = ArrayTrait::new(); + let season_ids_span = season_ids.span(); + + for season_id in season_ids_span { + let season = self.seasons.read(*season_id); + if season.season_id != 0 { + result.append(season); + } + }; + + result + } + + fn get_auditions_by_ids( + self: @ContractState, audition_ids: Array, + ) -> Array { + let mut result = ArrayTrait::new(); + let audition_ids_span = audition_ids.span(); + + for audition_id in audition_ids_span { + let audition = self.auditions.read(*audition_id); + if audition.audition_id != 0 { + result.append(audition); + } + }; + + result + } + + // Complex filtering + fn get_auditions_by_criteria( + self: @ContractState, + audition_ids: Array, + genre: Option, + season_id: Option, + start_time: Option, + end_time: Option, + paused_state: Option, + ) -> Array { + let mut result = ArrayTrait::new(); + let audition_ids_span = audition_ids.span(); + + for audition_id in audition_ids_span { + let audition = self.auditions.read(*audition_id); + if audition.audition_id == 0 { + continue; + } + + // Apply filters + if let Option::Some(filter_genre) = genre { + if audition.genre != filter_genre { + continue; + } + } + + if let Option::Some(filter_season_id) = season_id { + if audition.season_id != filter_season_id { + continue; + } + } + + if let Option::Some(filter_start_time) = start_time { + if audition + .start_timestamp + .try_into() + .expect('start_timestamp conversion') < filter_start_time { + continue; + } + } + + if let Option::Some(filter_end_time) = end_time { + if audition + .end_timestamp + .try_into() + .expect('end_timestamp conversion') > filter_end_time { + continue; + } + } + + if let Option::Some(filter_paused) = paused_state { + if audition.paused != filter_paused { + continue; + } + } + + result.append(audition); + }; + + result + } + + // Utility functions + fn is_audition_active( + self: @ContractState, audition_id: felt252, current_timestamp: u64, + ) -> bool { + let audition = self.auditions.read(audition_id); + if audition.audition_id == 0 || audition.paused { + return false; + } + + audition + .start_timestamp + .try_into() + .expect('start_timestamp conversion') <= current_timestamp + && (audition.end_timestamp == 0 + || audition + .end_timestamp + .try_into() + .expect('end_timestamp conversion') > current_timestamp) + } + + /// returns 404 if not found + /// 0 = Not started + /// 1 = Active + /// 2 = Ended + /// 3 = Paused + fn get_audition_status(self: @ContractState, audition_id: felt252) -> felt252 { + let audition = self.auditions.read(audition_id); + if audition.audition_id == 0 { + return 404; // Not found + } + + if audition.paused { + return 3; // Paused + } + + let current_time = get_block_timestamp(); + + if audition + .start_timestamp + .try_into() + .expect('start_timestamp conversion') > current_time { + return 0; // Not started + } + + if audition.end_timestamp != 0 + && audition + .end_timestamp + .try_into() + .expect('end_timestamp conversion') <= current_time { + return 2; // Ended + } + + 1 // Active + } + + fn count_votes_for_audition( + self: @ContractState, + audition_id: felt252, + voter_performer_pairs: Array<(felt252, felt252)>, + ) -> u32 { + let mut count = 0; + let pairs_span = voter_performer_pairs.span(); + + for pair in pairs_span { + let (performer, voter) = *pair; + let vote_key = (audition_id, performer, voter); + let vote = self.votes.read(vote_key); + if vote.audition_id != 0 { + count += 1; + } + }; + + count + } + + fn get_performer_history(self: @ContractState, performer: felt252) -> Array { + let length = self.performer_audition_history_length.read(performer); + let mut result = ArrayTrait::new(); + let mut i = 0; + while i < length { + let audition_id = self.performer_audition_history_items.read((performer, i)); + result.append(audition_id); + i += 1; + }; + result + } + + fn get_voter_history(self: @ContractState, voter: felt252) -> Array { + let length = self.voter_audition_history_length.read(voter); + let mut result = ArrayTrait::new(); + let mut i = 0; + while i < length { + let audition_id = self.voter_audition_history_items.read((voter, i)); + result.append(audition_id); + i += 1; + }; + result + } + + fn get_genre_audition_count(self: @ContractState, genre: felt252) -> u32 { + self.auditions_by_genre_length.read(genre) + } } #[generate_trait] diff --git a/contract_/tests/test_season_and_audition.cairo b/contract_/tests/test_season_and_audition.cairo index e830331..f52bc7f 100644 --- a/contract_/tests/test_season_and_audition.cairo +++ b/contract_/tests/test_season_and_audition.cairo @@ -2351,3 +2351,437 @@ fn test_end_audition_functionality() { stop_cheat_block_timestamp(contract.contract_address); stop_cheat_caller_address(contract.contract_address); } + +// Additional helper functions for testing query functions +fn create_test_season(season_id: felt252, genre: felt252, name: felt252) -> Season { + Season { + season_id, + genre, + name, + start_timestamp: 1672531200, + end_timestamp: 1675123200, + paused: false, + } +} + +fn create_test_audition_with_times( + audition_id: felt252, + season_id: felt252, + genre: felt252, + name: felt252, + start_timestamp: felt252, + end_timestamp: felt252, + paused: bool, +) -> Audition { + Audition { audition_id, season_id, genre, name, start_timestamp, end_timestamp, paused } +} + +fn setup_test_data(contract: ISeasonAndAuditionDispatcher) { + // Create multiple seasons with different genres + contract.create_season(1, 'Pop', 'Pop Season 1', 1672531200, 1675123200, false); + contract.create_season(2, 'Rock', 'Rock Season 1', 1672531200, 1675123200, false); + contract.create_season(3, 'Pop', 'Pop Season 2', 1672531200, 1675123200, false); + + contract.create_audition(1, 1, 'Pop', 'Pop Audition 1', 1672531200, 1675123200, false); + contract.create_audition(2, 1, 'Pop', 'Pop Audition 2', 1672531200, 1675123200, false); + contract.create_audition(3, 2, 'Rock', 'Rock Audition 1', 1672531200, 1675123200, false); + contract.create_audition(4, 3, 'Pop', 'Pop Audition 3', 1672531200, 1675123200, true); // paused + + contract + .create_audition( + 5, 1, 'Pop', 'Future Audition', 1893456000, 1896048000, false, + ); // Future dates + contract + .create_audition( + 6, 2, 'Rock', 'Past Audition', 1640995200, 1643673600, false, + ); // Past dates + + contract.add_oracle(OWNER()); + contract.record_vote(1, 'performer1', 'voter1', 100); + contract.record_vote(2, 'performer1', 'voter1', 150); // Same performer in different audition + contract.record_vote(3, 'performer2', 'voter2', 200); +} + +// Test genre-based filtering +#[test] +fn test_get_seasons_by_genre() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + setup_test_data(contract); + + // Test getting Pop seasons (should use index) + let pop_seasons = contract.get_seasons_by_genre('Pop', 10); + assert!(pop_seasons.len() == 2, "Should return 2 Pop seasons"); + + // Test getting Rock seasons + let rock_seasons = contract.get_seasons_by_genre('Rock', 10); + assert!(rock_seasons.len() == 1, "Should return 1 Rock season"); + + // Test max_results limit + let limited_seasons = contract.get_seasons_by_genre('Pop', 1); + assert!(limited_seasons.len() == 1, "Should respect max_results limit"); + + // Test non-existent genre + let jazz_seasons = contract.get_seasons_by_genre('Jazz', 10); + assert!(jazz_seasons.len() == 0, "Should return empty array for non-existent genre"); + + stop_cheat_caller_address(contract.contract_address); +} + +#[test] +fn test_get_auditions_by_genre() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + setup_test_data(contract); + + // Test getting Pop auditions (should use index) + let pop_auditions = contract.get_auditions_by_genre('Pop', 10); + assert!(pop_auditions.len() == 4, "Should return 4 Pop auditions"); + + // Test getting Rock auditions + let rock_auditions = contract.get_auditions_by_genre('Rock', 10); + assert!(rock_auditions.len() == 2, "Should return 2 Rock auditions"); + + // Test max_results limit + let limited_auditions = contract.get_auditions_by_genre('Pop', 2); + assert!(limited_auditions.len() == 2, "Should respect max_results limit"); + + stop_cheat_caller_address(contract.contract_address); +} + +// Test time-based queries +#[test] +fn test_get_auditions_in_time_range() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + setup_test_data(contract); + + // Test getting auditions in a specific time range + let start_time: u64 = 1672531200; + let end_time: u64 = 1675123200; + let auditions_in_range = contract.get_auditions_in_time_range(start_time, end_time); + + assert!(auditions_in_range.len() >= 4, "Should return auditions in time range"); + + // Test with future time range + let future_start: u64 = 1893456000; + let future_end: u64 = 1896048000; + let future_auditions = contract.get_auditions_in_time_range(future_start, future_end); + assert!(future_auditions.len() >= 1, "Should return future auditions"); + + stop_cheat_caller_address(contract.contract_address); +} + +// Test invalid time range (start > end) +#[test] +#[should_panic(expected: ('Start time > end time',))] +fn test_get_auditions_in_time_range_invalid() { + let (contract, _, _) = deploy_contract(); + start_cheat_caller_address(contract.contract_address, OWNER()); + contract.get_auditions_in_time_range(1675123200, 1672531200); // start > end + stop_cheat_caller_address(contract.contract_address); +} + + +// Test season-based queries +#[test] +fn test_get_auditions_by_season() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + setup_test_data(contract); + + // Test getting auditions for season 1 (should use index) + let season1_auditions = contract.get_auditions_by_season(1); + assert!(season1_auditions.len() == 3, "Should return 3 auditions for season 1"); + + // Test getting auditions for season 2 + let season2_auditions = contract.get_auditions_by_season(2); + assert!(season2_auditions.len() == 2, "Should return 2 auditions for season 2"); + + // Test getting auditions for season 3 + let season3_auditions = contract.get_auditions_by_season(3); + assert!(season3_auditions.len() == 1, "Should return 1 audition for season 3"); + + // Test non-existent season + let nonexistent_auditions = contract.get_auditions_by_season(999); + assert!(nonexistent_auditions.len() == 0, "Should return empty array for non-existent season"); + + stop_cheat_caller_address(contract.contract_address); +} + +// Test analytics functions +#[test] +fn test_vote_analytics() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + setup_test_data(contract); + + contract.record_vote(1, 'test_performer1', 'test_voter1', 100); + contract.record_vote(1, 'test_performer1', 'test_voter2', 150); + contract.record_vote(1, 'test_performer2', 'test_voter1', 200); + + // Test vote count + let vote_count = contract.get_audition_vote_count(1); + assert!( + vote_count == 4, "Should return correct vote count", + ); // 1 in the setup_test_data + 3 here in this function = 4 votes total for the audition with `id = 1` + + // Test total weight for performer + let performer1_weight = contract.get_total_vote_weight_for_performer(1, 'test_performer1'); + assert!(performer1_weight == 250, "Should return correct total weight for performer1"); + + let performer2_weight = contract.get_total_vote_weight_for_performer(1, 'test_performer2'); + assert!(performer2_weight == 200, "Should return correct total weight for performer2"); + + stop_cheat_caller_address(contract.contract_address); +} + +#[test] +fn test_get_genre_audition_count() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + setup_test_data(contract); + + let pop_count = contract.get_genre_audition_count('Pop'); + assert!(pop_count == 4, "Should return 4 auditions for Pop genre"); + + let rock_count = contract.get_genre_audition_count('Rock'); + assert!(rock_count == 2, "Should return 2 auditions for Rock genre"); + + let unknown_count = contract.get_genre_audition_count('Jazz'); + assert!(unknown_count == 0, "Should return 0 for unknown genre"); + + stop_cheat_caller_address(contract.contract_address); +} + + +// Test pagination and listing +#[test] +fn test_get_seasons_by_ids() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + setup_test_data(contract); + + // Test getting multiple seasons by IDs + let season_ids = array![1, 2, 3]; + let seasons = contract.get_seasons_by_ids(season_ids); + assert!(seasons.len() == 3, "Should return 3 seasons"); + + // Test with some non-existent IDs + let mixed_ids = array![1, 999, 2]; + let mixed_seasons = contract.get_seasons_by_ids(mixed_ids); + assert!(mixed_seasons.len() == 2, "Should return only existing seasons"); + + stop_cheat_caller_address(contract.contract_address); +} + +#[test] +fn test_get_auditions_by_ids() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + setup_test_data(contract); + + // Test getting multiple auditions by IDs + let audition_ids = array![1, 2, 3]; + let auditions = contract.get_auditions_by_ids(audition_ids); + assert!(auditions.len() == 3, "Should return 3 auditions"); + + // Test with some non-existent IDs + let mixed_ids = array![1, 999, 2]; + let mixed_auditions = contract.get_auditions_by_ids(mixed_ids); + assert!(mixed_auditions.len() == 2, "Should return only existing auditions"); + + stop_cheat_caller_address(contract.contract_address); +} + +// Test utility functions +#[test] +fn test_is_audition_active() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + setup_test_data(contract); + + // Test active audition + let current_time: u64 = 1673000000; // Between start and end + let is_active = contract.is_audition_active(1, current_time); + assert!(is_active, "Should return true for active audition"); + + // Test paused audition + let is_paused_active = contract.is_audition_active(4, current_time); + assert!(!is_paused_active, "Should return false for paused audition"); + + // Test future audition + let is_future_active = contract.is_audition_active(5, current_time); + assert!(!is_future_active, "Should return false for future audition"); + + // Test past audition + let is_past_active = contract.is_audition_active(6, current_time); + assert!(!is_past_active, "Should return false for past audition"); + + stop_cheat_caller_address(contract.contract_address); +} + +#[test] +fn test_count_votes_for_audition() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + setup_test_data(contract); + + contract.record_vote(1, 'test_performer1', 'test_voter1', 100); + contract.record_vote(1, 'test_performer2', 'test_voter2', 150); + contract.record_vote(1, 'test_performer3', 'test_voter3', 200); + + // Test counting votes + let voter_performer_pairs = array![ + ('test_performer1', 'test_voter1'), + ('test_performer2', 'test_voter2'), + ('test_performer3', 'test_voter3'), + ]; + let vote_count = contract.count_votes_for_audition(1, voter_performer_pairs); + assert!(vote_count == 3, "Should return correct vote count"); + + // Test with some non-existent pairs + let mixed_pairs = array![('test_performer1', 'test_voter1'), ('performer999', 'voter999')]; + let mixed_count = contract.count_votes_for_audition(1, mixed_pairs); + assert!(mixed_count == 1, "Should return count only for existing votes"); + + stop_cheat_caller_address(contract.contract_address); +} + +// Test performer history +#[test] +fn test_get_performer_history() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + setup_test_data(contract); + + let history = contract.get_performer_history('performer1'); + assert!(history.len() == 2, "Should return 2 auditions for performer1"); + assert!(*history.at(0) == 1, "First audition should be 1"); + assert!(*history.at(1) == 2, "Second audition should be 2"); + + let empty_history = contract.get_performer_history('unknown'); + assert!(empty_history.len() == 0, "Should return empty for unknown performer"); + + stop_cheat_caller_address(contract.contract_address); +} + +// Test voter history +#[test] +fn test_get_voter_history() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + setup_test_data(contract); + + let history = contract.get_voter_history('voter1'); + assert!(history.len() == 2, "Should return 2 auditions for voter1"); + assert!(*history.at(0) == 1, "First audition should be 1"); + assert!(*history.at(1) == 2, "Second audition should be 2"); + + let empty_history = contract.get_voter_history('unknown'); + assert!(empty_history.len() == 0, "Should return empty for unknown voter"); + + stop_cheat_caller_address(contract.contract_address); +} + + +// Test edge cases and error scenarios +#[test] +fn test_query_functions_performance_limits() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + + // Create many seasons and auditions + let mut i: u32 = 1; + while i <= 20 { + contract.create_season(i.into(), 'Pop', 'Season', 1672531200, 1675123200, false); + contract + .create_audition(i.into(), i.into(), 'Pop', 'Audition', 1672531200, 1675123200, false); + i += 1; + }; + + // Test max_results limiting + let limited_seasons = contract.get_seasons_by_genre('Pop', 5); + assert!(limited_seasons.len() == 5, "Should respect max_results limit"); + + let limited_auditions = contract.get_auditions_by_genre('Pop', 5); + assert!(limited_auditions.len() == 5, "Should respect max_results limit"); + + stop_cheat_caller_address(contract.contract_address); +} + +#[test] +fn test_query_functions_with_empty_data() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + + // Test functions with no data + let empty_seasons = contract.get_seasons_by_genre('Pop', 10); + assert!(empty_seasons.len() == 0, "Should return empty array when no data"); + + let empty_auditions = contract.get_auditions_by_genre('Pop', 10); + assert!(empty_auditions.len() == 0, "Should return empty array when no data"); + + let empty_active = contract.get_active_auditions(1673000000); + assert!(empty_active.len() == 0, "Should return empty array when no data"); + + stop_cheat_caller_address(contract.contract_address); +} + +// Stress test with large datasets +#[test] +fn test_stress_large_dataset() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + + // Create 50 seasons and auditions + let mut i: u32 = 1; + while i <= 50 { + contract.create_season(i.into(), 'Pop', 'Season', 1672531200, 1675123200, false); + contract + .create_audition(i.into(), i.into(), 'Pop', 'Audition', 1672531200, 1675123200, false); + i += 1; + }; + + // Test querying with large data + let pop_seasons = contract.get_seasons_by_genre('Pop', 50); + assert!(pop_seasons.len() == 50, "Should handle 50 Pop seasons"); + + let pop_auditions = contract.get_auditions_by_genre('Pop', 50); + assert!(pop_auditions.len() == 50, "Should handle 50 Pop auditions"); + + stop_cheat_caller_address(contract.contract_address); +} + +#[test] +#[should_panic(expected: ('Vote already exists',))] +fn test_duplicate_vote() { + let (contract, _, _) = deploy_contract(); + + start_cheat_caller_address(contract.contract_address, OWNER()); + setup_test_data(contract); + contract.add_oracle(OWNER()); + + // Record a vote + contract.record_vote(1, 'performer1', 'voter1', 100); + + // Attempt duplicate vote (same key) + contract.record_vote(1, 'performer1', 'voter1', 150); + + stop_cheat_caller_address(contract.contract_address); +} diff --git a/contract_/tests/test_vote_recording.cairo b/contract_/tests/test_vote_recording.cairo index 35e8b76..6e0ffce 100644 --- a/contract_/tests/test_vote_recording.cairo +++ b/contract_/tests/test_vote_recording.cairo @@ -88,7 +88,7 @@ fn test_record_vote_success() { let audition_id: felt252 = 1; let performer: felt252 = 'performer1'; let voter: felt252 = 'voter1'; - let weight: felt252 = 100; + let weight: u64 = 100; // Create audition first create_test_audition(contract, audition_id); @@ -115,7 +115,7 @@ fn test_record_vote_success() { ( contract.contract_address, SeasonAndAudition::Event::VoteRecorded( - VoteRecorded { audition_id, performer, voter, weight }, + VoteRecorded { audition_id, performer, voter, weight: weight.into() }, ), ), ], @@ -130,7 +130,7 @@ fn test_record_vote_duplicate_should_fail() { let audition_id: felt252 = 1; let performer: felt252 = 'performer1'; let voter: felt252 = 'voter1'; - let weight: felt252 = 100; + let weight: u64 = 100; // Create audition first create_test_audition(contract, audition_id); @@ -152,7 +152,7 @@ fn test_record_vote_unauthorized_should_fail() { let audition_id: felt252 = 1; let performer: felt252 = 'performer1'; let voter: felt252 = 'voter1'; - let weight: felt252 = 100; + let weight: u64 = 100; // Create audition first create_test_audition(contract, audition_id); @@ -172,7 +172,7 @@ fn test_record_multiple_votes_different_combinations() { let performer2: felt252 = 'performer2'; let voter1: felt252 = 'voter1'; let voter2: felt252 = 'voter2'; - let weight: felt252 = 100; + let weight: u64 = 100; // Create audition first create_test_audition(contract, audition_id); @@ -213,7 +213,7 @@ fn test_record_votes_different_auditions() { let audition_id2: felt252 = 2; let performer: felt252 = 'performer1'; let voter: felt252 = 'voter1'; - let weight: felt252 = 100; + let weight: u64 = 100; // Create auditions first create_test_audition(contract, audition_id1);