aboutsummaryrefslogtreecommitdiff
path: root/lib/project/tests/test_config.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-09-07 06:47:47 -0400
committerhistoria <historiavg@proton.me>2026-09-07 06:47:47 -0400
commit84dd2d068317998f6fb59400c534ef5be6b51b53 (patch)
tree025293e9d9229e02960374771ae522d9de2628ce /lib/project/tests/test_config.py
parent39b0f2bbed74f6487a41b82501ae3c6799e4b5c4 (diff)
downloadproducer-84dd2d068317998f6fb59400c534ef5be6b51b53.tar.gz
slop rewriteHEADmain
Diffstat (limited to 'lib/project/tests/test_config.py')
-rw-r--r--lib/project/tests/test_config.py134
1 files changed, 134 insertions, 0 deletions
diff --git a/lib/project/tests/test_config.py b/lib/project/tests/test_config.py
new file mode 100644
index 0000000..8144fc8
--- /dev/null
+++ b/lib/project/tests/test_config.py
@@ -0,0 +1,134 @@
+from dataclasses import asdict, fields
+
+import pytest
+
+from voiceforge.cli import main, parser, settings_for
+from voiceforge.config import PROFILES, RANGES, Settings, resolve, toml
+from voiceforge.pipeline import filters
+
+
+@pytest.mark.parametrize("profile", PROFILES)
+def test_profiles_and_toml_round_trip(profile, tmp_path):
+ settings = resolve(profile=profile)
+ assert asdict(settings) == asdict(Settings()) | PROFILES[profile] | {"profile": profile}
+ path = tmp_path / "settings.toml"
+ path.write_text(toml(settings))
+ assert resolve(path) == settings
+
+
+def test_precedence_defaults_profile_file_flags_then_set(tmp_path):
+ path = tmp_path / "settings.toml"
+ path.write_text('profile = "narrator"\ncompressor_ratio = 2.2\nwarmth_db = 3\n')
+ args = parser().parse_args([
+ "config", "--config", str(path), "--profile", "radio",
+ "--warmth-db", "4", "--set", "warmth_db=5", "--set", "warmth_db=6",
+ ])
+ settings = settings_for(args)
+ assert settings.profile == "radio"
+ assert settings.compressor_attack_ms == 8 # Selected profile beats file profile.
+ assert settings.compressor_ratio == 2.2 # File values beat profile defaults.
+ assert settings.warmth_db == 6 # Last --set beats named flags.
+ assert settings.sample_rate == 48000
+
+
+@pytest.mark.parametrize("field", [f.name for f in fields(Settings) if type(getattr(Settings(), f.name)) is bool])
+def test_boolean_flags_preserve_unspecified_and_accept_both_forms(field):
+ assert getattr(parser().parse_args(["config"]), field) is None
+ for enabled in (True, False):
+ flag = "--" + ("" if enabled else "no-") + field.replace("_", "-")
+ extra = ["--limiter"] if field == "normalize" and enabled else []
+ args = parser().parse_args(["config", "--profile", "cleanup-only", flag, *extra])
+ assert getattr(settings_for(args), field) is enabled
+
+
+def test_false_cli_toggle_overrides_true_file_value(tmp_path):
+ path = tmp_path / "settings.toml"
+ path.write_text("eq = true\n")
+ args = parser().parse_args(["config", "--config", str(path), "--no-eq"])
+ assert settings_for(args).eq is False
+
+
+def test_no_denoise_and_unquoted_set_strings():
+ assert settings_for(parser().parse_args(["config", "--no-denoise"])).denoiser == "none"
+ args = parser().parse_args(["config", "--no-denoise", "--set", "denoiser=fft"])
+ assert settings_for(args).denoiser == "fft"
+
+
+@pytest.mark.parametrize("key", RANGES)
+def test_numeric_ranges_are_inclusive_and_reject_outside(key):
+ low, high = RANGES[key]
+ for value in (low, high):
+ assert getattr(resolve(overrides={key: value}), key) == value
+ for value in (low - 0.01, high + 0.01):
+ with pytest.raises(ValueError, match=key):
+ resolve(overrides={key: value})
+
+
+@pytest.mark.parametrize("overrides,match", [
+ ({"typo": True}, "Unknown setting"),
+ ({"highpass": 1}, "highpass must be bool"),
+ ({"sample_rate": 48000.0}, "sample_rate must be int"),
+ ({"target_lufs": True}, "finite number"),
+ ({"target_lufs": float("nan")}, "finite number"),
+ ({"target_lufs": float("inf")}, "finite number"),
+ ({"denoiser": "unknown"}, "denoiser must be one of"),
+ ({"device": "gpu"}, "device must be one of"),
+ ({"channel": "stereo"}, "channel must be one of"),
+ ({"hum_hz": 55}, "hum_hz must be one of"),
+ ({"sample_rate": 96000}, "sample_rate must be one of"),
+ ({"mp3_bitrate": 64}, "mp3_bitrate must be one of"),
+ ({"limiter": False}, "Normalization includes true-peak limiting"),
+])
+def test_invalid_configuration(overrides, match):
+ with pytest.raises(ValueError, match=match):
+ resolve(overrides=overrides)
+
+
+def test_unknown_profile_and_malformed_toml(tmp_path):
+ with pytest.raises(ValueError, match="Unknown profile"):
+ resolve(profile="missing")
+ path = tmp_path / "bad.toml"
+ path.write_text("normalize = [")
+ with pytest.raises(ValueError):
+ resolve(path)
+
+
+def test_invalid_cli_configuration_fails_before_processing(monkeypatch, capsys):
+ def unexpected(*args, **kwargs):
+ pytest.fail("Invalid configuration must not reach processing or tool setup")
+
+ monkeypatch.setattr("voiceforge.cli.process", unexpected)
+ monkeypatch.setattr("voiceforge.cli.ensure_ffmpeg", unexpected)
+ monkeypatch.setattr("sys.argv", ["voiceforge", "process", "missing.wav", "--target-lufs", "0"])
+ with pytest.raises(SystemExit) as error:
+ main()
+ assert error.value.code == 1
+ assert "target_lufs must be between" in capsys.readouterr().err
+
+
+def test_set_requires_assignment():
+ with pytest.raises(ValueError, match="KEY=VALUE"):
+ settings_for(parser().parse_args(["config", "--set", "normalize"]))
+
+
+@pytest.mark.parametrize("profile", ["radio", "not-a-profile"])
+def test_set_profile_uses_normal_profile_resolution(profile):
+ args = parser().parse_args(["config", "--set", f"profile={profile}"])
+ if profile not in PROFILES:
+ with pytest.raises(ValueError, match="Unknown profile"):
+ settings_for(args)
+ else:
+ assert settings_for(args) == resolve(profile=profile)
+
+
+@pytest.mark.parametrize("toggle,prefix,count", [
+ ("expansion", "agate=", 1), ("eq", "equalizer=", 3),
+ ("compression", "acompressor=", 1), ("deess", "deesser=", 1),
+ ("lowpass", "lowpass=", 1),
+])
+def test_shaping_filter_toggles(bypass, toggle, prefix, count):
+ assert filters(bypass) == ["anull"]
+ setattr(bypass, toggle, True)
+ chain = filters(bypass)
+ assert len(chain) == count
+ assert all(item.startswith(prefix) for item in chain)