feat(arr-e2e): extend the harness for subtitle scenarios

Daemon::spawn_with_env for extra child environment, database_path()
and media_root() accessors so a test can seed a media file directly,
the daemon built with translate-command, and the plausible/farfetched
Podnapisi zip fixtures the subtitle tests download from.
This commit is contained in:
Miguel Palhas
2026-08-25 05:25:28 +01:00
parent 5fc6854705
commit e47d2c920b
3 changed files with 76 additions and 8 deletions
+2
View File
@@ -14,10 +14,12 @@ tokio = { workspace = true }
wiremock = { workspace = true }
[dev-dependencies]
arr-db = { workspace = true }
arr-dl = { workspace = true }
arr-indexer = { workspace = true }
arr-meta = { workspace = true }
chrono = { workspace = true }
sqlx = { workspace = true }
uuid = { workspace = true }
[lints]
+73 -7
View File
@@ -12,9 +12,11 @@
// Dev-dependencies belong to the integration tests; the lib's own test build
// links them without using them. Same per-target quirk as arr-meta.
#[cfg(test)]
use {arr_dl as _, arr_indexer as _, arr_meta as _, chrono as _, uuid as _};
use {
arr_db as _, arr_dl as _, arr_indexer as _, arr_meta as _, chrono as _, sqlx as _, uuid as _,
};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::sync::OnceLock;
use std::time::{Duration, Instant};
@@ -36,6 +38,24 @@ pub mod fixtures {
pub const PROWLARR_MOVIE_SEARCH: &str = include_str!("../fixtures/prowlarr-movie-search.xml");
pub const TMDB_CONFIGURATION: &str = include_str!("../fixtures/tmdb-configuration.json");
pub const TMDB_MOVIE_DUNE: &str = include_str!("../fixtures/tmdb-movie-dune.json");
/// A zipped SRT with one cue at 00:00:01 — plausibly close to a fetched
/// subtitle's real timing for [`crate::probe_movie_fixture`]'s
/// three-second clip.
pub const PODNAPISI_PLAUSIBLE_ZIP: &[u8] =
include_bytes!("../fixtures/podnapisi-plausible.zip");
/// A zipped SRT timed five minutes in — implausible against the same
/// three-second clip, so `alass` must reject it (DESIGN.md §15).
pub const PODNAPISI_FARFETCHED_ZIP: &[u8] =
include_bytes!("../fixtures/podnapisi-farfetched.zip");
}
/// The clip the subtitle scenarios probe and sync against: three seconds,
/// a real English `subrip` track, real audio — the same fixture
/// `arr-probe`'s own extraction tests use, so a real `ffmpeg` extraction and
/// a real `alass` sync both have something genuine to work with.
#[must_use]
pub fn probe_movie_fixture() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../arr-probe/tests/fixtures/movie-1080p.mkv")
}
/// The enabled indexer in [`fixtures::PROWLARR_INDEXERS`].
@@ -172,6 +192,8 @@ impl FakeTmdb {
pub struct Daemon {
child: Child,
base_url: String,
database_path: PathBuf,
media_root: PathBuf,
// Owns the database and media root for the child's lifetime.
_dir: tempfile::TempDir,
}
@@ -185,35 +207,75 @@ impl Daemon {
/// Panics when the binary cannot be built or spawned, or when the health
/// endpoint does not answer within the boot timeout.
pub async fn spawn(prowlarr_url: &str, tmdb_url: &str, transmission_url: &str) -> Self {
Self::spawn_with_env(prowlarr_url, tmdb_url, transmission_url, &[]).await
}
/// Same as [`Self::spawn`], plus extra environment variables for the
/// child — the subtitle seams (`ARR_PODNAPISI_URL`,
/// `ARR_TRANSLATE_COMMAND_TEMPLATE`, ...) that only a test harness sets.
///
/// # Panics
///
/// Panics when the binary cannot be built or spawned, or when the health
/// endpoint does not answer within the boot timeout.
pub async fn spawn_with_env(
prowlarr_url: &str,
tmdb_url: &str,
transmission_url: &str,
extra_env: &[(&str, &str)],
) -> Self {
let dir = tempfile::tempdir().expect("create daemon tempdir");
let media_root = dir.path().join("media");
std::fs::create_dir(&media_root).expect("create media root");
let port = free_port();
let base_url = format!("http://127.0.0.1:{port}");
let database_path = dir.path().join("arr.db");
let child = Command::new(daemon_binary())
let mut command = Command::new(daemon_binary());
command
.env_remove("ARR_CONFIG_FILE")
.env("ARR_BIND_ADDR", format!("127.0.0.1:{port}"))
.env("ARR_DATABASE_PATH", dir.path().join("arr.db"))
.env("ARR_DATABASE_PATH", &database_path)
.env("ARR_MEDIA_ROOT", &media_root)
.env("ARR_PROWLARR_URL", prowlarr_url)
.env("ARR_PROWLARR_API_KEY", API_KEY)
.env("ARR_TMDB_URL", tmdb_url)
.env("ARR_TMDB_API_KEY", API_KEY)
.env("ARR_TRANSMISSION_URL", transmission_url)
.stdin(Stdio::null())
.spawn()
.expect("spawn arr daemon");
.stdin(Stdio::null());
for (key, value) in extra_env {
command.env(key, value);
}
let child = command.spawn().expect("spawn arr daemon");
let mut daemon = Self {
child,
base_url,
database_path,
media_root,
_dir: dir,
};
daemon.wait_until_healthy().await;
daemon
}
/// The child's SQLite database file. Subtitle scenarios have no HTTP
/// route to adopt an already-imported media file (that pipeline is its
/// own seam, DESIGN.md §12), so tests seed one directly here — the same
/// file the daemon's own reconcile loop reads and writes, WAL mode
/// making the two connections safe to interleave.
#[must_use]
pub fn database_path(&self) -> &Path {
&self.database_path
}
/// The child's media root (`ARR_MEDIA_ROOT`), for tests that plant a
/// video file for the reconcile loop to find.
#[must_use]
pub fn media_root(&self) -> &Path {
&self.media_root
}
/// The daemon's API base URL, such as `http://127.0.0.1:41234`.
#[must_use]
pub fn base_url(&self) -> &str {
@@ -275,6 +337,10 @@ fn daemon_binary() -> &'static PathBuf {
let mut build = Command::new(env!("CARGO"));
build
.args(["build", "-p", "arr-daemon", "--bin", "arr"])
// The remote-command translation backend (DESIGN.md §15, issue
// #193) is behind its own cargo feature; the subtitle e2e
// scenarios need it compiled in to drive a stub script.
.args(["--features", "translate-command"])
.args(["--message-format", "json-render-diagnostics"])
.stderr(Stdio::inherit());
// The test process carries the per-crate vars cargo set for *this*
+1 -1
View File
@@ -8,7 +8,7 @@
// The harness library links these; this test target does not use them all
// directly. Same per-target quirk as arr-meta's tests.
use {tempfile as _, wiremock as _};
use {arr_db as _, sqlx as _, tempfile as _, wiremock as _};
use std::path::PathBuf;