Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions server/RdtClient.Data/Data/TorrentData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public async Task UpdateRdData(Torrent torrent)
dbTorrent.RdSpeed = torrent.RdSpeed;
dbTorrent.RdSeeders = torrent.RdSeeders;
dbTorrent.RdFiles = torrent.RdFiles;
dbTorrent.ClientKind = torrent.ClientKind;

await dataContext.SaveChangesAsync();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class DebridClientTorrent
public String? Host { get; set; }
public Int64 Split { get; set; }
public Int64 Progress { get; set; }
public Double ProgressFraction { get; set; }
public String? Status { get; set; }
public String? Message { get; set; }
public Int64 StatusCode { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,46 @@ public async Task Delete_ShouldRemovePayloadRow()
Assert.False(await verifyContext.TorrentPayloads.AnyAsync(m => m.TorrentId == torrentId));
}

[Fact]
public async Task UpdateRdData_ShouldPersistClientKind()
{
var torrentId = Guid.NewGuid();

await using (var context = CreateContext())
{
await context.Database.EnsureCreatedAsync();

context.Torrents.Add(new()
{
TorrentId = torrentId,
Hash = "hash-client-kind",
Added = DateTimeOffset.UtcNow,
Type = DownloadType.Torrent
});

await context.SaveChangesAsync();
}

await using (var updateContext = CreateContext())
{
var torrentData = new TorrentData(updateContext);

await torrentData.UpdateRdData(new()
{
TorrentId = torrentId,
ClientKind = Provider.Premiumize,
RdProgress = 42,
RdStatus = TorrentStatus.Downloading
});
}

await using var verifyContext = CreateContext();
var torrent = await verifyContext.Torrents.SingleAsync(m => m.TorrentId == torrentId);

Assert.Equal(Provider.Premiumize, torrent.ClientKind);
Assert.Equal(42, torrent.RdProgress);
}

public Task InitializeAsync()
{
return Task.CompletedTask;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using RdtClient.Service.Services;

namespace RdtClient.Service.Test.Services;

public class PremiumizeProgressTrackerTest
{
[Fact]
public void Update_WhenProgressIncreases_IsNotStalledAndHasSpeed()
{
var tracker = new PremiumizeProgressTracker();
var torrentId = Guid.NewGuid();

tracker.Update(torrentId, 0.1);
Thread.Sleep(50);
tracker.Update(torrentId, 0.2);

Assert.False(tracker.IsStalled(torrentId));
Assert.True(tracker.GetSpeedBytesPerSec(torrentId) > 0);
Assert.True(tracker.GetProgressPerMin(torrentId) > 0);
}

[Fact]
public void Update_WhenNoProgressForStallThreshold_IsStalled()
{
var now = DateTimeOffset.UtcNow;
var tracker = new PremiumizeProgressTracker
{
UtcNowOverride = () => now
};
var torrentId = Guid.NewGuid();

tracker.Update(torrentId, 0.5);
now = now.Add(PremiumizeProgressTracker.StallThreshold);
tracker.Update(torrentId, 0.5);

Assert.True(tracker.IsStalled(torrentId));
}

[Fact]
public void Update_WhenProgressIncreasesAfterStallPeriod_ResetsStallTimer()
{
var now = DateTimeOffset.UtcNow;
var tracker = new PremiumizeProgressTracker
{
UtcNowOverride = () => now
};
var torrentId = Guid.NewGuid();

tracker.Update(torrentId, 0.5);
now = now.Add(PremiumizeProgressTracker.StallThreshold);
tracker.Update(torrentId, 0.5);

Assert.True(tracker.IsStalled(torrentId));

now = now.Add(TimeSpan.FromSeconds(1));
tracker.Update(torrentId, 0.6);

Assert.False(tracker.IsStalled(torrentId));
Assert.True(tracker.GetSpeedBytesPerSec(torrentId) > 0);
}

[Fact]
public void Remove_ClearsTrackedState()
{
var tracker = new PremiumizeProgressTracker();
var torrentId = Guid.NewGuid();

tracker.Update(torrentId, 0.5);
tracker.Remove(torrentId);

Assert.False(tracker.IsStalled(torrentId));
Assert.Null(tracker.GetSpeedBytesPerSec(torrentId));
Assert.Null(tracker.GetProgressPerMin(torrentId));
}
}
74 changes: 73 additions & 1 deletion server/RdtClient.Service.Test/Services/QBittorrentTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class QBittorrentTest
{
private readonly Mock<Authentication> _authenticationMock;
private readonly Mock<ILogger<QBittorrent>> _loggerMock;
private readonly PremiumizeProgressTracker _premiumizeProgressTracker;
private readonly QBittorrent _qBittorrent;
private readonly TestSettings _settings;
private readonly TorrentRunnerState _runnerState;
Expand All @@ -22,10 +23,11 @@ public QBittorrentTest()
_loggerMock = new();
_settings = new();
_runnerState = new();
_premiumizeProgressTracker = new();
_torrentsMock = new(null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, _settings, _runnerState);
_authenticationMock = new(null!, null!, null!);

_qBittorrent = new(_loggerMock.Object, _settings, _authenticationMock.Object, _torrentsMock.Object, null!, _runnerState);
_qBittorrent = new(_loggerMock.Object, _settings, _authenticationMock.Object, _torrentsMock.Object, null!, _runnerState, _premiumizeProgressTracker);
}

[Fact]
Expand Down Expand Up @@ -406,4 +408,74 @@ public async Task TorrentInfo_ShouldUseExistingCompletedContentRoot_WhenSelected
}
}
}

[Fact]
public async Task TorrentInfo_PremiumizeWithMovingProgress_ReportsDownloadingWithSyntheticSize()
{
// Arrange
var torrentId = Guid.NewGuid();

_premiumizeProgressTracker.Update(torrentId, 0.5);
_premiumizeProgressTracker.Update(torrentId, 0.6);

var torrent = new Torrent
{
TorrentId = torrentId,
Hash = "hash1",
RdName = "Premiumize Torrent",
ClientKind = Provider.Premiumize,
RdProgress = 60,
RdStatus = TorrentStatus.Downloading,
RdSeeders = null,
Type = DownloadType.Torrent,
Added = DateTimeOffset.UtcNow
};

_torrentsMock.Setup(m => m.Get()).ReturnsAsync([torrent]);

// Act
var result = await _qBittorrent.TorrentInfo();

// Assert
Assert.Single(result);
Assert.Equal("downloading", result[0].State);
Assert.Equal(PremiumizeProgressTracker.SyntheticSizeBytes, result[0].Size);
Assert.Equal((Int64)(PremiumizeProgressTracker.SyntheticSizeBytes * 0.6), result[0].Downloaded);
Assert.True(result[0].Downloaded > 0);
}

[Fact]
public async Task TorrentInfo_PremiumizeWhenTrackerStalled_ReportsStalledDownloadState()
{
// Arrange
var torrentId = Guid.NewGuid();
var now = DateTimeOffset.UtcNow;

_premiumizeProgressTracker.UtcNowOverride = () => now;
_premiumizeProgressTracker.Update(torrentId, 0.5);
now = now.Add(PremiumizeProgressTracker.StallThreshold);
_premiumizeProgressTracker.Update(torrentId, 0.5);

var torrent = new Torrent
{
TorrentId = torrentId,
Hash = "hash1",
RdName = "Premiumize Torrent",
ClientKind = Provider.Premiumize,
RdProgress = 50,
RdStatus = TorrentStatus.Downloading,
RdSeeders = null,
Type = DownloadType.Torrent,
Added = DateTimeOffset.UtcNow
};

_torrentsMock.Setup(m => m.Get()).ReturnsAsync([torrent]);

// Act
var result = await _qBittorrent.TorrentInfo();

// Assert
Assert.Single(result);
Assert.Equal("stalledDL", result[0].State);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
using System.Text;
using Microsoft.Extensions.Logging;
using Moq;
using RdtClient.Data.Enums;
using RdtClient.Data.Models.Data;
using RdtClient.Data.Models.DebridClient;
using RdtClient.Data.Models.Internal;
using RdtClient.Service.Services;
using RdtClient.Service.Services.DebridClients;
using Torrent = RdtClient.Data.Models.Data.Torrent;

namespace RdtClient.Service.Test.Services.TorrentClients;

Expand All @@ -13,13 +17,15 @@ public class PremiumizeDebridClientTest
private readonly Mock<IDownloadableFileFilter> _fileFilterMock;
private readonly Mock<IHttpClientFactory> _httpClientFactoryMock;
private readonly Mock<ILogger<PremiumizeDebridClient>> _loggerMock;
private readonly PremiumizeProgressTracker _premiumizeProgressTracker;
private readonly TestSettings _settings;

public PremiumizeDebridClientTest()
{
_loggerMock = new();
_httpClientFactoryMock = new();
_fileFilterMock = new();
_premiumizeProgressTracker = new();
_settings = new();
_settings.Current.Provider.ApiKey = "test-api-key";
}
Expand Down Expand Up @@ -194,11 +200,80 @@ public async Task AddTorrentFile_UsesPremiumizeNetFileUpload_ReturnsTransferId()
Assert.Contains("torrent body", handler.RequestBody);
}

[Fact]
public async Task UpdateData_WhenRunning_UpdatesTrackerAndSetsDerivedSpeed()
{
// Arrange
var torrentId = Guid.NewGuid();
var client = CreateClient(new RecordingHttpMessageHandler(_ => JsonResponse("""[]""")));
var torrent = new Torrent
{
TorrentId = torrentId,
RdId = "transfer-1"
};
var transfer = new DebridClientTorrent
{
Id = "transfer-1",
Filename = "test.mkv",
Hash = "hash",
Progress = 60,
ProgressFraction = 0.6,
Status = "running",
Speed = 0,
Seeders = null
};

// Act
var result = await client.UpdateData(torrent, transfer);

// Assert
Assert.Equal(TorrentStatus.Downloading, result.RdStatus);
Assert.Null(result.RdSeeders);
Assert.False(_premiumizeProgressTracker.IsStalled(torrentId));
}

[Fact]
public async Task UpdateData_WhenFinished_RemovesTrackerEntry()
{
// Arrange
var torrentId = Guid.NewGuid();
var client = CreateClient(new RecordingHttpMessageHandler(_ => JsonResponse("""[]""")));
var torrent = new Torrent
{
TorrentId = torrentId,
RdId = "transfer-1"
};

await client.UpdateData(torrent, new DebridClientTorrent
{
Id = "transfer-1",
Filename = "test.mkv",
Hash = "hash",
Progress = 60,
ProgressFraction = 0.6,
Status = "running"
});

// Act
await client.UpdateData(torrent, new DebridClientTorrent
{
Id = "transfer-1",
Filename = "test.mkv",
Hash = "hash",
Progress = 100,
ProgressFraction = 1.0,
Status = "finished"
});

// Assert
Assert.Null(_premiumizeProgressTracker.GetSpeedBytesPerSec(torrentId));
}

private PremiumizeDebridClient CreateClient(RecordingHttpMessageHandler handler)
{
_httpClientFactoryMock.Setup(m => m.CreateClient(It.IsAny<String>())).Returns(new HttpClient(handler));

return new(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _settings);
return new(_loggerMock.Object, _httpClientFactoryMock.Object, _fileFilterMock.Object, _settings, _premiumizeProgressTracker);
}

private static HttpResponseMessage JsonResponse(String json, HttpStatusCode statusCode = HttpStatusCode.OK)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public async Task Tick_ShouldNotRequeueCompletedErrorTorrent()
var torrentRunner = new TorrentRunner(Mock.Of<ILogger<TorrentRunner>>(),
torrents,
new(null!),
new(null!, torrents),
new(null!, torrents, new PremiumizeProgressTracker()),
Mock.Of<IHttpClientFactory>(),
new RateLimitCoordinator(),
testSettings,
Expand Down
1 change: 1 addition & 0 deletions server/RdtClient.Service/DiConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static void RegisterRdtServices(this IServiceCollection services)
services.AddSingleton<IDownloadableFileFilter, DownloadableFileFilter>();
services.AddSingleton<ITrackerListGrabber, TrackerListGrabber>();
services.AddSingleton<IEnricher, Enricher>();
services.AddSingleton<IPremiumizeProgressTracker, PremiumizeProgressTracker>();

services.AddScoped<IAuthorizationHandler, AuthSettingHandler>();
services.AddScoped<IAuthorizationHandler, SabnzbdHandler>();
Expand Down
Loading