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