v1.4.1: MAJOR FIX - Moved from Redis to Valkey, added migration service to support, Utilizing Hi-Fi API 2.7 with ISRC search, preserve local item json objects, add a quality fallback, added "transcoding" support that just reduces the fetched quality, while still downloading at the quality set in the .env, introduced real-time download visualizer on web-ui (not complete), move some stuff from json to redis, better retry logic, configurable timeouts per provider

This commit is contained in:
2026-03-23 11:20:28 -04:00
parent 299cb025f1
commit d4230a2f79
52 changed files with 3294 additions and 611 deletions
@@ -0,0 +1,115 @@
using allstarr.Models.Domain;
using allstarr.Models.Spotify;
using allstarr.Services.Common;
using System.Text.Json;
namespace allstarr.Tests;
public class SpotifyPlaylistCountHelperTests
{
[Fact]
public void ComputeServedItemCount_UsesExactCachedCount_WhenAvailable()
{
var matchedTracks = new List<MatchedTrack>
{
new() { MatchedSong = new Song { IsLocal = false } },
new() { MatchedSong = new Song { IsLocal = false } }
};
var count = SpotifyPlaylistCountHelper.ComputeServedItemCount(50, 9, matchedTracks);
Assert.Equal(50, count);
}
[Fact]
public void ComputeServedItemCount_FallsBackToLocalPlusExternalMatched()
{
var matchedTracks = new List<MatchedTrack>
{
new() { MatchedSong = new Song { IsLocal = true } },
new() { MatchedSong = new Song { IsLocal = false } },
new() { MatchedSong = new Song { IsLocal = false } }
};
var count = SpotifyPlaylistCountHelper.ComputeServedItemCount(null, 9, matchedTracks);
Assert.Equal(11, count);
}
[Fact]
public void CountExternalMatchedTracks_IgnoresLocalMatches()
{
var matchedTracks = new List<MatchedTrack>
{
new() { MatchedSong = new Song { IsLocal = true } },
new() { MatchedSong = new Song { IsLocal = false } },
new() { MatchedSong = new Song { IsLocal = false } }
};
Assert.Equal(2, SpotifyPlaylistCountHelper.CountExternalMatchedTracks(matchedTracks));
}
[Fact]
public void SumExternalMatchedRunTimeTicks_IgnoresLocalMatches()
{
var matchedTracks = new List<MatchedTrack>
{
new() { MatchedSong = new Song { IsLocal = true, Duration = 100 } },
new() { MatchedSong = new Song { IsLocal = false, Duration = 180 } },
new() { MatchedSong = new Song { IsLocal = false, Duration = 240 } }
};
var runTimeTicks = SpotifyPlaylistCountHelper.SumExternalMatchedRunTimeTicks(matchedTracks);
Assert.Equal((180L + 240L) * TimeSpan.TicksPerSecond, runTimeTicks);
}
[Fact]
public void SumCachedPlaylistRunTimeTicks_HandlesJsonElementsFromCache()
{
var cachedPlaylistItems = JsonSerializer.Deserialize<List<Dictionary<string, object?>>>("""
[
{ "RunTimeTicks": 1800000000 },
{ "RunTimeTicks": 2400000000 }
]
""")!;
var runTimeTicks = SpotifyPlaylistCountHelper.SumCachedPlaylistRunTimeTicks(cachedPlaylistItems);
Assert.Equal(4200000000L, runTimeTicks);
}
[Fact]
public void ComputeServedRunTimeTicks_UsesExactCachedRuntime_WhenAvailable()
{
var matchedTracks = new List<MatchedTrack>
{
new() { MatchedSong = new Song { IsLocal = false, Duration = 180 } }
};
var runTimeTicks = SpotifyPlaylistCountHelper.ComputeServedRunTimeTicks(
5000000000L,
900000000L,
matchedTracks);
Assert.Equal(5000000000L, runTimeTicks);
}
[Fact]
public void ComputeServedRunTimeTicks_FallsBackToLocalPlusExternalMatched()
{
var matchedTracks = new List<MatchedTrack>
{
new() { MatchedSong = new Song { IsLocal = true, Duration = 100 } },
new() { MatchedSong = new Song { IsLocal = false, Duration = 180 } },
new() { MatchedSong = new Song { IsLocal = false, Duration = 240 } }
};
var runTimeTicks = SpotifyPlaylistCountHelper.ComputeServedRunTimeTicks(
null,
900000000L,
matchedTracks);
Assert.Equal(5100000000L, runTimeTicks);
}
}