api.rs (6348B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2024, 2025, 2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 use axum::http::StatusCode; 18 use common::setup; 19 use jiff::Timestamp; 20 use sqlx::{PgPool, Row, postgres::PgRow}; 21 use taler_api::db::TypeHelper as _; 22 use taler_common::{ 23 api_common::{EddsaPublicKey, HashCode, ShortHashCode}, 24 api_revenue::RevenueConfig, 25 api_transfer::WireTransferConfig, 26 api_wire::{OutgoingHistory, TransferResponse, TransferState, WireConfig}, 27 db::IncomingType, 28 error_code::ErrorCode, 29 types::{amount::amount, payto::payto, url}, 30 }; 31 use taler_test_utils::{ 32 json, 33 routine::{ 34 Status, admin_add_incoming_routine, registration_routine, revenue_routine, 35 routine_pagination, transfer_routine, 36 }, 37 server::TestServer as _, 38 }; 39 use tracing::warn; 40 41 use crate::common::db::{AddIncomingResult, add_incoming}; 42 43 mod common; 44 45 #[tokio::test] 46 async fn errors() { 47 let (server, _) = setup().await; 48 server 49 .get("/unknown") 50 .await 51 .assert_error(ErrorCode::GENERIC_ENDPOINT_UNKNOWN); 52 server 53 .post("/taler-revenue/config") 54 .await 55 .assert_error(ErrorCode::GENERIC_METHOD_INVALID); 56 } 57 58 #[tokio::test] 59 async fn config() { 60 let (server, _) = setup().await; 61 server 62 .get("/taler-wire-gateway/config") 63 .await 64 .assert_ok_json::<WireConfig>(); 65 server 66 .get("/taler-wire-transfer-gateway/config") 67 .await 68 .assert_ok_json::<WireTransferConfig>(); 69 server 70 .get("/taler-revenue/config") 71 .await 72 .assert_ok_json::<RevenueConfig>(); 73 } 74 75 #[tokio::test] 76 async fn transfer() { 77 let (server, _) = setup().await; 78 transfer_routine(&server, TransferState::success, &payto("payto://test")).await; 79 } 80 81 #[tokio::test] 82 async fn outgoing_history() { 83 let (server, _) = setup().await; 84 routine_pagination::<OutgoingHistory, _>( 85 &server, 86 "/taler-wire-gateway/history/outgoing", 87 async |i| { 88 server 89 .post("/taler-wire-gateway/transfer") 90 .json(&json!({ 91 "request_uid": HashCode::rand(), 92 "amount": amount(format!("EUR:0.0{i}")), 93 "exchange_base_url": url("http://exchange.taler"), 94 "wtid": ShortHashCode::rand(), 95 "credit_account": url("payto://test"), 96 })) 97 .await 98 .assert_ok_json::<TransferResponse>(); 99 }, 100 ) 101 .await; 102 } 103 104 #[tokio::test] 105 async fn admin_add_incoming() { 106 let (server, _) = setup().await; 107 admin_add_incoming_routine(&server, &payto("payto://test"), true).await; 108 } 109 110 #[tokio::test] 111 async fn revenue() { 112 let (server, _) = setup().await; 113 revenue_routine(&server, &payto("payto://test"), true).await; 114 } 115 116 #[tokio::test] 117 async fn account_check() { 118 let (server, _) = setup().await; 119 server 120 .get("/taler-wire-gateway/account/check") 121 .query("account", "payto://test") 122 .await 123 .assert_status(StatusCode::NOT_IMPLEMENTED); 124 } 125 126 async fn check_in(pool: &PgPool) -> Vec<Status> { 127 sqlx::query( 128 " 129 SELECT pending_recurrent_in.authorization_pub IS NOT NULL, bounced.tx_in_id IS NOT NULL, type, taler_in.account_pub 130 FROM tx_in 131 LEFT JOIN taler_in USING (tx_in_id) 132 LEFT JOIN pending_recurrent_in USING (tx_in_id) 133 LEFT JOIN bounced USING (tx_in_id) 134 ORDER BY tx_in.tx_in_id 135 ", 136 ) 137 .try_map(|r: PgRow| { 138 Ok( 139 if r.try_get_flag(0)? { 140 Status::Pending 141 } else if r.try_get_flag(1)? { 142 Status::Bounced 143 } else { 144 match r.try_get(2)? { 145 None => Status::Simple, 146 Some(IncomingType::reserve) => Status::Reserve(r.try_get(3)?), 147 Some(IncomingType::kyc) => Status::Kyc(r.try_get(3)?), 148 Some(e) => unreachable!("{e:?}") 149 } 150 } 151 ) 152 }) 153 .fetch_all(pool) 154 .await 155 .unwrap() 156 } 157 158 async fn register_mapped(pool: &PgPool, account_pub: &EddsaPublicKey) { 159 let reason = match add_incoming( 160 pool, 161 &amount("EUR:42"), 162 &payto("payto://test"), 163 "lol", 164 &Timestamp::now(), 165 IncomingType::map, 166 account_pub, 167 ) 168 .await 169 .unwrap() 170 { 171 AddIncomingResult::Success { .. } => return, 172 AddIncomingResult::ReservePubReuse => "reserve pub reuse", 173 AddIncomingResult::UnknownMapping => "unknown mapping", 174 AddIncomingResult::MappingReuse => "mapping reuse", 175 }; 176 warn!("Bounce {reason}"); 177 sqlx::query( 178 " 179 WITH tx_in AS ( 180 INSERT INTO tx_in ( 181 amount, 182 debit_payto, 183 created_at, 184 subject 185 ) VALUES ( 186 (32, 0), 187 'payto', 188 0, 189 'subject' 190 ) RETURNING tx_in_id 191 ) 192 INSERT INTO bounced (tx_in_id) 193 SELECT tx_in_id FROM tx_in 194 ", 195 ) 196 .execute(pool) 197 .await 198 .unwrap(); 199 } 200 201 #[tokio::test] 202 async fn registration() { 203 let (server, pool) = setup().await; 204 registration_routine( 205 &server, 206 &payto("payto://test"), 207 || check_in(&pool), 208 |account_pub| { 209 let account_pub = account_pub.clone(); 210 let pool = &pool; 211 async move { register_mapped(pool, &account_pub).await } 212 }, 213 ) 214 .await; 215 }