Skip to content

Commit db78289

Browse files
authored
Merge pull request #451 from AmbireTech/remaining_spender_all_tests
Remaining spender all tests
2 parents ec0d6a5 + 850d811 commit db78289

File tree

6 files changed

+227
-16
lines changed

6 files changed

+227
-16
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

primitives/src/spender.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use crate::{Address, Channel, Deposit, UnifiedNum};
22
use serde::{Deserialize, Serialize};
33

4-
#[derive(Serialize, Deserialize, Debug, Clone)]
4+
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
55
#[serde(rename_all = "camelCase")]
66
pub struct SpenderLeaf {
77
pub total_spent: UnifiedNum,
88
// merkle_proof: [u8; 32], // TODO
99
}
1010

11-
#[derive(Debug, Clone, Serialize, Deserialize)]
11+
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
1212
pub struct Spender {
1313
pub total_deposited: UnifiedNum,
1414
pub spender_leaf: Option<SpenderLeaf>,

sentry/src/db.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -334,13 +334,19 @@ pub mod tests_postgres {
334334
// set the database in the configuration of the inside Pool (used for tests)
335335
config.dbname(&database.name);
336336

337-
let manager =
338-
deadpool_postgres::Manager::from_config(config, NoTls, self.manager_config.clone());
339-
337+
let manager = deadpool_postgres::Manager::from_config(
338+
config,
339+
NoTls,
340+
self.manager_config.clone(),
341+
);
342+
340343
deadpool_postgres::Pool::new(manager, 15)
341344
};
342345

343-
let result = database.pool.get().await?
346+
let result = database
347+
.pool
348+
.get()
349+
.await?
344350
.simple_query(queries)
345351
.await
346352
.map_err(PoolError::Backend)

test_harness/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,9 @@ pub mod run {
730730
let postgres = postgres_connection(42, postgres_config).await;
731731
let mut redis = redis_connection(app_config.redis_url).await?;
732732

733-
Manager::flush_db(&mut redis).await.expect("Should flush redis database");
733+
Manager::flush_db(&mut redis)
734+
.await
735+
.expect("Should flush redis database");
734736

735737
let campaign_remaining = CampaignRemaining::new(redis.clone());
736738

validator_worker/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,6 @@ serde_urlencoded = "0.7"
3939
toml = "0.5"
4040
# CLI
4141
clap = "^2.33"
42+
43+
[dev-dependencies]
44+
wiremock = "0.5"

validator_worker/src/sentry_interface.rs

Lines changed: 208 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
use std::{collections::HashMap, time::Duration};
22

33
use chrono::{DateTime, Utc};
4-
use futures::future::{join_all, TryFutureExt};
4+
use futures::future::{join_all, try_join_all, TryFutureExt};
55
use reqwest::{Client, Method};
66
use slog::Logger;
77

88
use primitives::{
99
adapter::Adapter,
1010
balances::{CheckedState, UncheckedState},
1111
sentry::{
12-
AccountingResponse, EventAggregateResponse, LastApprovedResponse, SuccessResponse,
13-
ValidatorMessageResponse,
12+
AccountingResponse, AllSpendersResponse, EventAggregateResponse, LastApprovedResponse,
13+
SuccessResponse, ValidatorMessageResponse,
1414
},
1515
spender::Spender,
1616
util::ApiUrl,
@@ -161,28 +161,51 @@ impl<A: Adapter + 'static> SentryApi<A> {
161161
.map_err(Error::Request)
162162
}
163163

164-
// TODO: Pagination & use of `AllSpendersResponse`
165-
pub async fn get_all_spenders(
164+
/// page always starts from 0
165+
pub async fn get_spenders_page(
166166
&self,
167-
channel: ChannelId,
168-
) -> Result<HashMap<Address, Spender>, Error> {
167+
channel: &ChannelId,
168+
page: u64,
169+
) -> Result<AllSpendersResponse, Error> {
169170
let url = self
170171
.whoami
171172
.url
172-
.join(&format!("v5/channel/{}/spender/all", channel))
173+
.join(&format!("v5/channel/{}/spender/all?page={}", channel, page))
173174
.expect("Should not error when creating endpoint");
174175

175176
self.client
176177
.get(url)
177178
.bearer_auth(&self.whoami.token)
178179
.send()
179180
.await?
180-
// TODO: Should be `AllSpendersResponse` and should have pagination!
181181
.json()
182182
.map_err(Error::Request)
183183
.await
184184
}
185185

186+
pub async fn get_all_spenders(
187+
&self,
188+
channel: ChannelId,
189+
) -> Result<HashMap<Address, Spender>, Error> {
190+
let first_page = self.get_spenders_page(&channel, 0).await?;
191+
192+
if first_page.pagination.total_pages < 2 {
193+
Ok(first_page.spenders)
194+
} else {
195+
let all: Vec<AllSpendersResponse> = try_join_all(
196+
(1..first_page.pagination.total_pages).map(|i| self.get_spenders_page(&channel, i)),
197+
)
198+
.await?;
199+
200+
let result_all: HashMap<Address, Spender> = std::iter::once(first_page)
201+
.chain(all.into_iter())
202+
.flat_map(|p| p.spenders)
203+
.collect();
204+
205+
Ok(result_all)
206+
}
207+
}
208+
186209
/// Get the accounting from Sentry
187210
/// `Balances` should always be in `CheckedState`
188211
pub async fn get_accounting(
@@ -378,3 +401,179 @@ pub mod campaigns {
378401
client.get(endpoint).send().await?.json().await
379402
}
380403
}
404+
405+
#[cfg(test)]
406+
mod test {
407+
use super::*;
408+
use adapter::DummyAdapter;
409+
use primitives::{
410+
adapter::DummyAdapterOptions,
411+
config::{configuration, Environment},
412+
sentry::Pagination,
413+
util::tests::{
414+
discard_logger,
415+
prep_db::{
416+
ADDRESSES, DUMMY_CAMPAIGN, DUMMY_VALIDATOR_LEADER, IDS,
417+
},
418+
},
419+
UnifiedNum,
420+
};
421+
use std::str::FromStr;
422+
use wiremock::{
423+
matchers::{method, path, query_param},
424+
Mock, MockServer, ResponseTemplate,
425+
};
426+
427+
#[tokio::test]
428+
async fn test_get_all_spenders() {
429+
let server = MockServer::start().await;
430+
let test_spender = Spender {
431+
total_deposited: UnifiedNum::from(100_000_000),
432+
spender_leaf: None,
433+
};
434+
let mut all_spenders = HashMap::new();
435+
all_spenders.insert(ADDRESSES["user"], test_spender.clone());
436+
all_spenders.insert(ADDRESSES["publisher"], test_spender.clone());
437+
all_spenders.insert(ADDRESSES["publisher2"], test_spender.clone());
438+
all_spenders.insert(ADDRESSES["creator"], test_spender.clone());
439+
all_spenders.insert(ADDRESSES["tester"], test_spender.clone());
440+
441+
let first_page_response = AllSpendersResponse {
442+
spenders: vec![
443+
(
444+
ADDRESSES["user"],
445+
all_spenders.get(&ADDRESSES["user"]).unwrap().to_owned(),
446+
),
447+
(
448+
ADDRESSES["publisher"],
449+
all_spenders
450+
.get(&ADDRESSES["publisher"])
451+
.unwrap()
452+
.to_owned(),
453+
),
454+
]
455+
.into_iter()
456+
.collect(),
457+
pagination: Pagination {
458+
page: 0,
459+
total_pages: 3,
460+
},
461+
};
462+
463+
let second_page_response = AllSpendersResponse {
464+
spenders: vec![
465+
(
466+
ADDRESSES["publisher2"],
467+
all_spenders
468+
.get(&ADDRESSES["publisher2"])
469+
.unwrap()
470+
.to_owned(),
471+
),
472+
(
473+
ADDRESSES["creator"],
474+
all_spenders.get(&ADDRESSES["creator"]).unwrap().to_owned(),
475+
),
476+
]
477+
.into_iter()
478+
.collect(),
479+
pagination: Pagination {
480+
page: 1,
481+
total_pages: 3,
482+
},
483+
};
484+
485+
let third_page_response = AllSpendersResponse {
486+
spenders: vec![(
487+
ADDRESSES["tester"],
488+
all_spenders.get(&ADDRESSES["tester"]).unwrap().to_owned(),
489+
)]
490+
.into_iter()
491+
.collect(),
492+
pagination: Pagination {
493+
page: 2,
494+
total_pages: 3,
495+
},
496+
};
497+
498+
Mock::given(method("GET"))
499+
.and(path(format!(
500+
"/v5/channel/{}/spender/all",
501+
DUMMY_CAMPAIGN.channel.id()
502+
)))
503+
.and(query_param("page", "0"))
504+
.respond_with(ResponseTemplate::new(200).set_body_json(&first_page_response))
505+
.mount(&server)
506+
.await;
507+
508+
Mock::given(method("GET"))
509+
.and(path(format!(
510+
"/v5/channel/{}/spender/all",
511+
DUMMY_CAMPAIGN.channel.id()
512+
)))
513+
.and(query_param("page", "1"))
514+
.respond_with(ResponseTemplate::new(200).set_body_json(&second_page_response))
515+
.mount(&server)
516+
.await;
517+
518+
Mock::given(method("GET"))
519+
.and(path(format!(
520+
"/v5/channel/{}/spender/all",
521+
DUMMY_CAMPAIGN.channel.id()
522+
)))
523+
.and(query_param("page", "2"))
524+
.respond_with(ResponseTemplate::new(200).set_body_json(&third_page_response))
525+
.mount(&server)
526+
.await;
527+
528+
let mut validators = Validators::new();
529+
validators.insert(
530+
DUMMY_VALIDATOR_LEADER.id,
531+
Validator {
532+
url: ApiUrl::from_str(&server.uri()).expect("Should parse"),
533+
token: AuthToken::default(),
534+
},
535+
);
536+
let mut config = configuration(Environment::Development, None).expect("Should get Config");
537+
config.spendable_find_limit = 2;
538+
539+
let adapter = DummyAdapter::init(
540+
DummyAdapterOptions {
541+
dummy_identity: IDS["leader"],
542+
dummy_auth: Default::default(),
543+
dummy_auth_tokens: Default::default(),
544+
},
545+
&config,
546+
);
547+
let logger = discard_logger();
548+
549+
let sentry =
550+
SentryApi::init(adapter, logger, config, validators).expect("Should build sentry");
551+
552+
let mut res = sentry
553+
.get_all_spenders(DUMMY_CAMPAIGN.channel.id())
554+
.await
555+
.expect("should get response");
556+
557+
// Checks for page 1
558+
let res_user = res.remove(&ADDRESSES["user"]);
559+
let res_publisher = res.remove(&ADDRESSES["publisher"]);
560+
assert!(res_user.is_some() && res_publisher.is_some());
561+
assert_eq!(res_user.unwrap(), test_spender);
562+
assert_eq!(res_publisher.unwrap(), test_spender);
563+
564+
// Checks for page 2
565+
let res_publisher2 = res.remove(&ADDRESSES["publisher2"]);
566+
let res_creator = res.remove(&ADDRESSES["creator"]);
567+
assert!(res_publisher2.is_some() && res_creator.is_some());
568+
assert_eq!(res_publisher2.unwrap(), test_spender);
569+
assert_eq!(res_creator.unwrap(), test_spender);
570+
571+
// Checks for page 3
572+
let res_tester = res.remove(&ADDRESSES["tester"]);
573+
assert!(res_tester.is_some());
574+
assert_eq!(res_tester.unwrap(), test_spender);
575+
576+
// There should be no remaining elements
577+
assert_eq!(res.len(), 0)
578+
}
579+
}

0 commit comments

Comments
 (0)