mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-01-17 10:17:55 -05:00
1025 lines
36 KiB
C#
1025 lines
36 KiB
C#
using Cleanuparr.Domain.Enums;
|
|
using Cleanuparr.Infrastructure.Features.Context;
|
|
using Cleanuparr.Infrastructure.Features.DownloadClient;
|
|
using Cleanuparr.Infrastructure.Features.DownloadClient.QBittorrent;
|
|
using Cleanuparr.Persistence.Models.Configuration.QueueCleaner;
|
|
using Moq;
|
|
using Newtonsoft.Json.Linq;
|
|
using QBittorrent.Client;
|
|
using Xunit;
|
|
|
|
namespace Cleanuparr.Infrastructure.Tests.Features.DownloadClient;
|
|
|
|
public class QBitServiceTests : IClassFixture<QBitServiceFixture>
|
|
{
|
|
private readonly QBitServiceFixture _fixture;
|
|
|
|
public QBitServiceTests(QBitServiceFixture fixture)
|
|
{
|
|
_fixture = fixture;
|
|
_fixture.ResetMocks();
|
|
}
|
|
|
|
public class ShouldRemoveFromArrQueueAsync_BasicScenarios : QBitServiceTests
|
|
{
|
|
public ShouldRemoveFromArrQueueAsync_BasicScenarios(QBitServiceFixture fixture) : base(fixture)
|
|
{
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TorrentNotFound_ReturnsEmptyResult()
|
|
{
|
|
// Arrange
|
|
const string hash = "nonexistent";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(Array.Empty<TorrentInfo>());
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.False(result.Found);
|
|
Assert.False(result.ShouldRemove);
|
|
Assert.Equal(DeleteReason.None, result.DeleteReason);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TorrentIsIgnored_ReturnsEmptyResult_WithFound()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
const string ignoredCategory = "ignored-category";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
Category = ignoredCategory,
|
|
State = TorrentState.Downloading
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, new[] { ignoredCategory });
|
|
|
|
// Assert
|
|
Assert.True(result.Found);
|
|
Assert.False(result.ShouldRemove);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TorrentFound_SetsIsPrivateCorrectly_WhenPrivate()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.Downloading,
|
|
DownloadSpeed = 1000
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(true) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Normal }
|
|
});
|
|
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateSlowRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((false, DeleteReason.None, false));
|
|
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateStallRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((false, DeleteReason.None, false));
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.True(result.Found);
|
|
Assert.True(result.IsPrivate);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TorrentFound_SetsIsPrivateCorrectly_WhenPublic()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.Downloading,
|
|
DownloadSpeed = 1000
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Normal }
|
|
});
|
|
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateSlowRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((false, DeleteReason.None, false));
|
|
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateStallRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((false, DeleteReason.None, false));
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.True(result.Found);
|
|
Assert.False(result.IsPrivate);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TorrentPropertiesNotFound_ReturnsEmptyResult()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.Downloading
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync((TorrentProperties?)null); // Properties not found
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.False(result.Found);
|
|
Assert.False(result.ShouldRemove);
|
|
Assert.False(result.IsPrivate);
|
|
Assert.Equal(DeleteReason.None, result.DeleteReason);
|
|
Assert.False(result.DeleteFromClient);
|
|
}
|
|
}
|
|
|
|
public class ShouldRemoveFromArrQueueAsync_AllFilesSkippedScenarios : QBitServiceTests
|
|
{
|
|
public ShouldRemoveFromArrQueueAsync_AllFilesSkippedScenarios(QBitServiceFixture fixture) : base(fixture)
|
|
{
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AllFilesSkippedByQBit_WithNoDownload_DeletesFromClient()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.Downloading,
|
|
CompletionOn = DateTime.UtcNow,
|
|
Downloaded = 0 // No data downloaded
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Skip },
|
|
new TorrentContent { Index = 1, Priority = TorrentContentPriority.Skip }
|
|
});
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.True(result.ShouldRemove);
|
|
Assert.Equal(DeleteReason.AllFilesSkippedByQBit, result.DeleteReason);
|
|
Assert.True(result.DeleteFromClient);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AllFilesSkippedByUser_DeletesFromClient()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.Downloading,
|
|
Downloaded = 1000 // Some data downloaded
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Skip },
|
|
new TorrentContent { Index = 1, Priority = TorrentContentPriority.Skip }
|
|
});
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.True(result.ShouldRemove);
|
|
Assert.Equal(DeleteReason.AllFilesSkipped, result.DeleteReason);
|
|
Assert.True(result.DeleteFromClient);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SomeFilesWanted_DoesNotRemove()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.Downloading,
|
|
DownloadSpeed = 1000
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Skip },
|
|
new TorrentContent { Index = 1, Priority = TorrentContentPriority.Normal } // At least one wanted
|
|
});
|
|
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateSlowRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((false, DeleteReason.None, false));
|
|
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateStallRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((false, DeleteReason.None, false));
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.False(result.ShouldRemove);
|
|
}
|
|
}
|
|
|
|
public class ShouldRemoveFromArrQueueAsync_MetadataDownloadingScenarios : QBitServiceTests
|
|
{
|
|
public ShouldRemoveFromArrQueueAsync_MetadataDownloadingScenarios(QBitServiceFixture fixture) : base(fixture)
|
|
{
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DownloadingMetadata_WithStrikesEnabled_IncreasesStrikes()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var queueCleanerConfig = new QueueCleanerConfig
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
DownloadingMetadataMaxStrikes = 3
|
|
};
|
|
|
|
ContextProvider.Set(nameof(QueueCleanerConfig), queueCleanerConfig);
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.FetchingMetadata // Metadata downloading state
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Normal }
|
|
});
|
|
|
|
_fixture.Striker
|
|
.Setup(x => x.StrikeAndCheckLimit(hash, It.IsAny<string>(), (ushort)3, StrikeType.DownloadingMetadata))
|
|
.ReturnsAsync(false);
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.False(result.ShouldRemove);
|
|
_fixture.Striker.Verify(
|
|
x => x.StrikeAndCheckLimit(hash, It.IsAny<string>(), (ushort)3, StrikeType.DownloadingMetadata),
|
|
Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DownloadingMetadata_ExceedsMaxStrikes_RemovesFromQueue()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var queueCleanerConfig = new QueueCleanerConfig
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
DownloadingMetadataMaxStrikes = 3
|
|
};
|
|
|
|
ContextProvider.Set(nameof(QueueCleanerConfig), queueCleanerConfig);
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.FetchingMetadata
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Normal }
|
|
});
|
|
|
|
_fixture.Striker
|
|
.Setup(x => x.StrikeAndCheckLimit(hash, It.IsAny<string>(), (ushort)3, StrikeType.DownloadingMetadata))
|
|
.ReturnsAsync(true); // Strike limit exceeded
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.True(result.ShouldRemove);
|
|
Assert.Equal(DeleteReason.DownloadingMetadata, result.DeleteReason);
|
|
Assert.True(result.DeleteFromClient);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DownloadingMetadata_WithStrikesDisabled_DoesNotRemove()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var queueCleanerConfig = new QueueCleanerConfig
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
DownloadingMetadataMaxStrikes = 0 // Disabled
|
|
};
|
|
|
|
ContextProvider.Set(nameof(QueueCleanerConfig), queueCleanerConfig);
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.FetchingMetadata
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Normal }
|
|
});
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.False(result.ShouldRemove);
|
|
_fixture.Striker.Verify(
|
|
x => x.StrikeAndCheckLimit(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<ushort>(), It.IsAny<StrikeType>()),
|
|
Times.Never);
|
|
}
|
|
}
|
|
|
|
public class ShouldRemoveFromArrQueueAsync_SlowDownloadScenarios : QBitServiceTests
|
|
{
|
|
public ShouldRemoveFromArrQueueAsync_SlowDownloadScenarios(QBitServiceFixture fixture) : base(fixture)
|
|
{
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SlowDownload_NotInDownloadingState_SkipsCheck()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.Uploading, // Not downloading
|
|
DownloadSpeed = 100
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Normal }
|
|
});
|
|
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateStallRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((false, DeleteReason.None, false));
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.False(result.ShouldRemove);
|
|
_fixture.RuleEvaluator.Verify(
|
|
x => x.EvaluateSlowRulesAsync(It.IsAny<QBitItemWrapper>()),
|
|
Times.Never);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SlowDownload_ZeroSpeed_SkipsCheck()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.Downloading,
|
|
DownloadSpeed = 0 // Zero speed
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Normal }
|
|
});
|
|
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateStallRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((false, DeleteReason.None, false));
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.False(result.ShouldRemove);
|
|
_fixture.RuleEvaluator.Verify(
|
|
x => x.EvaluateSlowRulesAsync(It.IsAny<QBitItemWrapper>()),
|
|
Times.Never);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SlowDownload_MatchesRule_RemovesFromQueue()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.Downloading,
|
|
DownloadSpeed = 1000 // Some speed
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Normal }
|
|
});
|
|
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateSlowRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((true, DeleteReason.SlowSpeed, true)); // Rule matched
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.True(result.ShouldRemove);
|
|
Assert.Equal(DeleteReason.SlowSpeed, result.DeleteReason);
|
|
Assert.True(result.DeleteFromClient);
|
|
}
|
|
}
|
|
|
|
public class ShouldRemoveFromArrQueueAsync_StalledDownloadScenarios : QBitServiceTests
|
|
{
|
|
public ShouldRemoveFromArrQueueAsync_StalledDownloadScenarios(QBitServiceFixture fixture) : base(fixture)
|
|
{
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StalledDownload_NotInStalledState_SkipsCheck()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.Downloading, // Not stalled
|
|
DownloadSpeed = 1000
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Normal }
|
|
});
|
|
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateSlowRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((false, DeleteReason.None, false));
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.False(result.ShouldRemove);
|
|
_fixture.RuleEvaluator.Verify(
|
|
x => x.EvaluateStallRulesAsync(It.IsAny<QBitItemWrapper>()),
|
|
Times.Never);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StalledDownload_MatchesRule_RemovesFromQueue()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.StalledDownload // Stalled
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Normal }
|
|
});
|
|
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateStallRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((true, DeleteReason.Stalled, true)); // Rule matched
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.True(result.ShouldRemove);
|
|
Assert.Equal(DeleteReason.Stalled, result.DeleteReason);
|
|
Assert.True(result.DeleteFromClient);
|
|
}
|
|
}
|
|
|
|
public class ShouldRemoveFromArrQueueAsync_IntegrationScenarios : QBitServiceTests
|
|
{
|
|
public ShouldRemoveFromArrQueueAsync_IntegrationScenarios(QBitServiceFixture fixture) : base(fixture)
|
|
{
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SlowCheckPasses_ButStalledCheckFails_RemovesFromQueue()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.StalledDownload, // Stalled, not downloading
|
|
DownloadSpeed = 0
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Normal }
|
|
});
|
|
|
|
// Slow check is skipped because not in downloading state
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateStallRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((true, DeleteReason.Stalled, true));
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.True(result.ShouldRemove);
|
|
Assert.Equal(DeleteReason.Stalled, result.DeleteReason);
|
|
_fixture.RuleEvaluator.Verify(
|
|
x => x.EvaluateSlowRulesAsync(It.IsAny<QBitItemWrapper>()),
|
|
Times.Never); // Skipped
|
|
_fixture.RuleEvaluator.Verify(
|
|
x => x.EvaluateStallRulesAsync(It.IsAny<QBitItemWrapper>()),
|
|
Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BothChecksPass_DoesNotRemove()
|
|
{
|
|
// Arrange
|
|
const string hash = "test-hash";
|
|
var sut = _fixture.CreateSut();
|
|
|
|
var torrentInfo = new TorrentInfo
|
|
{
|
|
Hash = hash,
|
|
Name = "Test Torrent",
|
|
State = TorrentState.Downloading,
|
|
DownloadSpeed = 5000000 // Good speed
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentListAsync(It.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))))
|
|
.ReturnsAsync(new[] { torrentInfo });
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentTrackersAsync(hash))
|
|
.ReturnsAsync(Array.Empty<TorrentTracker>());
|
|
|
|
var properties = new TorrentProperties
|
|
{
|
|
AdditionalData = new Dictionary<string, JToken>
|
|
{
|
|
{ "is_private", JToken.FromObject(false) }
|
|
}
|
|
};
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentPropertiesAsync(hash))
|
|
.ReturnsAsync(properties);
|
|
|
|
_fixture.ClientWrapper
|
|
.Setup(x => x.GetTorrentContentsAsync(hash))
|
|
.ReturnsAsync(new[]
|
|
{
|
|
new TorrentContent { Index = 0, Priority = TorrentContentPriority.Normal }
|
|
});
|
|
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateSlowRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((false, DeleteReason.None, false));
|
|
|
|
_fixture.RuleEvaluator
|
|
.Setup(x => x.EvaluateStallRulesAsync(It.IsAny<QBitItemWrapper>()))
|
|
.ReturnsAsync((false, DeleteReason.None, false));
|
|
|
|
// Act
|
|
var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>());
|
|
|
|
// Assert
|
|
Assert.False(result.ShouldRemove);
|
|
Assert.Equal(DeleteReason.None, result.DeleteReason);
|
|
}
|
|
}
|
|
}
|