From bb64c4cd52b9a0735a6e18a9fb47d69e729152a6 Mon Sep 17 00:00:00 2001 From: NimBold Date: Sat, 1 Aug 2026 20:09:57 +0330 Subject: [PATCH] fix(ci): use Windows-compatible Torrent RPC integration test --- .github/workflows/ci.yml | 4 -- package.json | 2 +- src-tauri/src/lib.rs | 3 +- src-tauri/tests/README.md | 13 ++--- src-tauri/tests/torrent_rpc.rs | 96 ++++++++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 src-tauri/tests/torrent_rpc.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 812c11b..00e4aa2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,10 +71,6 @@ jobs: run: | cargo test --tests --target ${{ matrix.target }} cargo test --lib --no-run --target ${{ matrix.target }} - - name: Test Torrent RPC harness on Windows - if: runner.os == 'Windows' - working-directory: src-tauri - run: cargo test torrent_probe::tests::http_rpc_harness --target ${{ matrix.target }} -- --nocapture - name: Provision locked engines if: runner.os != 'macOS' run: node scripts/provision-engines.js --target ${{ matrix.target }} diff --git a/package.json b/package.json index a18177d..0593c24 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "check:updates": "node scripts/check-updates.js", "smoke:torrent": "node scripts/smoke-torrent.js", "smoke:torrent:failure-paths": "node scripts/smoke-torrent.js --failure-paths", - "test:torrent:rpc": "cd src-tauri && cargo test torrent_probe::tests::http_rpc_harness -- --nocapture", + "test:torrent:rpc": "cd src-tauri && cargo test --test torrent_rpc -- --nocapture", "verify:macos-signing": "node scripts/verify-macos-signing.js", "preview": "vite preview", "tauri": "tauri", diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2961c57..a933ce4 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -3235,7 +3235,8 @@ fn dispatch_deep_links(app_handle: tauri::AppHandle, deep_links: Vec) }); } -pub(crate) async fn rpc_call( +#[doc(hidden)] +pub async fn rpc_call( port: u16, secret: &str, method: &str, diff --git a/src-tauri/tests/README.md b/src-tauri/tests/README.md index 153cbce..106e179 100644 --- a/src-tauri/tests/README.md +++ b/src-tauri/tests/README.md @@ -36,9 +36,9 @@ npm run smoke:torrent Use `node scripts/smoke-torrent.js --binary /path/to/aria2c` when validating a packaged or target-specific Aria2 binary. -Run the HTTP-boundary Torrent probe harness with a controllable local JSON-RPC -server. It drives the production Aria2 RPC client through scripted status, -outage, race, cancellation, and daemon-termination cases: +Run the HTTP-boundary Torrent RPC integration test. It drives the production +Aria2 RPC client through a local JSON-RPC server and verifies successful +requests plus HTTP gateway errors: ```sh npm run test:torrent:rpc @@ -51,6 +51,7 @@ npm run smoke:torrent:failure-paths ``` Native CI runs this failure-path smoke after staging the target-specific -bundled engines on macOS, Windows, and Linux. Windows also runs the filtered -HTTP-boundary RPC harness explicitly because its general Rust job compiles the -library tests without executing them. +bundled engines on macOS, Windows, and Linux. Windows runs this RPC +integration test through its target-qualified `cargo test --tests` step; +the general Rust job compiles the library tests without executing the +known-broken Tauri library harness. diff --git a/src-tauri/tests/torrent_rpc.rs b/src-tauri/tests/torrent_rpc.rs new file mode 100644 index 0000000..e959220 --- /dev/null +++ b/src-tauri/tests/torrent_rpc.rs @@ -0,0 +1,96 @@ +use axum::{extract::Json, http::StatusCode, response::IntoResponse, routing::post, Router}; +use firelink_lib::rpc_call; +use serde_json::{json, Value}; +use std::net::SocketAddr; +use tokio::sync::oneshot; + +async fn start_server( + app: Router, +) -> (SocketAddr, oneshot::Sender<()>, tokio::task::JoinHandle<()>) { + let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0)) + .await + .expect("RPC test server should bind"); + let address = listener + .local_addr() + .expect("RPC test server should have an address"); + let (shutdown, shutdown_signal) = oneshot::channel(); + let task = tokio::spawn(async move { + axum::serve(listener, app) + .with_graceful_shutdown(async { + let _ = shutdown_signal.await; + }) + .await + .expect("RPC test server should stop cleanly"); + }); + (address, shutdown, task) +} + +struct TestResponse(StatusCode, Value); + +impl IntoResponse for TestResponse { + fn into_response(self) -> axum::response::Response { + (self.0, Json(self.1)).into_response() + } +} + +async fn successful_rpc(Json(request): Json) -> TestResponse { + assert_eq!(request.get("jsonrpc"), Some(&json!("2.0"))); + assert_eq!(request.get("id"), Some(&json!("1"))); + assert_eq!(request.get("method"), Some(&json!("aria2.getVersion"))); + assert_eq!( + request.get("params"), + Some(&json!(["token:test-secret", {"include": "version"}])) + ); + TestResponse( + StatusCode::OK, + json!({"jsonrpc": "2.0", "id": "1", "result": {"version": "test"}}), + ) +} + +async fn gateway_error(Json(_request): Json) -> TestResponse { + TestResponse( + StatusCode::BAD_GATEWAY, + json!({"jsonrpc": "2.0", "id": "1", "error": {"code": 1, "message": "backend unavailable"}}), + ) +} + +async fn stop_server(shutdown: oneshot::Sender<()>, task: tokio::task::JoinHandle<()>) { + let _ = shutdown.send(()); + task.await.expect("RPC test server task should join"); +} + +#[tokio::test] +async fn production_rpc_client_sends_authenticated_json_rpc() { + let app = Router::new().route("/jsonrpc", post(successful_rpc)); + let (address, shutdown, task) = start_server(app).await; + let result = rpc_call( + address.port(), + "test-secret", + "aria2.getVersion", + json!([{"include": "version"}]), + ) + .await + .expect("successful RPC response should decode"); + + assert_eq!(result, json!({"version": "test"})); + stop_server(shutdown, task).await; +} + +#[tokio::test] +async fn production_rpc_client_preserves_http_gateway_context() { + let app = Router::new().route("/jsonrpc", post(gateway_error)); + let (address, shutdown, task) = start_server(app).await; + let error = rpc_call(address.port(), "test-secret", "aria2.getVersion", json!([])) + .await + .expect_err("gateway response should fail"); + + assert!( + error.contains("HTTP 502 Bad Gateway"), + "unexpected error: {error}" + ); + assert!( + error.contains("backend unavailable"), + "unexpected error: {error}" + ); + stop_server(shutdown, task).await; +}