Skip to content

Commit c65e318

Browse files
committed
feat: /info endpoints
1 parent cea8d1a commit c65e318

File tree

3 files changed

+109
-13
lines changed

3 files changed

+109
-13
lines changed

openapi.yaml

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -237,25 +237,43 @@ paths:
237237
type: string
238238
example: "Internal Server Error"
239239

240-
/ln_verification/price:
240+
/sms_verification/info:
241241
get:
242-
summary: Get Lightning Network verification price
242+
summary: Get SMS verification info
243+
description: |
244+
Returns service availability for SMS verification.
245+
246+
Used by Franky to check if the user is in a country which allows SMS verification.
247+
IP-based geo-blocking is done at the nginx level; this endpoint confirms service availability.
248+
operationId: getSmsVerificationInfo
249+
tags:
250+
- SMS Verification
251+
responses:
252+
'200':
253+
description: SMS verification is available
254+
255+
/ln_verification/info:
256+
get:
257+
summary: Get Lightning Network verification info
243258
description: |
244259
Returns the configured price in satoshis for Lightning Network verification.
245260
This is the amount that must be paid to complete a verification.
246-
operationId: getLnVerificationPrice
261+
262+
Used by Franky to check if the user is in a country which allows Lightning verification.
263+
IP-based geo-blocking is done at the nginx level; this endpoint confirms service availability.
264+
operationId: getLnVerificationInfo
247265
tags:
248266
- Lightning Network Verification
249267
responses:
250268
'200':
251-
description: Price retrieved successfully
269+
description: Info retrieved successfully
252270
content:
253271
application/json:
254272
schema:
255-
$ref: '#/components/schemas/GetPriceResponse'
273+
$ref: '#/components/schemas/LnInfoResponse'
256274
examples:
257275
success:
258-
summary: Price response
276+
summary: Info response
259277
value:
260278
amountSat: 1000
261279

@@ -538,7 +556,7 @@ components:
538556
description: Unix timestamp in milliseconds when the verification was created
539557
example: 1704066600000
540558

541-
GetPriceResponse:
559+
LnInfoResponse:
542560
type: object
543561
required:
544562
- amountSat

src/ln_verification/http.rs

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pub async fn router(
5353
.route("/", post(create_verification_handler))
5454
.route("/{id}", get(get_verification_handler))
5555
.route("/{id}/await", get(await_verification_handler))
56-
.route("/price", get(get_price_handler))
56+
.route("/info", get(get_info_handler))
5757
.with_state(state))
5858
}
5959

@@ -128,9 +128,11 @@ async fn await_verification_handler(
128128
}
129129
}
130130

131-
/// Get the configured Lightning invoice price handler
132-
async fn get_price_handler(State(state): State<AppState>) -> Json<GetPriceResponse> {
133-
Json(GetPriceResponse {
131+
/// Get Lightning verification info handler.
132+
/// Used by Franky to check if the user is in a country which allows Lightning verification.
133+
/// IP-based geo-blocking is done at the nginx level; this endpoint confirms service availability.
134+
async fn get_info_handler(State(state): State<AppState>) -> Json<GetInfoResponse> {
135+
Json(GetInfoResponse {
134136
amount_sat: state.ln_service.get_price_sat(),
135137
})
136138
}
@@ -176,7 +178,7 @@ impl GetVerificationResponse {
176178

177179
#[derive(Debug, serde::Serialize, serde::Deserialize)]
178180
#[serde(rename_all = "camelCase")]
179-
pub struct GetPriceResponse {
181+
pub struct GetInfoResponse {
180182
amount_sat: u64,
181183
}
182184

@@ -199,3 +201,60 @@ impl IntoResponse for LnVerificationError {
199201
(status, self.to_string()).into_response()
200202
}
201203
}
204+
205+
#[cfg(test)]
206+
mod tests {
207+
use super::*;
208+
use crate::infrastructure::sql::SqlDb;
209+
use crate::ln_verification::phoenixd_api::PhoenixdAPI;
210+
use crate::shared::HomeserverAdminAPI;
211+
use axum_test::TestServer;
212+
use sqlx::PgPool;
213+
214+
/// Creates a test router for LN verification WITHOUT starting the background sync task.
215+
/// This is suitable for testing endpoints that don't require websocket connectivity.
216+
async fn create_test_router(pool: PgPool) -> Router {
217+
let db = SqlDb::test(pool).await;
218+
// Use placeholder URLs - these won't be called for the /info endpoint
219+
let phoenixd_api = PhoenixdAPI::new(&"http://localhost:1".parse().unwrap(), "unused");
220+
let homeserver_api = HomeserverAdminAPI::new(
221+
&"http://localhost:1".parse().unwrap(),
222+
"unused",
223+
"test-homeserver-pubky",
224+
);
225+
226+
let ln_service = crate::ln_verification::service::LnVerificationService::new(
227+
db,
228+
phoenixd_api,
229+
homeserver_api.clone(),
230+
1000, // amount_sat
231+
"Test Invoice".to_string(),
232+
600, // invoice_expiry_seconds
233+
);
234+
let ln_service = std::sync::Arc::new(ln_service);
235+
236+
// Note: We intentionally skip spawning the background sync task for this test
237+
let state = AppState::new(ln_service, homeserver_api);
238+
Router::new()
239+
.route("/info", get(get_info_handler))
240+
.with_state(state)
241+
}
242+
243+
/// Tests the /info endpoint returns 200 OK with the configured price.
244+
/// This endpoint is used by Franky to check if Lightning verification is available
245+
/// (geo-blocking is done at nginx level).
246+
#[sqlx::test]
247+
async fn test_info_endpoint_returns_ok_with_price(pool: PgPool) {
248+
let router = create_test_router(pool).await;
249+
let server = TestServer::new(router).expect("Failed to create test server");
250+
251+
let response = server.get("/info").await;
252+
response.assert_status_ok();
253+
254+
let body: serde_json::Value = response.json();
255+
assert_eq!(
256+
body["amountSat"], 1000,
257+
"Response should include the configured price"
258+
);
259+
}
260+
}

src/sms_verification/http.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use axum::{
33
extract::State,
44
http::StatusCode,
55
response::{IntoResponse, Response},
6-
routing::post,
6+
routing::{get, post},
77
};
88

99
use crate::sms_verification::{
@@ -24,6 +24,7 @@ pub async fn router(
2424
Ok(Router::new()
2525
.route("/send_code", post(send_code_handler))
2626
.route("/validate_code", post(validate_code_handler))
27+
.route("/info", get(get_info_handler))
2728
.with_state(state))
2829
}
2930

@@ -36,6 +37,7 @@ pub async fn router_with_db(
3637
Ok(Router::new()
3738
.route("/send_code", post(send_code_handler))
3839
.route("/validate_code", post(validate_code_handler))
40+
.route("/info", get(get_info_handler))
3941
.with_state(state))
4042
}
4143

@@ -63,6 +65,13 @@ async fn validate_code_handler(
6365
Ok(Json(response))
6466
}
6567

68+
/// Get SMS verification info handler.
69+
/// Used by Franky to check if the user is in a country which allows SMS verification.
70+
/// IP-based geo-blocking is done at the nginx level; this endpoint confirms service availability.
71+
async fn get_info_handler() -> StatusCode {
72+
StatusCode::OK
73+
}
74+
6675
impl IntoResponse for SmsVerificationError {
6776
fn into_response(self) -> Response {
6877
let status = match self {
@@ -299,4 +308,14 @@ mod tests {
299308

300309
// Wiremock verifies all expected calls were made
301310
}
311+
312+
/// Tests the /info endpoint returns 200 OK.
313+
#[sqlx::test]
314+
async fn test_info_endpoint_returns_ok(pool: PgPool) {
315+
let servers = WiremockServers::start().await;
316+
let (server, _pool) = create_http_test_server(pool, &servers).await;
317+
318+
let response = server.get("/sms_verification/info").await;
319+
response.assert_status_ok();
320+
}
302321
}

0 commit comments

Comments
 (0)