test.rs (7993B)
1 use std::sync::{Arc, LazyLock}; 2 3 use axum::{ 4 Router, 5 http::{StatusCode, header}, 6 }; 7 use serde_json::json; 8 use sqlx::{PgPool, Row as _, postgres::PgRow}; 9 use taler_common::{ 10 api_common::{HashCode, ShortHashCode}, 11 api_revenue::RevenueConfig, 12 api_transfer::PreparedTransferConfig, 13 api_wire::{OutgoingHistory, TransferRequest, TransferResponse, TransferState, WireConfig}, 14 db::IncomingType, 15 error_code::ErrorCode, 16 types::{ 17 amount::{Amount, Currency, amount}, 18 base32::Base32, 19 payto::{PaytoURI, payto}, 20 url, 21 }, 22 }; 23 use taler_test_utils::{ 24 db::db_test_setup_manual, 25 routine::{ 26 Status, admin_add_incoming_routine, in_history_routine, registration_routine, 27 revenue_routine, routine_pagination, transfer_routine, 28 }, 29 server::TestServer, 30 }; 31 use tokio::sync::watch::Sender; 32 33 use crate::{ 34 api::TalerRouter, 35 auth::AuthMethod, 36 constants::MAX_BODY_LENGTH, 37 db::TypeHelper as _, 38 test::{api::TestApi, db::notification_listener}, 39 }; 40 41 mod api; 42 mod db; 43 44 fn test_api(pool: PgPool, currency: Currency) -> Router { 45 let outgoing_channel = Sender::new(0); 46 let incoming_channel = Sender::new(0); 47 let wg = TestApi { 48 currency, 49 pool: pool.clone(), 50 outgoing_channel: outgoing_channel.clone(), 51 incoming_channel: incoming_channel.clone(), 52 }; 53 tokio::spawn(notification_listener( 54 pool, 55 outgoing_channel, 56 incoming_channel, 57 )); 58 let state = Arc::new(wg); 59 Router::new() 60 .wire_gateway(state.clone(), AuthMethod::None) 61 .prepared_transfer(state.clone()) 62 .revenue(state, AuthMethod::None) 63 } 64 65 async fn setup() -> (Router, PgPool) { 66 let (_, pool) = db_test_setup_manual("db".as_ref(), "taler-api").await; 67 ( 68 test_api(pool.clone(), "EUR".parse().unwrap()).finalize(), 69 pool, 70 ) 71 } 72 73 #[tokio::test] 74 async fn body_parsing() { 75 let (server, _) = setup().await; 76 let normal_body = TransferRequest { 77 request_uid: Base32::rand(), 78 amount: Amount::zero(&Currency::EUR), 79 exchange_base_url: url("https://test.com"), 80 wtid: Base32::rand(), 81 credit_account: payto("payto:://test?receiver-name=lol"), 82 metadata: None, 83 }; 84 85 // Check OK 86 server 87 .post("/taler-wire-gateway/transfer") 88 .json(&normal_body) 89 .deflate() 90 .await 91 .assert_ok_json::<TransferResponse>(); 92 93 // Headers check 94 server 95 .post("/taler-wire-gateway/transfer") 96 .json(&normal_body) 97 .remove(header::CONTENT_TYPE) 98 .await 99 .assert_error_status( 100 ErrorCode::GENERIC_HTTP_HEADERS_MALFORMED, 101 StatusCode::UNSUPPORTED_MEDIA_TYPE, 102 ); 103 server 104 .post("/taler-wire-gateway/transfer") 105 .json(&normal_body) 106 .deflate() 107 .remove(header::CONTENT_ENCODING) 108 .await 109 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 110 server 111 .post("/taler-wire-gateway/transfer") 112 .json(&normal_body) 113 .header(header::CONTENT_TYPE, "invalid") 114 .await 115 .assert_error_status( 116 ErrorCode::GENERIC_HTTP_HEADERS_MALFORMED, 117 StatusCode::UNSUPPORTED_MEDIA_TYPE, 118 ); 119 server 120 .post("/taler-wire-gateway/transfer") 121 .json(&normal_body) 122 .header(header::CONTENT_ENCODING, "deflate") 123 .await 124 .assert_error(ErrorCode::GENERIC_COMPRESSION_INVALID); 125 server 126 .post("/taler-wire-gateway/transfer") 127 .json(&normal_body) 128 .header(header::CONTENT_ENCODING, "invalid") 129 .await 130 .assert_error_status( 131 ErrorCode::GENERIC_HTTP_HEADERS_MALFORMED, 132 StatusCode::UNSUPPORTED_MEDIA_TYPE, 133 ); 134 135 // Body size limit 136 let huge_body = TransferRequest { 137 credit_account: payto(format!( 138 "payto:://test?message={:A<1$}", 139 "payout", MAX_BODY_LENGTH 140 )), 141 ..normal_body 142 }; 143 server 144 .post("/taler-wire-gateway/transfer") 145 .json(&huge_body) 146 .await 147 .assert_error(ErrorCode::GENERIC_UPLOAD_EXCEEDS_LIMIT); 148 server 149 .post("/taler-wire-gateway/transfer") 150 .json(&huge_body) 151 .deflate() 152 .await 153 .assert_error(ErrorCode::GENERIC_UPLOAD_EXCEEDS_LIMIT); 154 } 155 156 static PAYTO: LazyLock<PaytoURI> = LazyLock::new(|| payto("payto://test?receiver-name=Test")); 157 158 #[tokio::test] 159 async fn errors() { 160 let (server, _) = setup().await; 161 server 162 .get("/unknown") 163 .await 164 .assert_error(ErrorCode::GENERIC_ENDPOINT_UNKNOWN); 165 server 166 .post("/taler-revenue/config") 167 .await 168 .assert_error(ErrorCode::GENERIC_METHOD_INVALID); 169 } 170 171 #[tokio::test] 172 async fn config() { 173 let (server, _) = setup().await; 174 server 175 .get("/taler-wire-gateway/config") 176 .await 177 .assert_ok_json::<WireConfig>(); 178 server 179 .get("/taler-prepared-transfer/config") 180 .await 181 .assert_ok_json::<PreparedTransferConfig>(); 182 server 183 .get("/taler-revenue/config") 184 .await 185 .assert_ok_json::<RevenueConfig>(); 186 } 187 188 #[tokio::test] 189 async fn transfer() { 190 let (server, _) = setup().await; 191 transfer_routine(&server, TransferState::success, &PAYTO).await; 192 } 193 194 #[tokio::test] 195 async fn outgoing_history() { 196 let (server, _) = setup().await; 197 routine_pagination::<OutgoingHistory>( 198 &server, 199 "/taler-wire-gateway/history/outgoing", 200 async |i| { 201 server 202 .post("/taler-wire-gateway/transfer") 203 .json(json!({ 204 "request_uid": HashCode::rand(), 205 "amount": amount(format!("EUR:0.0{i}")), 206 "exchange_base_url": url("http://exchange.taler"), 207 "wtid": ShortHashCode::rand(), 208 "credit_account": PAYTO.clone(), 209 })) 210 .await 211 .assert_ok_json::<TransferResponse>(); 212 }, 213 ) 214 .await; 215 } 216 217 #[tokio::test] 218 async fn admin_add_incoming() { 219 let (server, _) = setup().await; 220 admin_add_incoming_routine(&server, &PAYTO, true).await; 221 } 222 223 #[tokio::test] 224 async fn in_history() { 225 let (server, _) = setup().await; 226 in_history_routine(&server, &PAYTO, true).await; 227 } 228 229 #[tokio::test] 230 async fn revenue() { 231 let (server, _) = setup().await; 232 revenue_routine(&server, &PAYTO, true).await; 233 } 234 235 #[tokio::test] 236 async fn account_check() { 237 let (server, _) = setup().await; 238 server 239 .get("/taler-wire-gateway/account/check") 240 .query("account", "payto://test") 241 .await 242 .assert_status(StatusCode::NOT_IMPLEMENTED); 243 } 244 245 async fn check_in(pool: &PgPool) -> Vec<Status> { 246 sqlx::query( 247 " 248 SELECT pending_recurrent_in.authorization_pub IS NOT NULL, bounced.tx_in_id IS NOT NULL, type, taler_in.account_pub 249 FROM tx_in 250 LEFT JOIN taler_in USING (tx_in_id) 251 LEFT JOIN pending_recurrent_in USING (tx_in_id) 252 LEFT JOIN bounced USING (tx_in_id) 253 ORDER BY tx_in.tx_in_id 254 ", 255 ) 256 .try_map(|r: PgRow| { 257 Ok( 258 if r.try_get_flag(0)? { 259 Status::Pending 260 } else if r.try_get_flag(1)? { 261 Status::Bounced 262 } else { 263 match r.try_get(2)? { 264 None => Status::Simple, 265 Some(IncomingType::reserve) => Status::Reserve(r.try_get(3)?), 266 Some(IncomingType::kyc) => Status::Kyc(r.try_get(3)?), 267 Some(e) => unreachable!("{e:?}") 268 } 269 } 270 ) 271 }) 272 .fetch_all(pool) 273 .await 274 .unwrap() 275 } 276 277 #[tokio::test] 278 async fn registration() { 279 let (server, pool) = setup().await; 280 registration_routine(&server, &PAYTO, || check_in(&pool)).await; 281 }