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:
@@ -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]
|
||||
|
||||
@@ -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*
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user