commit fe2d3cb9080ca9c9548591e2829a7e8faaeacc6a
parent dc223fd8aa6b1c27be78484ceb7a8efbb1c42cef
Author: Antoine A <>
Date: Thu, 26 Mar 2026 18:48:00 +0100
common: improve routine tests
Diffstat:
5 files changed, 207 insertions(+), 153 deletions(-)
diff --git a/common/taler-api/tests/api.rs b/common/taler-api/tests/api.rs
@@ -84,13 +84,7 @@ async fn outgoing_history() {
routine_pagination::<OutgoingHistory, _>(
&server,
"/taler-wire-gateway/history/outgoing",
- |it| {
- it.outgoing_transactions
- .into_iter()
- .map(|it| *it.row_id as i64)
- .collect()
- },
- async |server, i| {
+ async |i| {
server
.post("/taler-wire-gateway/transfer")
.json(&json!({
diff --git a/common/taler-test-utils/src/routine.rs b/common/taler-test-utils/src/routine.rs
@@ -21,9 +21,9 @@ use std::{
};
use aws_lc_rs::signature::{Ed25519KeyPair, KeyPair as _};
-use axum::Router;
+use axum::{Router, http::StatusCode};
use jiff::{SignedDuration, Timestamp};
-use serde::{Deserialize, de::DeserializeOwned};
+use serde::de::DeserializeOwned;
use taler_api::{
crypto::{check_eddsa_signature, eddsa_sign},
subject::fmt_in_subject,
@@ -34,8 +34,8 @@ use taler_common::{
api_revenue::RevenueIncomingHistory,
api_transfer::{RegistrationResponse, TransferSubject, TransferType},
api_wire::{
- IncomingBankTransaction, IncomingHistory, TransferList, TransferRequest, TransferResponse,
- TransferState, TransferStatus,
+ IncomingBankTransaction, IncomingHistory, OutgoingHistory, TransferList, TransferRequest,
+ TransferResponse, TransferState, TransferStatus,
},
db::IncomingType,
error_code::ErrorCode,
@@ -48,11 +48,60 @@ use crate::{
server::{TestResponse, TestServer as _},
};
-pub async fn routine_pagination<'a, T: DeserializeOwned, F: Future<Output = ()>>(
- server: &'a Router,
+pub trait Page: DeserializeOwned {
+ fn ids(&self) -> Vec<i64>;
+}
+
+impl Page for IncomingHistory {
+ fn ids(&self) -> Vec<i64> {
+ self.incoming_transactions
+ .iter()
+ .map(|it| match it {
+ IncomingBankTransaction::Reserve { row_id, .. }
+ | IncomingBankTransaction::Wad { row_id, .. }
+ | IncomingBankTransaction::Kyc { row_id, .. } => **row_id as i64,
+ })
+ .collect()
+ }
+}
+
+impl Page for OutgoingHistory {
+ fn ids(&self) -> Vec<i64> {
+ self.outgoing_transactions
+ .iter()
+ .map(|it| *it.row_id as i64)
+ .collect()
+ }
+}
+
+impl Page for RevenueIncomingHistory {
+ fn ids(&self) -> Vec<i64> {
+ self.incoming_transactions
+ .iter()
+ .map(|it| *it.row_id as i64)
+ .collect()
+ }
+}
+
+impl Page for TransferList {
+ fn ids(&self) -> Vec<i64> {
+ self.transfers.iter().map(|it| *it.row_id as i64).collect()
+ }
+}
+
+pub async fn latest_id<T: Page>(router: &Router, url: &str) -> i64 {
+ let res = router.get(&format!("{url}?limit=-1")).await;
+ if res.status == StatusCode::NO_CONTENT {
+ 0
+ } else {
+ res.assert_ids::<T>(1)[0]
+ }
+}
+
+pub async fn routine_pagination<T: Page, F: Future<Output = ()>>(
+ server: &Router,
url: &str,
- ids: fn(T) -> Vec<i64>,
- mut register: impl FnMut(&'a Router, usize) -> F,
+ mut register: impl FnMut(usize) -> F,
) {
// Check supported
if !server.get(url).await.is_implemented() {
@@ -61,14 +110,16 @@ pub async fn routine_pagination<'a, T: DeserializeOwned, F: Future<Output = ()>>
// Check history is following specs
let assert_history = async |args: &str, size: usize| {
- let resp = server.get(&format!("{url}?{args}")).await;
- assert_history_ids(&resp, ids, size)
+ server
+ .get(&format!("{url}?{args}"))
+ .await
+ .assert_ids::<T>(size)
};
// Get latest registered id
- let latest_id = async || assert_history("limit=-1", 1).await[0];
+ let latest_id = async || latest_id::<T>(server, url).await;
for i in 0..20 {
- register(server, i).await;
+ register(i).await;
}
let id = latest_id().await;
@@ -85,7 +136,7 @@ pub async fn routine_pagination<'a, T: DeserializeOwned, F: Future<Output = ()>>
assert_history(&format!("limit=-10&{}", id - 4), 10).await;
}
-pub async fn assert_time<R: Debug>(range: std::ops::Range<u128>, task: impl Future<Output = R>) {
+async fn assert_time<R: Debug>(range: std::ops::Range<u128>, task: impl Future<Output = R>) {
let start = Instant::now();
task.await;
let elapsed = start.elapsed().as_millis();
@@ -94,26 +145,75 @@ pub async fn assert_time<R: Debug>(range: std::ops::Range<u128>, task: impl Futu
}
}
-pub async fn routine_history<
- 'a,
- T: DeserializeOwned,
- FR: Future<Output = ()>,
- FI: Future<Output = ()>,
->(
- server: &'a Router,
+async fn check_history_trigger<T: Page, F: Future<Output = ()>>(
+ server: &Router,
+ url: &str,
+ lambda: impl FnOnce() -> F,
+) {
+ // Check history is following specs
+ macro_rules! assert_history {
+ ($args:expr, $size:expr) => {
+ async {
+ server
+ .get(&format!("{url}?{}", $args))
+ .await
+ .assert_ids::<T>($size)
+ }
+ };
+ }
+ // Get latest registered id
+ let latest_id = latest_id::<T>(server, url).await;
+ tokio::join!(
+ // Check polling succeed
+ assert_time(
+ 100..400,
+ assert_history!(
+ format_args!("limit=2&offset={latest_id}&timeout_ms=1000"),
+ 1
+ )
+ ),
+ assert_time(
+ 200..500,
+ assert_history!(
+ format_args!("limit=1&offset={}&timeout_ms=200", latest_id + 3),
+ 0
+ )
+ ),
+ async {
+ sleep(Duration::from_millis(100)).await;
+ lambda().await
+ }
+ );
+}
+
+async fn check_history_in_trigger<F: Future<Output = ()>>(
+ server: &Router,
+ lambda: impl FnOnce() -> F,
+) {
+ check_history_trigger::<IncomingHistory, _>(
+ server,
+ "/taler-wire-gateway/history/incoming",
+ lambda,
+ )
+ .await;
+}
+
+pub async fn routine_history<T: Page, FR: Future<Output = ()>, FI: Future<Output = ()>>(
+ server: &Router,
url: &str,
- ids: fn(T) -> Vec<i64>,
nb_register: usize,
- mut register: impl FnMut(&'a Router, usize) -> FR,
+ mut register: impl FnMut(usize) -> FR,
nb_ignore: usize,
- mut ignore: impl FnMut(&'a Router, usize) -> FI,
+ mut ignore: impl FnMut(usize) -> FI,
) {
// Check history is following specs
macro_rules! assert_history {
($args:expr, $size:expr) => {
async {
- let resp = server.get(&format!("{url}?{}", $args)).await;
- assert_history_ids(&resp, ids, $size)
+ server
+ .get(&format!("{url}?{}", $args))
+ .await
+ .assert_ids::<T>($size)
}
};
}
@@ -127,10 +227,10 @@ pub async fn routine_history<
let mut ignore_iter = (0..nb_ignore).peekable();
while register_iter.peek().is_some() || ignore_iter.peek().is_some() {
if let Some(idx) = register_iter.next() {
- register(server, idx).await
+ register(idx).await
}
if let Some(idx) = ignore_iter.next() {
- ignore(server, idx).await
+ ignore(idx).await
}
}
let nb_total = nb_register + nb_ignore;
@@ -179,7 +279,7 @@ pub async fn routine_history<
),
async {
sleep(Duration::from_millis(100)).await;
- register(server, 0).await
+ register(0).await
}
);
@@ -194,7 +294,7 @@ pub async fn routine_history<
),
async {
sleep(Duration::from_millis(100)).await;
- register(server, i).await
+ register(i).await
}
);
}
@@ -210,60 +310,55 @@ pub async fn routine_history<
async {
sleep(Duration::from_millis(100)).await;
for i in 0..nb_ignore {
- ignore(server, i).await
+ ignore(i).await
}
}
);
- routine_pagination(server, url, ids, register).await;
+ routine_pagination::<T, _>(server, url, register).await;
}
-#[track_caller]
-fn assert_history_ids<'de, T: Deserialize<'de>>(
- resp: &'de TestResponse,
- ids: impl Fn(T) -> Vec<i64>,
- size: usize,
-) -> Vec<i64> {
- if size == 0 {
- resp.assert_no_content();
- return vec![];
- }
- let body = resp.assert_ok_json::<T>();
- let history: Vec<_> = ids(body);
- let params = resp.query::<PageParams>().check(1024).unwrap();
-
- // testing the size is like expected
- assert_eq!(size, history.len(), "bad history length: {history:?}");
- if params.limit < 0 {
- // testing that the first id is at most the 'offset' query param.
- assert!(
- params
- .offset
- .map(|offset| history[0] <= offset)
- .unwrap_or(true),
- "bad history offset: {params:?} {history:?}"
- );
- // testing that the id decreases.
- assert!(
- history.as_slice().is_sorted_by(|a, b| a > b),
- "bad history order: {history:?}"
- )
- } else {
- // testing that the first id is at least the 'offset' query param.
- assert!(
- params
- .offset
- .map(|offset| history[0] >= offset)
- .unwrap_or(true),
- "bad history offset: {params:?} {history:?}"
- );
- // testing that the id increases.
- assert!(
- history.as_slice().is_sorted(),
- "bad history order: {history:?}"
- )
+impl TestResponse {
+ #[track_caller]
+ fn assert_ids<T: Page>(&self, size: usize) -> Vec<i64> {
+ if size == 0 {
+ self.assert_no_content();
+ return vec![];
+ }
+ let body = self.assert_ok_json::<T>();
+ let page = body.ids();
+ let params = self.query::<PageParams>().check(1024).unwrap();
+
+ // testing the size is like expected
+ assert_eq!(size, page.len(), "bad page length: {page:?}");
+ if params.limit < 0 {
+ // testing that the first id is at most the 'offset' query param.
+ assert!(
+ params
+ .offset
+ .map(|offset| page[0] <= offset)
+ .unwrap_or(true),
+ "bad page offset: {params:?} {page:?}"
+ );
+ // testing that the id decreases.
+ assert!(
+ page.as_slice().is_sorted_by(|a, b| a > b),
+ "bad page order: {page:?}"
+ )
+ } else {
+ // testing that the first id is at least the 'offset' query param.
+ assert!(
+ params
+ .offset
+ .map(|offset| page[0] >= offset)
+ .unwrap_or(true),
+ "bad page offset: {params:?} {page:?}"
+ );
+ // testing that the id increases.
+ assert!(page.as_slice().is_sorted(), "bad page order: {page:?}")
+ }
+ page
}
- history
}
// Get currency from config
@@ -452,29 +547,19 @@ pub async fn transfer_routine(
}
// Pagination test
- routine_pagination::<TransferList, _>(
- server,
- "/taler-wire-gateway/transfers",
- |it| {
- it.transfers
- .into_iter()
- .map(|it| *it.row_id as i64)
- .collect()
- },
- async |server, i| {
- server
- .post("/taler-wire-gateway/transfer")
- .json(&json!({
- "request_uid": HashCode::rand(),
- "amount": amount(format!("{currency}:0.0{i}")),
- "exchange_base_url": url("http://exchange.taler"),
- "wtid": ShortHashCode::rand(),
- "credit_account": credit_account,
- }))
- .await
- .assert_ok_json::<TransferResponse>();
- },
- )
+ routine_pagination::<TransferList, _>(server, "/taler-wire-gateway/transfers", async |i| {
+ server
+ .post("/taler-wire-gateway/transfer")
+ .json(&json!({
+ "request_uid": HashCode::rand(),
+ "amount": amount(format!("{currency}:0.0{i}")),
+ "exchange_base_url": url("http://exchange.taler"),
+ "wtid": ShortHashCode::rand(),
+ "credit_account": credit_account,
+ }))
+ .await
+ .assert_ok_json::<TransferResponse>();
+ })
.await;
}
}
@@ -557,17 +642,11 @@ async fn add_incoming_routine(
pub async fn revenue_routine(server: &Router, debit_acount: &PaytoURI, kyc: bool) {
let currency = &get_wire_currency(server).await;
- routine_history(
+ routine_history::<RevenueIncomingHistory, _, _>(
server,
"/taler-revenue/history",
- |it: RevenueIncomingHistory| {
- it.incoming_transactions
- .into_iter()
- .map(|it| *it.row_id as i64)
- .collect()
- },
2,
- async |server, i| {
+ async |i| {
if i % 2 == 0 || !kyc {
server
.post("/taler-wire-gateway/admin/add-incoming")
@@ -591,7 +670,7 @@ pub async fn revenue_routine(server: &Router, debit_acount: &PaytoURI, kyc: bool
}
},
0,
- async |_, _| {},
+ async |_| {},
)
.await;
}
@@ -602,21 +681,11 @@ pub async fn admin_add_incoming_routine(server: &Router, debit_acount: &PaytoURI
// History
// TODO check non taler some are ignored
- routine_history(
+ routine_history::<IncomingHistory, _, _>(
server,
"/taler-wire-gateway/history/incoming",
- |it: IncomingHistory| {
- it.incoming_transactions
- .into_iter()
- .map(|it| match it {
- IncomingBankTransaction::Reserve { row_id, .. }
- | IncomingBankTransaction::Wad { row_id, .. }
- | IncomingBankTransaction::Kyc { row_id, .. } => *row_id as i64,
- })
- .collect()
- },
2,
- async |server, i| {
+ async |i| {
if i % 2 == 0 || !kyc {
server
.post("/taler-wire-gateway/admin/add-incoming")
@@ -640,7 +709,7 @@ pub async fn admin_add_incoming_routine(server: &Router, debit_acount: &PaytoURI
}
},
0,
- async |_, _| {},
+ async |_| {},
)
.await;
// Add incoming reserve
@@ -787,7 +856,7 @@ pub async fn registration_routine<F1: Future<Output = Vec<Status>>, F2: Future<O
}))
.await
.assert_ok_json::<RegistrationResponse>();
- register(&auth_pub1).await;
+ check_history_in_trigger(server, async || register(&auth_pub1).await).await;
check_in(&[Reserve(acc_pub1.clone())]).await;
register(&auth_pub1).await;
check_in(&[Reserve(acc_pub1.clone()), Bounced]).await;
@@ -859,15 +928,18 @@ pub async fn registration_routine<F1: Future<Output = Vec<Status>>, F2: Future<O
}))
.await
.assert_ok_json::<RegistrationResponse>();
- server
- .post("/taler-wire-transfer-gateway/registration")
- .json(&json!(req + {
- "account_pub": acc_pub4,
- "authorization_sig": eddsa_sign(&key_pair1, acc_pub4.as_ref()),
- "recurrent": true
- }))
- .await
- .assert_ok_json::<RegistrationResponse>();
+ check_history_in_trigger(server, async || {
+ server
+ .post("/taler-wire-transfer-gateway/registration")
+ .json(&json!(req + {
+ "account_pub": acc_pub4,
+ "authorization_sig": eddsa_sign(&key_pair1, acc_pub4.as_ref()),
+ "recurrent": true
+ }))
+ .await
+ .assert_ok_json::<RegistrationResponse>();
+ })
+ .await;
check_in(&[
Reserve(acc_pub1.clone()),
Bounced,
diff --git a/common/taler-test-utils/src/server.rs b/common/taler-test-utils/src/server.rs
@@ -158,7 +158,7 @@ pub struct TestResponse {
bytes: Bytes,
method: Method,
uri: Uri,
- status: StatusCode,
+ pub status: StatusCode,
}
impl TestResponse {
diff --git a/taler-cyclos/src/api.rs b/taler-cyclos/src/api.rs
@@ -390,13 +390,7 @@ mod test {
routine_pagination::<OutgoingHistory, _>(
&server,
"/taler-wire-gateway/history/outgoing",
- |it| {
- it.outgoing_transactions
- .into_iter()
- .map(|it| *it.row_id as i64)
- .collect()
- },
- async |_, i| {
+ async |i| {
db::register_tx_out(
&mut pool.acquire().await.unwrap(),
&db::TxOut {
diff --git a/taler-magnet-bank/src/api.rs b/taler-magnet-bank/src/api.rs
@@ -364,13 +364,7 @@ mod test {
routine_pagination::<OutgoingHistory, _>(
&server,
"/taler-wire-gateway/history/outgoing",
- |it| {
- it.outgoing_transactions
- .into_iter()
- .map(|it| *it.row_id as i64)
- .collect()
- },
- async |_, i| {
+ async |i| {
let mut conn = pool.acquire().await.unwrap();
let now = Zoned::now().date();
db::register_tx_out(