fix(daemon): consume the MovieCommand channel
ci / web (push) Successful in 36s
ci / rust (push) Successful in 1m23s
e2e / e2e (push) Successful in 1m17s

Nothing drained AppState's movie_commands mpsc, so manual search and
one-click grab were accepted with 202 and then did nothing until the
64-slot buffer filled and the endpoint started 503ing.

A new daemon task drains it: Search resets the movie's backoff and
runs the targeted-search + grab lane immediately; Grab sends the
already-chosen release straight to Transmission.

Closes #107
This commit is contained in:
Miguel Palhas
2026-08-23 09:02:42 +01:00
parent ca8a891347
commit 83ca921501
8 changed files with 675 additions and 33 deletions
+27 -10
View File
@@ -52,8 +52,9 @@ async fn daemon_runs_against_fakes_and_a_real_transmission() {
let movie_id = movie["id"].as_i64().expect("movie id");
assert_eq!(movie["wanted"], true);
// The manual-search trigger is accepted; its consumer is the reconcile
// loop (#21), so no releases materialise yet.
// The manual-search trigger is accepted and consumed by the daemon
// (issue #107), so the fixture's releases materialise across the
// process boundary.
let search = reqwest::Client::new()
.post(format!("{base}/api/movies/{movie_id}/search"))
.send()
@@ -61,14 +62,30 @@ async fn daemon_runs_against_fakes_and_a_real_transmission() {
.expect("trigger search");
assert_eq!(search.status(), reqwest::StatusCode::ACCEPTED);
let releases: Vec<serde_json::Value> =
reqwest::get(format!("{base}/api/movies/{movie_id}/releases"))
.await
.expect("list releases")
.json()
.await
.expect("releases json");
assert!(releases.is_empty());
let releases: Vec<serde_json::Value> = wait_for_releases(base, movie_id).await;
assert_eq!(releases.len(), 2);
assert!(releases.iter().any(|release| release["name"]
.as_str()
.is_some_and(|name| name.contains("Dune.Part.Two.2024.2160p.WEB-DL"))));
}
/// The manual search runs on a background task, not inline with the HTTP
/// response, so give it a moment to land instead of racing it.
async fn wait_for_releases(base: &str, movie_id: i64) -> Vec<serde_json::Value> {
for _ in 0..50 {
let releases: Vec<serde_json::Value> =
reqwest::get(format!("{base}/api/movies/{movie_id}/releases"))
.await
.expect("list releases")
.json()
.await
.expect("releases json");
if !releases.is_empty() {
return releases;
}
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
panic!("no releases appeared after the manual search");
}
/// The recorded fixtures satisfy the real Prowlarr and TMDB clients, so a