Add audio CF tests and translation keys

This commit is contained in:
Ahmed Al-Taiar 2026-01-17 13:51:29 -05:00
parent 4dee096c83
commit b27ffc06a7
7 changed files with 846 additions and 0 deletions

View file

@ -0,0 +1,127 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.CustomFormats.Specifications
{
[TestFixture]
public class AudioBitDepthSpecificationFixture : CoreTest
{
private CustomFormatInput _input;
[SetUp]
public void Setup()
{
_input = new CustomFormatInput
{
AlbumInfo = new ParsedAlbumInfo(),
MediaInfo = new MediaInfoModel()
};
}
[TestCase(16, 16, 24, true)]
[TestCase(24, 16, 24, true)]
[TestCase(24, 24, 32, true)]
[TestCase(32, 24, 32, true)]
[TestCase(16, 24, 32, false)]
[TestCase(32, 0, 24, false)]
[TestCase(0, 16, 24, false)]
public void should_match_bit_depth_range(int bitDepth, int min, int max, bool expected)
{
_input.MediaInfo.AudioBits = bitDepth;
var spec = new AudioBitDepthSpecification
{
Min = min,
Max = max
};
spec.IsSatisfiedBy(_input).Should().Be(expected);
}
[Test]
public void should_not_match_when_media_info_is_null()
{
_input.MediaInfo = null;
var spec = new AudioBitDepthSpecification
{
Min = 16,
Max = 24
};
spec.IsSatisfiedBy(_input).Should().BeFalse();
}
[TestCase(16, 16, 24, false)]
[TestCase(24, 16, 24, false)]
[TestCase(16, 24, 32, true)]
public void should_match_negated_bit_depth_range(int bitDepth, int min, int max, bool expected)
{
_input.MediaInfo.AudioBits = bitDepth;
var spec = new AudioBitDepthSpecification
{
Min = min,
Max = max,
Negate = true
};
spec.IsSatisfiedBy(_input).Should().Be(expected);
}
[Test]
public void should_be_valid_when_min_is_zero()
{
var spec = new AudioBitDepthSpecification
{
Min = 0,
Max = 24
};
var result = spec.Validate();
result.IsValid.Should().BeTrue();
}
[Test]
public void should_be_valid_when_max_equals_min()
{
var spec = new AudioBitDepthSpecification
{
Min = 24,
Max = 24
};
var result = spec.Validate();
result.IsValid.Should().BeTrue();
}
[Test]
public void should_be_invalid_when_max_is_less_than_min()
{
var spec = new AudioBitDepthSpecification
{
Min = 24,
Max = 16
};
var result = spec.Validate();
result.IsValid.Should().BeFalse();
}
[Test]
public void should_be_invalid_when_min_is_negative()
{
var spec = new AudioBitDepthSpecification
{
Min = -1,
Max = 24
};
var result = spec.Validate();
result.IsValid.Should().BeFalse();
}
}
}

View file

@ -0,0 +1,127 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.CustomFormats.Specifications
{
[TestFixture]
public class AudioBitrateSpecificationFixture : CoreTest
{
private CustomFormatInput _input;
[SetUp]
public void Setup()
{
_input = new CustomFormatInput
{
AlbumInfo = new ParsedAlbumInfo(),
MediaInfo = new MediaInfoModel()
};
}
[TestCase(320, 256, 320, true)]
[TestCase(256, 192, 320, true)]
[TestCase(128, 128, 256, true)]
[TestCase(1411, 1000, 1500, true)]
[TestCase(96, 128, 320, false)]
[TestCase(320, 0, 256, false)]
[TestCase(0, 128, 320, false)]
public void should_match_bitrate_range(int bitrate, int min, int max, bool expected)
{
_input.MediaInfo.AudioBitrate = bitrate;
var spec = new AudioBitrateSpecification
{
Min = min,
Max = max
};
spec.IsSatisfiedBy(_input).Should().Be(expected);
}
[Test]
public void should_not_match_when_media_info_is_null()
{
_input.MediaInfo = null;
var spec = new AudioBitrateSpecification
{
Min = 128,
Max = 320
};
spec.IsSatisfiedBy(_input).Should().BeFalse();
}
[TestCase(320, 256, 320, false)]
[TestCase(256, 192, 320, false)]
[TestCase(96, 128, 320, true)]
public void should_match_negated_bitrate_range(int bitrate, int min, int max, bool expected)
{
_input.MediaInfo.AudioBitrate = bitrate;
var spec = new AudioBitrateSpecification
{
Min = min,
Max = max,
Negate = true
};
spec.IsSatisfiedBy(_input).Should().Be(expected);
}
[Test]
public void should_be_valid_when_min_is_zero()
{
var spec = new AudioBitrateSpecification
{
Min = 0,
Max = 320
};
var result = spec.Validate();
result.IsValid.Should().BeTrue();
}
[Test]
public void should_be_valid_when_max_equals_min()
{
var spec = new AudioBitrateSpecification
{
Min = 320,
Max = 320
};
var result = spec.Validate();
result.IsValid.Should().BeTrue();
}
[Test]
public void should_be_invalid_when_max_is_less_than_min()
{
var spec = new AudioBitrateSpecification
{
Min = 320,
Max = 256
};
var result = spec.Validate();
result.IsValid.Should().BeFalse();
}
[Test]
public void should_be_invalid_when_min_is_negative()
{
var spec = new AudioBitrateSpecification
{
Min = -1,
Max = 320
};
var result = spec.Validate();
result.IsValid.Should().BeFalse();
}
}
}

View file

@ -0,0 +1,127 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.CustomFormats.Specifications
{
[TestFixture]
public class AudioChannelsSpecificationFixture : CoreTest
{
private CustomFormatInput _input;
[SetUp]
public void Setup()
{
_input = new CustomFormatInput
{
AlbumInfo = new ParsedAlbumInfo(),
MediaInfo = new MediaInfoModel()
};
}
[TestCase(2, 2, 2, true)]
[TestCase(2, 1, 2, true)]
[TestCase(6, 2, 8, true)]
[TestCase(8, 6, 8, true)]
[TestCase(1, 2, 6, false)]
[TestCase(8, 0, 6, false)]
[TestCase(0, 2, 6, false)]
public void should_match_channels_range(int channels, int min, int max, bool expected)
{
_input.MediaInfo.AudioChannels = channels;
var spec = new AudioChannelsSpecification
{
Min = min,
Max = max
};
spec.IsSatisfiedBy(_input).Should().Be(expected);
}
[Test]
public void should_not_match_when_media_info_is_null()
{
_input.MediaInfo = null;
var spec = new AudioChannelsSpecification
{
Min = 2,
Max = 6
};
spec.IsSatisfiedBy(_input).Should().BeFalse();
}
[TestCase(2, 2, 2, false)]
[TestCase(2, 1, 2, false)]
[TestCase(1, 2, 6, true)]
public void should_match_negated_channels_range(int channels, int min, int max, bool expected)
{
_input.MediaInfo.AudioChannels = channels;
var spec = new AudioChannelsSpecification
{
Min = min,
Max = max,
Negate = true
};
spec.IsSatisfiedBy(_input).Should().Be(expected);
}
[Test]
public void should_be_valid_when_min_is_zero()
{
var spec = new AudioChannelsSpecification
{
Min = 0,
Max = 6
};
var result = spec.Validate();
result.IsValid.Should().BeTrue();
}
[Test]
public void should_be_valid_when_max_equals_min()
{
var spec = new AudioChannelsSpecification
{
Min = 2,
Max = 2
};
var result = spec.Validate();
result.IsValid.Should().BeTrue();
}
[Test]
public void should_be_invalid_when_max_is_less_than_min()
{
var spec = new AudioChannelsSpecification
{
Min = 6,
Max = 2
};
var result = spec.Validate();
result.IsValid.Should().BeFalse();
}
[Test]
public void should_be_invalid_when_min_is_negative()
{
var spec = new AudioChannelsSpecification
{
Min = -1,
Max = 6
};
var result = spec.Validate();
result.IsValid.Should().BeFalse();
}
}
}

View file

@ -0,0 +1,179 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.CustomFormats.Specifications
{
[TestFixture]
public class AudioCodecSpecificationFixture : CoreTest
{
private CustomFormatInput _input;
[SetUp]
public void Setup()
{
_input = new CustomFormatInput
{
AlbumInfo = new ParsedAlbumInfo(),
MediaInfo = new MediaInfoModel()
};
}
[TestCase("FLAC", "FLAC", true)]
[TestCase("MP3", "MP3", true)]
[TestCase("AAC", "AAC", true)]
[TestCase("ALAC", "ALAC", true)]
[TestCase("OPUS", "OPUS", true)]
[TestCase("WavPack", "WavPack", true)]
[TestCase("APE", "APE", true)]
[TestCase("FLAC", "MP3", false)]
[TestCase("MP3", "FLAC", false)]
public void should_match_codec_exact(string actualCodec, string pattern, bool expected)
{
_input.MediaInfo.AudioFormat = actualCodec;
var spec = new AudioCodecSpecification
{
Value = pattern
};
spec.IsSatisfiedBy(_input).Should().Be(expected);
}
[TestCase("FLAC", "flac", true)]
[TestCase("mp3", "MP3", true)]
[TestCase("AAC", "aac", true)]
public void should_match_codec_case_insensitive(string actualCodec, string pattern, bool expected)
{
_input.MediaInfo.AudioFormat = actualCodec;
var spec = new AudioCodecSpecification
{
Value = pattern
};
spec.IsSatisfiedBy(_input).Should().Be(expected);
}
[TestCase("FLAC", "FLAC|MP3", true)]
[TestCase("MP3", "FLAC|MP3", true)]
[TestCase("AAC", "FLAC|MP3", false)]
[TestCase("ALAC", "ALAC|OPUS|WavPack", true)]
public void should_match_codec_regex(string actualCodec, string pattern, bool expected)
{
_input.MediaInfo.AudioFormat = actualCodec;
var spec = new AudioCodecSpecification
{
Value = pattern
};
spec.IsSatisfiedBy(_input).Should().Be(expected);
}
[Test]
public void should_not_match_when_media_info_is_null()
{
_input.MediaInfo = null;
var spec = new AudioCodecSpecification
{
Value = "FLAC"
};
spec.IsSatisfiedBy(_input).Should().BeFalse();
}
[Test]
public void should_not_match_when_audio_format_is_null()
{
_input.MediaInfo.AudioFormat = null;
var spec = new AudioCodecSpecification
{
Value = "FLAC"
};
spec.IsSatisfiedBy(_input).Should().BeFalse();
}
[Test]
public void should_not_match_when_audio_format_is_empty()
{
_input.MediaInfo.AudioFormat = string.Empty;
var spec = new AudioCodecSpecification
{
Value = "FLAC"
};
spec.IsSatisfiedBy(_input).Should().BeFalse();
}
[TestCase("FLAC", "FLAC", false)]
[TestCase("MP3", "MP3", false)]
[TestCase("FLAC", "MP3", true)]
public void should_match_negated_codec(string actualCodec, string pattern, bool expected)
{
_input.MediaInfo.AudioFormat = actualCodec;
var spec = new AudioCodecSpecification
{
Value = pattern,
Negate = true
};
spec.IsSatisfiedBy(_input).Should().Be(expected);
}
[Test]
public void should_be_valid_when_value_is_set()
{
var spec = new AudioCodecSpecification
{
Value = "FLAC"
};
var result = spec.Validate();
result.IsValid.Should().BeTrue();
}
[Test]
public void should_be_invalid_when_value_is_empty()
{
var spec = new AudioCodecSpecification
{
Value = string.Empty
};
var result = spec.Validate();
result.IsValid.Should().BeFalse();
}
[Test]
public void should_be_invalid_when_value_is_whitespace()
{
var spec = new AudioCodecSpecification
{
Value = " "
};
var result = spec.Validate();
result.IsValid.Should().BeFalse();
}
[Test]
public void should_be_invalid_when_value_is_null()
{
var spec = new AudioCodecSpecification
{
Value = null
};
var result = spec.Validate();
result.IsValid.Should().BeFalse();
}
}
}

View file

@ -0,0 +1,127 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.CustomFormats.Specifications
{
[TestFixture]
public class AudioSampleRateSpecificationFixture : CoreTest
{
private CustomFormatInput _input;
[SetUp]
public void Setup()
{
_input = new CustomFormatInput
{
AlbumInfo = new ParsedAlbumInfo(),
MediaInfo = new MediaInfoModel()
};
}
[TestCase(44100, 40000, 48000, true)]
[TestCase(48000, 40000, 50000, true)]
[TestCase(96000, 88200, 192000, true)]
[TestCase(192000, 176400, 192000, true)]
[TestCase(44100, 48000, 96000, false)]
[TestCase(192000, 0, 96000, false)]
[TestCase(0, 44100, 96000, false)]
public void should_match_sample_rate_range(int sampleRate, int min, int max, bool expected)
{
_input.MediaInfo.AudioSampleRate = sampleRate;
var spec = new AudioSampleRateSpecification
{
Min = min,
Max = max
};
spec.IsSatisfiedBy(_input).Should().Be(expected);
}
[Test]
public void should_not_match_when_media_info_is_null()
{
_input.MediaInfo = null;
var spec = new AudioSampleRateSpecification
{
Min = 44100,
Max = 48000
};
spec.IsSatisfiedBy(_input).Should().BeFalse();
}
[TestCase(44100, 40000, 48000, false)]
[TestCase(48000, 40000, 50000, false)]
[TestCase(44100, 48000, 96000, true)]
public void should_match_negated_sample_rate_range(int sampleRate, int min, int max, bool expected)
{
_input.MediaInfo.AudioSampleRate = sampleRate;
var spec = new AudioSampleRateSpecification
{
Min = min,
Max = max,
Negate = true
};
spec.IsSatisfiedBy(_input).Should().Be(expected);
}
[Test]
public void should_be_valid_when_min_is_zero()
{
var spec = new AudioSampleRateSpecification
{
Min = 0,
Max = 48000
};
var result = spec.Validate();
result.IsValid.Should().BeTrue();
}
[Test]
public void should_be_valid_when_max_equals_min()
{
var spec = new AudioSampleRateSpecification
{
Min = 44100,
Max = 44100
};
var result = spec.Validate();
result.IsValid.Should().BeTrue();
}
[Test]
public void should_be_invalid_when_max_is_less_than_min()
{
var spec = new AudioSampleRateSpecification
{
Min = 48000,
Max = 44100
};
var result = spec.Validate();
result.IsValid.Should().BeFalse();
}
[Test]
public void should_be_invalid_when_min_is_negative()
{
var spec = new AudioSampleRateSpecification
{
Min = -1,
Max = 48000
};
var result = spec.Validate();
result.IsValid.Should().BeFalse();
}
}
}

View file

@ -1,8 +1,11 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.TrackImport.Specifications;
@ -10,6 +13,7 @@
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.CustomFormats;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Specifications
@ -20,14 +24,22 @@ public class UpgradeSpecificationFixture : CoreTest<UpgradeSpecification>
private Artist _artist;
private Album _album;
private LocalTrack _localTrack;
private CustomFormat _customFormat1;
private CustomFormat _customFormat2;
[SetUp]
public void Setup()
{
_customFormat1 = new CustomFormat("My Format", new ReleaseTitleSpecification()) { Id = 1 };
_customFormat2 = new CustomFormat("My Other Format", new ReleaseTitleSpecification()) { Id = 2 };
CustomFormatsTestHelpers.GivenCustomFormats(_customFormat1, _customFormat2);
_artist = Builder<Artist>.CreateNew()
.With(e => e.QualityProfile = new QualityProfile
{
Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_customFormat1.Name, _customFormat2.Name)
}).Build();
_album = Builder<Album>.CreateNew().Build();
@ -236,5 +248,142 @@ public void should_return_true_if_track_file_is_null()
Subject.IsSatisfiedBy(_localTrack, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_false_if_custom_format_score_is_lower()
{
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<LocalTrack>()))
.Returns(new List<CustomFormat>());
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<TrackFile>(), It.IsAny<Artist>()))
.Returns(new List<CustomFormat> { _customFormat1 });
_localTrack.Tracks = Builder<Track>.CreateListOfSize(1)
.All()
.With(e => e.TrackFileId = 1)
.With(e => e.TrackFile = new LazyLoaded<TrackFile>(
new TrackFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(version: 1))
}))
.Build()
.ToList();
Subject.IsSatisfiedBy(_localTrack, null).Accepted.Should().BeFalse();
}
[Test]
public void should_return_true_if_custom_format_score_is_higher()
{
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<LocalTrack>()))
.Returns(new List<CustomFormat> { _customFormat1 });
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<TrackFile>(), It.IsAny<Artist>()))
.Returns(new List<CustomFormat>());
_localTrack.Tracks = Builder<Track>.CreateListOfSize(1)
.All()
.With(e => e.TrackFileId = 1)
.With(e => e.TrackFile = new LazyLoaded<TrackFile>(
new TrackFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(version: 1))
}))
.Build()
.ToList();
Subject.IsSatisfiedBy(_localTrack, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_if_custom_format_score_is_equal_and_does_not_prefer_propers()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.DoNotPrefer);
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<LocalTrack>()))
.Returns(new List<CustomFormat> { _customFormat1 });
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<TrackFile>(), It.IsAny<Artist>()))
.Returns(new List<CustomFormat> { _customFormat1 });
_localTrack.Tracks = Builder<Track>.CreateListOfSize(1)
.All()
.With(e => e.TrackFileId = 1)
.With(e => e.TrackFile = new LazyLoaded<TrackFile>(
new TrackFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2))
}))
.Build()
.ToList();
Subject.IsSatisfiedBy(_localTrack, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_false_if_custom_format_score_is_equal_and_not_a_revision_upgrade()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.PreferAndUpgrade);
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<LocalTrack>()))
.Returns(new List<CustomFormat> { _customFormat1 });
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<TrackFile>(), It.IsAny<Artist>()))
.Returns(new List<CustomFormat> { _customFormat1 });
_localTrack.Tracks = Builder<Track>.CreateListOfSize(1)
.All()
.With(e => e.TrackFileId = 1)
.With(e => e.TrackFile = new LazyLoaded<TrackFile>(
new TrackFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2))
}))
.Build()
.ToList();
Subject.IsSatisfiedBy(_localTrack, null).Accepted.Should().BeFalse();
}
[Test]
public void should_return_true_if_custom_formats_are_better_even_with_lower_revision()
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.DownloadPropersAndRepacks)
.Returns(ProperDownloadTypes.PreferAndUpgrade);
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<LocalTrack>()))
.Returns(new List<CustomFormat> { _customFormat1, _customFormat2 });
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(s => s.ParseCustomFormat(It.IsAny<TrackFile>(), It.IsAny<Artist>()))
.Returns(new List<CustomFormat> { _customFormat1 });
_localTrack.Tracks = Builder<Track>.CreateListOfSize(1)
.All()
.With(e => e.TrackFileId = 1)
.With(e => e.TrackFile = new LazyLoaded<TrackFile>(
new TrackFile
{
Quality = new QualityModel(Quality.MP3_256, new Revision(version: 2))
}))
.Build()
.ToList();
Subject.IsSatisfiedBy(_localTrack, null).Accepted.Should().BeTrue();
}
}
}

View file

@ -143,6 +143,16 @@
"AuthenticationRequiredWarning": "To prevent remote access without authentication, {appName} now requires authentication to be enabled. You can optionally disable authentication from local addresses.",
"Auto": "Auto",
"AutoAdd": "Auto Add",
"AudioBitDepth": "Audio Bit Depth",
"AudioBitDepthHelpText": "The bit depth of the audio (e.g., 16 for CD quality, 24 for Hi-Res)",
"AudioBitrate": "Audio Bitrate",
"AudioBitrateHelpText": "The bitrate of the audio in kbps (e.g., 320 for MP3)",
"AudioChannels": "Audio Channels",
"AudioChannelsHelpText": "The number of audio channels (e.g., 2 for stereo, 6 for 5.1)",
"AudioCodec": "Audio Codec",
"AudioCodecHelpText": "The audio codec name or regex pattern (e.g., FLAC, MP3, AAC)",
"AudioSampleRate": "Audio Sample Rate",
"AudioSampleRateHelpText": "The sample rate of the audio in Hz (e.g., 44100 for CD quality, 192000 for Hi-Res)",
"AutoRedownloadFailed": "Redownload Failed",
"AutoRedownloadFailedFromInteractiveSearch": "Redownload Failed from Interactive Search",
"AutoRedownloadFailedFromInteractiveSearchHelpText": "Automatically search for and attempt to download a different release when failed release was grabbed from interactive search",