diff --git a/primitives/src/campaign.rs b/primitives/src/campaign.rs index b2f2fdcc5..eb1298e86 100644 --- a/primitives/src/campaign.rs +++ b/primitives/src/campaign.rs @@ -40,7 +40,7 @@ mod campaign_id { write!(f, "CampaignId({})", self) } } - + impl CampaignId { /// Generates randomly a `CampaignId` using `Uuid::new_v4().to_simple()` pub fn new() -> Self { diff --git a/sentry/src/routes/campaign.rs b/sentry/src/routes/campaign.rs index 29f7c1888..8fab0fb2a 100644 --- a/sentry/src/routes/campaign.rs +++ b/sentry/src/routes/campaign.rs @@ -997,10 +997,17 @@ mod test { use crate::test_util::setup_dummy_app; use crate::update_campaign::DeltaBudget; use adapter::primitives::Deposit; + use chrono::{TimeZone, Utc}; use hyper::StatusCode; use primitives::{ - test_util::{DUMMY_CAMPAIGN, IDS, LEADER}, - BigNum, ValidatorId, + campaign::validators::Validators, + config::GANACHE_CONFIG, + sentry::campaign_list::{CampaignListResponse, ValidatorParam}, + test_util::{ + DUMMY_CAMPAIGN, DUMMY_VALIDATOR_FOLLOWER, DUMMY_VALIDATOR_LEADER, FOLLOWER, GUARDIAN, + IDS, LEADER, LEADER_2, PUBLISHER_2, + }, + BigNum, ValidatorDesc, ValidatorId, }; #[tokio::test] @@ -1427,4 +1434,305 @@ mod test { ); } } + + async fn res_to_campaign_list_response(res: Response) -> CampaignListResponse { + let json = hyper::body::to_bytes(res.into_body()) + .await + .expect("Should get json"); + + serde_json::from_slice(&json).expect("Should deserialize CampaignListResponse") + } + + #[tokio::test] + async fn test_campaign_list() { + let mut app = setup_dummy_app().await; + app.config.campaigns_find_limit = 2; + // Setting up new leader and a channel and campaign which use it on Ganache #1337 + let dummy_leader_2 = ValidatorDesc { + id: IDS[&LEADER_2], + url: "http://tom.adex.network".to_string(), + fee: 200.into(), + fee_addr: None, + }; + let channel_new_leader = Channel { + leader: IDS[&*LEADER_2], + follower: IDS[&*FOLLOWER], + guardian: *GUARDIAN, + token: DUMMY_CAMPAIGN.channel.token, + nonce: DUMMY_CAMPAIGN.channel.nonce, + }; + let mut campaign_new_leader = DUMMY_CAMPAIGN.clone(); + campaign_new_leader.id = CampaignId::new(); + campaign_new_leader.channel = channel_new_leader; + campaign_new_leader.validators = + Validators::new((dummy_leader_2.clone(), DUMMY_VALIDATOR_FOLLOWER.clone())); + campaign_new_leader.created = Utc.ymd(2021, 2, 1).and_hms(8, 0, 0); + + let chain_1_token = GANACHE_CONFIG + .chains + .get("Ganache #1") + .unwrap() + .tokens + .get("Mocked TOKEN 2") + .unwrap() + .address; + // Setting up new follower and a channel and campaign which use it on Ganache #1 + let dummy_follower_2 = ValidatorDesc { + id: IDS[&GUARDIAN], + url: "http://jerry.adex.network".to_string(), + fee: 300.into(), + fee_addr: None, + }; + let channel_new_follower = Channel { + leader: IDS[&*LEADER], + follower: IDS[&*GUARDIAN], + guardian: *GUARDIAN, + token: chain_1_token, + nonce: DUMMY_CAMPAIGN.channel.nonce, + }; + let mut campaign_new_follower = DUMMY_CAMPAIGN.clone(); + campaign_new_follower.id = CampaignId::new(); + campaign_new_follower.channel = channel_new_follower; + campaign_new_follower.validators = + Validators::new((DUMMY_VALIDATOR_LEADER.clone(), dummy_follower_2.clone())); + campaign_new_follower.created = Utc.ymd(2021, 2, 1).and_hms(9, 0, 0); + + // Setting up a channel and campaign which use the new leader and follower on Ganache #1 + let channel_new_leader_and_follower = Channel { + leader: IDS[&*LEADER_2], + follower: IDS[&*GUARDIAN], + guardian: *GUARDIAN, + token: chain_1_token, + nonce: DUMMY_CAMPAIGN.channel.nonce, + }; + let mut campaign_new_leader_and_follower = DUMMY_CAMPAIGN.clone(); + campaign_new_leader_and_follower.id = CampaignId::new(); + campaign_new_leader_and_follower.channel = channel_new_leader_and_follower; + campaign_new_leader_and_follower.validators = + Validators::new((dummy_leader_2.clone(), dummy_follower_2.clone())); + campaign_new_leader_and_follower.created = Utc.ymd(2021, 2, 1).and_hms(10, 0, 0); + + insert_channel(&app.pool, DUMMY_CAMPAIGN.channel) + .await + .expect("Should insert dummy channel"); + insert_campaign(&app.pool, &DUMMY_CAMPAIGN) + .await + .expect("Should insert dummy campaign"); + insert_channel(&app.pool, channel_new_leader) + .await + .expect("Should insert dummy channel"); + insert_campaign(&app.pool, &campaign_new_leader) + .await + .expect("Should insert dummy campaign"); + insert_channel(&app.pool, channel_new_follower) + .await + .expect("Should insert dummy channel"); + insert_campaign(&app.pool, &campaign_new_follower) + .await + .expect("Should insert dummy campaign"); + insert_channel(&app.pool, channel_new_leader_and_follower) + .await + .expect("Should insert dummy channel"); + insert_campaign(&app.pool, &campaign_new_leader_and_follower) + .await + .expect("Should insert dummy campaign"); + + let mut campaign_other_creator = DUMMY_CAMPAIGN.clone(); + campaign_other_creator.id = CampaignId::new(); + campaign_other_creator.creator = *PUBLISHER_2; + campaign_other_creator.created = Utc.ymd(2021, 2, 1).and_hms(11, 0, 0); + + insert_campaign(&app.pool, &campaign_other_creator) + .await + .expect("Should insert dummy campaign"); + + let mut campaign_long_active_to = DUMMY_CAMPAIGN.clone(); + campaign_long_active_to.id = CampaignId::new(); + campaign_long_active_to.active.to = Utc.ymd(2101, 1, 30).and_hms(0, 0, 0); + campaign_long_active_to.created = Utc.ymd(2021, 2, 1).and_hms(12, 0, 0); + + insert_campaign(&app.pool, &campaign_long_active_to) + .await + .expect("Should insert dummy campaign"); + + let build_request = |query: CampaignListQuery| { + let query = serde_urlencoded::to_string(query).expect("should parse query"); + Request::builder() + .uri(format!("http://127.0.0.1/v5/campaign/list?{}", query)) + .body(Body::empty()) + .expect("Should build Request") + }; + + // Test for dummy leader + { + let query = CampaignListQuery { + page: 0, + active_to_ge: Utc::now(), + creator: None, + validator: Some(ValidatorParam::Leader(DUMMY_VALIDATOR_LEADER.id)), + }; + let res = campaign_list(build_request(query), &app) + .await + .expect("should get campaigns"); + let res = res_to_campaign_list_response(res).await; + + assert_eq!( + res.campaigns, + vec![DUMMY_CAMPAIGN.clone(), campaign_new_follower.clone()], + "First page of campaigns with dummy leader is correct" + ); + assert_eq!(res.pagination.total_pages, 2); + + let query = CampaignListQuery { + page: 1, + active_to_ge: Utc::now(), + creator: None, + validator: Some(ValidatorParam::Leader(DUMMY_VALIDATOR_LEADER.id)), + }; + let res = campaign_list(build_request(query), &app) + .await + .expect("should get campaigns"); + let res = res_to_campaign_list_response(res).await; + + assert_eq!( + res.campaigns, + vec![ + campaign_other_creator.clone(), + campaign_long_active_to.clone() + ], + "Second page of campaigns with dummy leader is correct" + ); + } + + // Test for dummy follower + { + let query = CampaignListQuery { + page: 0, + active_to_ge: Utc::now(), + creator: None, + validator: Some(ValidatorParam::Validator(DUMMY_VALIDATOR_FOLLOWER.id)), + }; + let res = campaign_list(build_request(query), &app) + .await + .expect("should get campaigns"); + let res = res_to_campaign_list_response(res).await; + + assert_eq!( + res.campaigns, + vec![DUMMY_CAMPAIGN.clone(), campaign_new_leader.clone()], + "First page of campaigns with dummy follower is correct" + ); + assert_eq!(res.pagination.total_pages, 2); + + let query = CampaignListQuery { + page: 1, + active_to_ge: Utc::now(), + creator: None, + validator: Some(ValidatorParam::Validator(DUMMY_VALIDATOR_FOLLOWER.id)), + }; + let res = campaign_list(build_request(query), &app) + .await + .expect("should get campaigns"); + let res = res_to_campaign_list_response(res).await; + + assert_eq!( + res.campaigns, + vec![ + campaign_other_creator.clone(), + campaign_long_active_to.clone() + ], + "Second page of campaigns with dummy follower is correct" + ); + } + + // Test for dummy leader 2 + { + let query = CampaignListQuery { + page: 0, + active_to_ge: Utc::now(), + creator: None, + validator: Some(ValidatorParam::Leader(dummy_leader_2.id)), + }; + let res = campaign_list(build_request(query), &app) + .await + .expect("should get campaigns"); + let res = res_to_campaign_list_response(res).await; + + assert_eq!( + res.campaigns, + vec![ + campaign_new_leader.clone(), + campaign_new_leader_and_follower.clone() + ], + "Campaigns with dummy leader 2 are correct" + ); + assert_eq!(res.pagination.total_pages, 1); + } + + // Test for dummy follower 2 + { + let query = CampaignListQuery { + page: 0, + active_to_ge: Utc::now(), + creator: None, + validator: Some(ValidatorParam::Validator(dummy_follower_2.id)), + }; + let res = campaign_list(build_request(query), &app) + .await + .expect("should get campaigns"); + let res = res_to_campaign_list_response(res).await; + + assert_eq!( + res.campaigns, + vec![ + campaign_new_follower.clone(), + campaign_new_leader_and_follower.clone() + ], + "Campaigns with dummy follower 2 are correct" + ); + assert_eq!(res.pagination.total_pages, 1); + } + + // Test for other creator + { + let query = CampaignListQuery { + page: 0, + active_to_ge: Utc::now(), + creator: Some(*PUBLISHER_2), + validator: None, + }; + let res = campaign_list(build_request(query), &app) + .await + .expect("should get campaigns"); + let res = res_to_campaign_list_response(res).await; + + assert_eq!( + res.campaigns, + vec![campaign_other_creator.clone()], + "The campaign with a different creator is retrieved correctly" + ); + assert_eq!(res.pagination.total_pages, 1); + } + + // Test for active_to + { + let query = CampaignListQuery { + page: 0, + active_to_ge: Utc.ymd(2101, 1, 1).and_hms(0, 0, 0), + creator: None, + validator: None, + }; + let res = campaign_list(build_request(query), &app) + .await + .expect("should get campaigns"); + let res = res_to_campaign_list_response(res).await; + + assert_eq!( + res.campaigns, + vec![campaign_long_active_to.clone()], + "The campaign with a longer active_to is retrieved correctly" + ); + assert_eq!(res.pagination.total_pages, 1); + } + } } diff --git a/validator_worker/src/sentry_interface.rs b/validator_worker/src/sentry_interface.rs index fd56557cd..f5f2b1191 100644 --- a/validator_worker/src/sentry_interface.rs +++ b/validator_worker/src/sentry_interface.rs @@ -28,7 +28,7 @@ pub type ChainsValidators = HashMap; pub type Validators = HashMap; pub type AuthToken = String; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Validator { /// Sentry API url pub url: ApiUrl, @@ -621,13 +621,17 @@ mod test { use super::*; use adapter::dummy::{Adapter, Dummy, Options}; use primitives::{ - config::{configuration, Environment}, - sentry::Pagination, + campaign::validators::Validators as CampaignValidators, + config::{configuration, Environment, GANACHE_CONFIG}, + sentry::{ + campaign_list::CampaignListResponse, channel_list::ChannelListResponse, Pagination, + }, test_util::{ discard_logger, ADVERTISER, ADVERTISER_2, CREATOR, DUMMY_CAMPAIGN, - DUMMY_VALIDATOR_LEADER, IDS, LEADER, PUBLISHER, PUBLISHER_2, + DUMMY_VALIDATOR_FOLLOWER, DUMMY_VALIDATOR_LEADER, FOLLOWER, GUARDIAN, IDS, LEADER, + LEADER_2, PUBLISHER, PUBLISHER_2, }, - UnifiedNum, + CampaignId, UnifiedNum, ValidatorDesc, }; use std::str::FromStr; use wiremock::{ @@ -780,4 +784,262 @@ mod test { // There should be no remaining elements assert_eq!(res.len(), 0) } + + #[tokio::test] + async fn test_collecting_and_channels_and_campaigns() { + let server = MockServer::start().await; + let chain_1_token = GANACHE_CONFIG + .chains + .get("Ganache #1") + .unwrap() + .tokens + .get("Mocked TOKEN 2") + .unwrap() + .address; + + // Setting up new leader and a channel and campaign which use it on Ganache #1337 + let dummy_leader_2 = ValidatorDesc { + id: IDS[&LEADER_2], + url: "http://tom.adex.network".to_string(), + fee: 200.into(), + fee_addr: None, + }; + let channel_new_leader = Channel { + leader: IDS[&*LEADER_2], + follower: IDS[&*FOLLOWER], + guardian: *GUARDIAN, + token: DUMMY_CAMPAIGN.channel.token, + nonce: DUMMY_CAMPAIGN.channel.nonce, + }; + let mut campaign_new_leader = DUMMY_CAMPAIGN.clone(); + campaign_new_leader.id = CampaignId::new(); + campaign_new_leader.channel = channel_new_leader; + campaign_new_leader.validators = + CampaignValidators::new((dummy_leader_2.clone(), DUMMY_VALIDATOR_FOLLOWER.clone())); + + // Setting up new follower and a channel and campaign which use it on Ganache #1 + let dummy_follower_2 = ValidatorDesc { + id: IDS[&GUARDIAN], + url: "http://jerry.adex.network".to_string(), + fee: 300.into(), + fee_addr: None, + }; + let channel_new_follower = Channel { + leader: IDS[&*LEADER], + follower: IDS[&*GUARDIAN], + guardian: *GUARDIAN, + token: chain_1_token, + nonce: DUMMY_CAMPAIGN.channel.nonce, + }; + let mut campaign_new_follower = DUMMY_CAMPAIGN.clone(); + campaign_new_follower.id = CampaignId::new(); + campaign_new_follower.channel = channel_new_follower; + campaign_new_follower.validators = + CampaignValidators::new((DUMMY_VALIDATOR_LEADER.clone(), dummy_follower_2.clone())); + + // Setting up a channel and campaign which use the new leader and follower on Ganache #1 + let channel_new_leader_and_follower = Channel { + leader: IDS[&*LEADER_2], + follower: IDS[&*GUARDIAN], + guardian: *GUARDIAN, + token: chain_1_token, + nonce: DUMMY_CAMPAIGN.channel.nonce, + }; + let mut campaign_new_leader_and_follower = DUMMY_CAMPAIGN.clone(); + campaign_new_leader_and_follower.id = CampaignId::new(); + campaign_new_leader_and_follower.channel = channel_new_leader_and_follower; + campaign_new_leader_and_follower.validators = + CampaignValidators::new((dummy_leader_2.clone(), dummy_follower_2.clone())); + + // Initializing SentryApi instance + let sentry_url = ApiUrl::from_str(&server.uri()).expect("Should parse"); + + let mut config = configuration(Environment::Development, None).expect("Should get Config"); + config.spendable_find_limit = 2; + let adapter = Adapter::with_unlocked(Dummy::init(Options { + dummy_identity: IDS[&LEADER], + dummy_auth_tokens: vec![(IDS[&LEADER].to_address(), "AUTH_Leader".into())] + .into_iter() + .collect(), + })); + let logger = discard_logger(); + + let sentry = SentryApi::new(adapter, logger, config.clone(), sentry_url.clone()) + .expect("Should build sentry"); + + // Getting Wiremock to return the campaigns when called + let first_page_response = CampaignListResponse { + campaigns: vec![DUMMY_CAMPAIGN.clone(), campaign_new_leader.clone()], + pagination: Pagination { + page: 0, + total_pages: 2, + }, + }; + + let second_page_response = CampaignListResponse { + campaigns: vec![ + campaign_new_follower.clone(), + campaign_new_leader_and_follower.clone(), + ], + pagination: Pagination { + page: 1, + total_pages: 2, + }, + }; + + Mock::given(method("GET")) + .and(path("/v5/campaign/list")) + .and(query_param("page", "0")) + .respond_with(ResponseTemplate::new(200).set_body_json(&first_page_response)) + .mount(&server) + .await; + + Mock::given(method("GET")) + .and(path("/v5/campaign/list")) + .and(query_param("page", "1")) + .respond_with(ResponseTemplate::new(200).set_body_json(&second_page_response)) + .mount(&server) + .await; + + // Testing collect_channels() + { + let mut expected_channels: HashSet> = HashSet::new(); + expected_channels.insert( + config + .find_chain_of(DUMMY_CAMPAIGN.channel.token) + .expect("Should find channel token in config") + .with_channel(DUMMY_CAMPAIGN.channel), + ); + expected_channels.insert( + config + .find_chain_of(channel_new_leader.token) + .expect("Should find channel token in config") + .with_channel(channel_new_leader), + ); + expected_channels.insert( + config + .find_chain_of(channel_new_follower.token) + .expect("Should find channel token in config") + .with_channel(channel_new_follower), + ); + expected_channels.insert( + config + .find_chain_of(channel_new_leader_and_follower.token) + .expect("Should find channel token in config") + .with_channel(channel_new_leader_and_follower), + ); + + let (channels, chains_validators) = sentry + .collect_channels() + .await + .expect("Should collect channels"); + assert_eq!(channels, expected_channels, "Correct channels are returned"); + + let chains_validators_1337 = chains_validators + .get(&ChainId::from(1337)) + .expect("There should be validators for #1337 chain"); + assert!( + chains_validators_1337.contains_key(&DUMMY_VALIDATOR_LEADER.id), + "Dummy leader is included" + ); + assert!( + chains_validators_1337.contains_key(&DUMMY_VALIDATOR_FOLLOWER.id), + "Dummy follower is included" + ); + assert!( + chains_validators_1337.contains_key(&dummy_leader_2.id), + "Dummy leader 2 is included" + ); + assert_eq!( + chains_validators_1337.keys().len(), + 3, + "There are no extra validators returned" + ); + + let chains_validators_1 = chains_validators + .get(&ChainId::from(1)) + .expect("There should be validators for #1 chain"); + assert!( + chains_validators_1.contains_key(&DUMMY_VALIDATOR_LEADER.id), + "Dummy leader is returned" + ); + assert!( + chains_validators_1.contains_key(&dummy_follower_2.id), + "Dummy follower 2 is returned" + ); + assert!( + chains_validators_1.contains_key(&dummy_leader_2.id), + "Dummy leader 2 is returned" + ); + assert_eq!( + chains_validators_1.keys().len(), + 3, + "There are no extra validators returned" + ); + } + // Calls all_campaigns() to see if all campaigns are returned + // We test for query parameters in campaign_list() tests + { + let all_campaigns = vec![ + DUMMY_CAMPAIGN.clone(), + campaign_new_leader.clone(), + campaign_new_follower.clone(), + campaign_new_leader_and_follower.clone(), + ]; + let res = campaigns::all_campaigns(sentry.client.clone(), &sentry_url.clone(), None) + .await + .expect("Should get all campaigns"); + assert_eq!(res, all_campaigns, "All campaigns are present"); + } + // test all_channels + { + // Get Wiremock to return the channels + let first_page_response = ChannelListResponse { + channels: vec![DUMMY_CAMPAIGN.channel.clone(), channel_new_leader], + pagination: Pagination { + page: 0, + total_pages: 2, + }, + }; + + let second_page_response = ChannelListResponse { + channels: vec![ + channel_new_follower.clone(), + channel_new_leader_and_follower.clone(), + ], + pagination: Pagination { + page: 1, + total_pages: 2, + }, + }; + + Mock::given(method("GET")) + .and(path("/v5/channel/list")) + .and(query_param("page", "0")) + .respond_with(ResponseTemplate::new(200).set_body_json(&first_page_response)) + .mount(&server) + .await; + + Mock::given(method("GET")) + .and(path("/v5/channel/list")) + .and(query_param("page", "1")) + .respond_with(ResponseTemplate::new(200).set_body_json(&second_page_response)) + .mount(&server) + .await; + let all_channels = vec![ + DUMMY_CAMPAIGN.channel, + channel_new_leader, + channel_new_follower, + channel_new_leader_and_follower, + ]; + let res = channels::all_channels( + sentry.client.clone(), + &sentry_url.clone(), + DUMMY_VALIDATOR_LEADER.id, + ) + .await + .expect("Should get channels"); + assert_eq!(all_channels, res, "All channels are present"); + } + } }