From 60263ca685ce3d5093be5b3e1c8592832ad28cdc Mon Sep 17 00:00:00 2001 From: MentalBlank Date: Mon, 26 May 2025 00:39:47 +1000 Subject: [PATCH] Tracker enrichment test changes --- .../Services/EnricherTest.cs | 665 +++++++++++------- 1 file changed, 414 insertions(+), 251 deletions(-) diff --git a/server/RdtClient.Service.Test/Services/EnricherTest.cs b/server/RdtClient.Service.Test/Services/EnricherTest.cs index dfd1933..95a36a8 100644 --- a/server/RdtClient.Service.Test/Services/EnricherTest.cs +++ b/server/RdtClient.Service.Test/Services/EnricherTest.cs @@ -26,8 +26,103 @@ public class EnricherTest : IDisposable private const String TestMagnetLink = "magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567&dn=TestFile&tr=http%3A%2F%2Ftracker1.com%2Fannounce&tr=http%3A%2F%2Ftracker2.com%2Fannounce"; + // Helper methods for creating BEncodedDictionary objects for torrents + private static BEncodedDictionary CreateStandardTorrentDict(String announceUrl, List? announceListTier = null) + { + var torrentDict = new BEncodedDictionary + { + ["announce"] = new BEncodedString(announceUrl) + }; + + var finalAnnounceList = new BEncodedList(); + + if (announceListTier != null) + { + var tier = new BEncodedList(); + + foreach (var tracker in announceListTier) + { + tier.Add(new BEncodedString(tracker)); + } + + finalAnnounceList.Add(tier); + } + else + { + finalAnnounceList.Add(new BEncodedList + { + new BEncodedString(announceUrl) + }); + } + + torrentDict["announce-list"] = finalAnnounceList; + + if (!torrentDict.ContainsKey("info")) + { + torrentDict["info"] = new BEncodedDictionary(); + } + + return torrentDict; + } + + private static BEncodedDictionary CreateTorrentDictWithOnlyAnnounce(String announceUrl) + { + return new BEncodedDictionary + { + ["announce"] = new BEncodedString(announceUrl), + ["info"] = new BEncodedDictionary() + }; + } + + private static BEncodedDictionary CreateTorrentDictWithNoTrackers() + { + return new BEncodedDictionary + { + ["info"] = new BEncodedDictionary() + }; + } + + private static BEncodedDictionary CreateTorrentDictWithComplexAnnounceList(String primaryAnnounceUrl, List> announceListTiers) + { + var torrentDict = new BEncodedDictionary + { + ["announce"] = new BEncodedString(primaryAnnounceUrl) + }; + + var bencodedAnnounceList = new BEncodedList(); + + foreach (var tier in announceListTiers) + { + var bencodedTier = new BEncodedList(); + + foreach (var tracker in tier) + { + bencodedTier.Add(new BEncodedString(tracker)); + } + + bencodedAnnounceList.Add(bencodedTier); + } + + torrentDict["announce-list"] = bencodedAnnounceList; + + if (!torrentDict.ContainsKey("info")) + { + torrentDict["info"] = new BEncodedDictionary(); + } + + return torrentDict; + } + + private void SetupTrackerListGrabber(String[] trackerList) + { + _trackerListGrabberMock + .Setup(t => t.GetTrackers()) + .ReturnsAsync(trackerList) + .Verifiable(); + } + [Fact] - public async Task EnrichMagnetLink_AddsNoTrackers_WhenNoTrackersFromTrackerGrabber() + public async Task EnrichMagnetLink_WhenNoTrackers_ReturnsOriginal() { // Arrange SetupTrackerListGrabber([]); @@ -43,18 +138,25 @@ public class EnricherTest : IDisposable [Theory] [InlineData("magnet:?xt=urn:btih:HASH", - new[] - { - "http://new-tracker.com/announce" - }, - "magnet:?xt=urn:btih:HASH&tr=http%3A%2F%2Fnew-tracker.com%2Fannounce")] + "magnet:?xt=urn:btih:HASH&tr=http%3A%2F%2Fnew-tracker.com%2Fannounce", + "http://new-tracker.com/announce")] [InlineData("magnet:?xt=urn:btih:HASH", - new[] - { - "http://tracker1.com/announce", "http://tracker2.com/announce" - }, - "magnet:?xt=urn:btih:HASH&tr=http%3A%2F%2Ftracker1.com%2Fannounce&tr=http%3A%2F%2Ftracker2.com%2Fannounce")] - public async Task EnrichMagnetLink_AddsTrackers_WhenTrackersFromTrackerGrabber_Theory(String magnetLink, String[] newTrackers, String expectedResult) + "magnet:?xt=urn:btih:HASH&tr=http%3A%2F%2Ftracker1.com%2Fannounce&tr=http%3A%2F%2Ftracker2.com%2Fannounce", + "http://tracker1.com/announce", + "http://tracker2.com/announce")] + [InlineData("magnet:?xt=urn:btih:HASH", "magnet:?xt=urn:btih:HASH&tr=udp%3A%2F%2Ftracker1", "udp://tracker1")] + [InlineData("magnet:?xt=urn:btih:HASH", "magnet:?xt=urn:btih:HASH&tr=udp%3A%2F%2Ftracker1&tr=udp%3A%2F%2Ftracker2", "udp://tracker1", "udp://tracker2")] + [InlineData("magnet:?xt=urn:btih:HASH", "magnet:?xt=urn:btih:HASH")] + [InlineData("magnet:?", + "magnet:?tr=udp%3A%2F%2Ftracker1", + "udp://tracker1")] + [InlineData("magnet:?", + "magnet:?tr=udp%3A%2F%2Ftracker1&tr=udp%3A%2F%2Ftracker2", + "udp://tracker1", + "udp://tracker2")] + [InlineData("magnet:?", + "magnet:?")] + public async Task EnrichMagnetLink_WhenTrackersProvided_AddsTrackers(String magnetLink, String expectedResult, params String[] newTrackers) { // Arrange SetupTrackerListGrabber(newTrackers); @@ -68,7 +170,7 @@ public class EnricherTest : IDisposable } [Fact] - public async Task EnrichMagnetLink_DoesNotAddDuplicateTrackers_WhenTrackersFromTrackerGrabberAlreadyPresent() + public async Task EnrichMagnetLink_WhenDuplicatesProvided_AddsUniqueTrackers() { // Arrange SetupTrackerListGrabber(["http://new-tracker.com/announce", "http://tracker1.com/announce"]); @@ -83,7 +185,7 @@ public class EnricherTest : IDisposable } [Fact] - public async Task EnrichMagnetLink_Throws_WhenTrackerGrabberThrows() + public async Task EnrichMagnetLink_WhenGrabberFails_ThrowsException() { // Arrange _trackerListGrabberMock @@ -97,175 +199,30 @@ public class EnricherTest : IDisposable } [Fact] - public async Task EnrichTorrentBytes_AddsTrackers_WhenTrackersFromTrackerGrabber() + public async Task EnrichMagnetLink_WhenTrackersAdded_PreservesParameters() { // Arrange - var originalTracker = "http://tracker1.com/announce"; - var newTracker = "http://new-tracker.com/announce"; + var magnetLink = "magnet:?xt=urn:btih:HASH&dn=MyFile&tr=udp%3A%2F%2Fexisting"; - var torrentDict = new BEncodedDictionary + var newTrackers = new[] { - ["announce"] = new BEncodedString(originalTracker), - ["announce-list"] = new BEncodedList - { - new BEncodedList - { - new BEncodedString(originalTracker) - } - } + "udp://new1", "udp://existing" }; - var originalTorrentBytes = torrentDict.Encode(); - - SetupTrackerListGrabber([newTracker]); + SetupTrackerListGrabber(newTrackers); var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); // Act - var enrichedBytes = await enricher.EnrichTorrentBytes(originalTorrentBytes); - var enrichedDict = BEncodedValue.Decode(enrichedBytes); + var result = await enricher.EnrichMagnetLink(magnetLink); // Assert - Assert.True(enrichedDict.ContainsKey("announce")); - Assert.True(enrichedDict.ContainsKey("announce-list")); - - var announceList = (BEncodedList)enrichedDict["announce-list"]; - var flattened = announceList.Cast().SelectMany(l => l.Cast().Select(s => s.Text)).ToList(); - - Assert.Contains(originalTracker, flattened); - Assert.Contains(newTracker, flattened); - - var announce = ((BEncodedString)enrichedDict["announce"]).Text; - Assert.Equal(flattened.First(), announce); - } - - private void SetupTrackerListGrabber(String[] trackerList) - { - _trackerListGrabberMock - .Setup(t => t.GetTrackers()) - .ReturnsAsync(trackerList) - .Verifiable(); + var queryParams = System.Web.HttpUtility.ParseQueryString(new Uri(result).Query); + Assert.Equal("urn:btih:HASH", queryParams["xt"]); + Assert.Equal("MyFile", queryParams["dn"]); } [Fact] - public async Task EnrichTorrentBytes_DoesNotAddDuplicateTrackers() - { - // Arrange - var originalTracker = "http://tracker1.com/announce"; - var duplicateTracker = "http://tracker1.com/announce"; - - var torrentDict = new BEncodedDictionary - { - ["announce"] = new BEncodedString(originalTracker), - ["announce-list"] = new BEncodedList - { - new BEncodedList - { - new BEncodedString(originalTracker) - } - } - }; - - var originalTorrentBytes = torrentDict.Encode(); - - SetupTrackerListGrabber([duplicateTracker]); - var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); - - // Act - var enrichedBytes = await enricher.EnrichTorrentBytes(originalTorrentBytes); - var enrichedDict = BEncodedValue.Decode(enrichedBytes); - - // Assert - var announceList = (BEncodedList)enrichedDict["announce-list"]; - var flattened = announceList.Cast().SelectMany(l => l.Cast().Select(s => s.Text)).ToList(); - - Assert.Single(flattened); - Assert.Contains(originalTracker, flattened); - } - - [Fact] - public async Task EnrichTorrentBytes_AddsTrackers_WhenNoAnnounceListPresent() - { - // Arrange - var originalTracker = "http://tracker1.com/announce"; - var newTracker = "http://new-tracker.com/announce"; - - var torrentDict = new BEncodedDictionary - { - ["announce"] = new BEncodedString(originalTracker) - - // No "announce-list" - }; - - var originalTorrentBytes = torrentDict.Encode(); - - SetupTrackerListGrabber([newTracker]); - var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); - - // Act - var enrichedBytes = await enricher.EnrichTorrentBytes(originalTorrentBytes); - var enrichedDict = BEncodedValue.Decode(enrichedBytes); - - // Assert - Assert.True(enrichedDict.ContainsKey("announce-list")); - var announceList = (BEncodedList)enrichedDict["announce-list"]; - var flattened = announceList.Cast().SelectMany(l => l.Cast().Select(s => s.Text)).ToList(); - - Assert.Contains(originalTracker, flattened); - Assert.Contains(newTracker, flattened); - } - - [Fact] - public async Task EnrichTorrentBytes_Throws_WhenTrackerGrabberThrows() - { - // Arrange - _trackerListGrabberMock - .Setup(t => t.GetTrackers()) - .ThrowsAsync(new InvalidOperationException("Unable to fetch tracker list for enrichment.")); - - var torrentDict = new BEncodedDictionary - { - ["announce"] = new BEncodedString("http://tracker1.com/announce"), - ["announce-list"] = new BEncodedList - { - new BEncodedList - { - new BEncodedString("http://tracker1.com/announce") - } - } - }; - - var originalTorrentBytes = torrentDict.Encode(); - - var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); - - // Act & Assert - await Assert.ThrowsAsync(() => enricher.EnrichTorrentBytes(originalTorrentBytes)); - } - - [Fact] - public async Task EnrichTorrentBytes_ThrowsArgumentException_OnNullOrEmptyInput() - { - var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); - - await Assert.ThrowsAsync(() => enricher.EnrichTorrentBytes(null!)); - await Assert.ThrowsAsync(() => enricher.EnrichTorrentBytes([])); - } - - [Fact] - public async Task EnrichTorrentBytes_ThrowsInvalidOperationException_OnNonTorrentBytes() - { - var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); - - var notTorrent = new Byte[] - { - 1, 2, 3, 4, 5 - }; - - await Assert.ThrowsAsync(() => enricher.EnrichTorrentBytes(notTorrent)); - } - - [Fact] - public async Task EnrichMagnetLink_WithExistingTrackersAndOtherParameters() + public async Task EnrichMagnetLink_WhenTrackersAdded_IncludesAllTrackers() { // Arrange var magnetLink = "magnet:?xt=urn:btih:HASH&dn=MyFile&tr=udp%3A%2F%2Fexisting"; @@ -285,99 +242,267 @@ public class EnricherTest : IDisposable var queryParams = System.Web.HttpUtility.ParseQueryString(new Uri(result).Query); var trValues = queryParams.GetValues("tr")?.ToList() ?? new List(); - Assert.Equal("urn:btih:HASH", queryParams["xt"]); - Assert.Equal("MyFile", queryParams["dn"]); Assert.Contains("udp://existing", trValues); Assert.Contains("udp://new1", trValues); Assert.Equal(2, trValues.Count); } - [Theory] - [InlineData("magnet:?xt=urn:btih:HASH", - new[] - { - "udp://tracker1" - }, - "magnet:?xt=urn:btih:HASH&tr=udp%3A%2F%2Ftracker1")] - [InlineData("magnet:?xt=urn:btih:HASH", - new[] - { - "udp://tracker1", "udp://tracker2" - }, - "magnet:?xt=urn:btih:HASH&tr=udp%3A%2F%2Ftracker1&tr=udp%3A%2F%2Ftracker2")] - [InlineData("magnet:?xt=urn:btih:HASH", - new String[] - { - }, - "magnet:?xt=urn:btih:HASH")] - public async Task EnrichMagnetLink_BasicMagnetWithHashOnly_Theory(String magnetLink, String[] newTrackers, String expectedResult) + [Fact] + public async Task EnrichTorrentBytes_WhenTrackersAdded_CreatesAnnounceKey() { // Arrange - SetupTrackerListGrabber(newTrackers); + var originalTracker = "http://tracker1.com/announce"; + var newTracker = "http://new-tracker.com/announce"; + var torrentDict = CreateStandardTorrentDict(originalTracker); + var originalTorrentBytes = torrentDict.Encode(); + SetupTrackerListGrabber([newTracker]); var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); // Act - var result = await enricher.EnrichMagnetLink(magnetLink); + var enrichedBytes = await enricher.EnrichTorrentBytes(originalTorrentBytes); + var enrichedDict = BEncodedValue.Decode(enrichedBytes); // Assert - Assert.Equal(expectedResult, result); - } - - [Theory] - [InlineData("magnet:?", - new[] - { - "udp://tracker1" - }, - "magnet:?tr=udp%3A%2F%2Ftracker1")] - [InlineData("magnet:?", - new[] - { - "udp://tracker1", "udp://tracker2" - }, - "magnet:?tr=udp%3A%2F%2Ftracker1&tr=udp%3A%2F%2Ftracker2")] - [InlineData("magnet:?", - new String[] - { - }, - "magnet:?")] - public async Task EnrichMagnetLink_MagnetEndsWithQuestionMarkOnly_Theory(String magnetLink, String[] newTrackers, String expectedResult) - { - // Arrange - SetupTrackerListGrabber(newTrackers); - var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); - - // Act - var result = await enricher.EnrichMagnetLink(magnetLink); - - // Assert - Assert.Equal(expectedResult, result); + Assert.True(enrichedDict.ContainsKey("announce")); } [Fact] - public async Task EnrichMagnetLink_GetTrackersReturnsEmptyArray_Unchanged() + public async Task EnrichTorrentBytes_WhenTrackersAdded_CreatesAnnounceListKey() { // Arrange - var magnetLink = "magnet:?xt=urn:btih:HASH&tr=udp%3A%2F%2Fexisting"; + var originalTracker = "http://tracker1.com/announce"; + var newTracker = "http://new-tracker.com/announce"; + var torrentDict = CreateStandardTorrentDict(originalTracker); + var originalTorrentBytes = torrentDict.Encode(); + SetupTrackerListGrabber([newTracker]); + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + + // Act + var enrichedBytes = await enricher.EnrichTorrentBytes(originalTorrentBytes); + var enrichedDict = BEncodedValue.Decode(enrichedBytes); + + // Assert + Assert.True(enrichedDict.ContainsKey("announce-list")); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenTrackersAdded_IncludesAllTrackers() + { + // Arrange + var originalTracker = "http://tracker1.com/announce"; + var newTracker = "http://new-tracker.com/announce"; + var torrentDict = CreateStandardTorrentDict(originalTracker); + var originalTorrentBytes = torrentDict.Encode(); + SetupTrackerListGrabber([newTracker]); // Grabber returns newTracker + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + + // Act + var enrichedBytes = await enricher.EnrichTorrentBytes(originalTorrentBytes); + var enrichedDict = BEncodedValue.Decode(enrichedBytes); + + // Assert + var announceList = (BEncodedList)enrichedDict["announce-list"]; + var flattened = announceList.Cast().SelectMany(l => l.Cast().Select(s => s.Text)).ToList(); + Assert.Contains(originalTracker, flattened); + Assert.Contains(newTracker, flattened); + Assert.Equal(2, flattened.Count); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenTrackersAdded_SetsPrimaryAnnounce() + { + // Arrange + var originalTracker = "http://tracker1.com/announce"; + var newTracker = "http://new-tracker.com/announce"; + var torrentDict = CreateStandardTorrentDict(originalTracker); + var originalTorrentBytes = torrentDict.Encode(); + SetupTrackerListGrabber([newTracker]); + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + + // Act + var enrichedBytes = await enricher.EnrichTorrentBytes(originalTorrentBytes); + var enrichedDict = BEncodedValue.Decode(enrichedBytes); + + // Assert + var announceList = (BEncodedList)enrichedDict["announce-list"]; + var flattened = announceList.Cast().SelectMany(l => l.Cast().Select(s => s.Text)).ToList(); + var announce = ((BEncodedString)enrichedDict["announce"]).Text; + Assert.Equal(flattened.First(), announce); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenDuplicatesProvided_AddsUniqueTrackers() + { + // Arrange + var originalTracker = "http://tracker1.com/announce"; + var duplicateTracker = "http://tracker1.com/announce"; + + var torrentDict = CreateStandardTorrentDict(originalTracker); + var originalTorrentBytes = torrentDict.Encode(); + + SetupTrackerListGrabber([duplicateTracker]); + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + + // Act + var enrichedBytes = await enricher.EnrichTorrentBytes(originalTorrentBytes); + var enrichedDict = BEncodedValue.Decode(enrichedBytes); + + // Assert + var announceList = (BEncodedList)enrichedDict["announce-list"]; + var flattened = announceList.Cast().SelectMany(l => l.Cast().Select(s => s.Text)).ToList(); + + Assert.Single(flattened); + Assert.Contains(originalTracker, flattened); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenNoAnnounceList_CreatesAnnounceListKey() + { + // Arrange + var originalTracker = "http://tracker1.com/announce"; + var newTracker = "http://new-tracker.com/announce"; + var torrentDict = CreateTorrentDictWithOnlyAnnounce(originalTracker); + var originalTorrentBytes = torrentDict.Encode(); + SetupTrackerListGrabber([newTracker]); + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + + // Act + var enrichedBytes = await enricher.EnrichTorrentBytes(originalTorrentBytes); + var enrichedDict = BEncodedValue.Decode(enrichedBytes); + + // Assert + Assert.True(enrichedDict.ContainsKey("announce-list")); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenNoAnnounceList_AddsOriginalTracker() + { + // Arrange + var originalTracker = "http://tracker1.com/announce"; + var newTracker = "http://new-tracker.com/announce"; + var torrentDict = CreateTorrentDictWithOnlyAnnounce(originalTracker); + var originalTorrentBytes = torrentDict.Encode(); + SetupTrackerListGrabber([newTracker]); + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + + // Act + var enrichedBytes = await enricher.EnrichTorrentBytes(originalTorrentBytes); + var enrichedDict = BEncodedValue.Decode(enrichedBytes); + + // Assert + var announceList = (BEncodedList)enrichedDict["announce-list"]; + var flattened = announceList.Cast().SelectMany(l => l.Cast().Select(s => s.Text)).ToList(); + Assert.Contains(originalTracker, flattened); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenNoAnnounceList_AddsGrabbedTrackers() + { + // Arrange + var originalTracker = "http://tracker1.com/announce"; + var newTracker = "http://new-tracker.com/announce"; + var torrentDict = CreateTorrentDictWithOnlyAnnounce(originalTracker); + var originalTorrentBytes = torrentDict.Encode(); + SetupTrackerListGrabber([newTracker]); + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + + // Act + var enrichedBytes = await enricher.EnrichTorrentBytes(originalTorrentBytes); + var enrichedDict = BEncodedValue.Decode(enrichedBytes); + + // Assert + var announceList = (BEncodedList)enrichedDict["announce-list"]; + var flattened = announceList.Cast().SelectMany(l => l.Cast().Select(s => s.Text)).ToList(); + Assert.Contains(newTracker, flattened); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenGrabberFails_ThrowsException() + { + // Arrange + _trackerListGrabberMock + .Setup(t => t.GetTrackers()) + .ThrowsAsync(new InvalidOperationException("Unable to fetch tracker list for enrichment.")); + + var torrentDict = CreateStandardTorrentDict("http://tracker1.com/announce"); + var originalTorrentBytes = torrentDict.Encode(); + + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + + // Act & Assert + await Assert.ThrowsAsync(() => enricher.EnrichTorrentBytes(originalTorrentBytes)); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenNoNewTrackers_ReturnsOriginalBytes() + { + // Arrange + var torrentDict = CreateStandardTorrentDict("http://existing.tracker/announce"); + var originalTorrentBytes = torrentDict.Encode(); + SetupTrackerListGrabber([]); var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); // Act - var result = await enricher.EnrichMagnetLink(magnetLink); + var resultBytes = await enricher.EnrichTorrentBytes(originalTorrentBytes); // Assert - Assert.Equal(magnetLink, result); + Assert.Equal(originalTorrentBytes, resultBytes); } [Fact] - public async Task EnrichTorrentBytes_NoExistingAnnounceOrAnnounceList() + public async Task EnrichTorrentBytes_WhenNoTrackers_ReturnsOriginalBytes() { // Arrange - var torrentDict = new BEncodedDictionary + var torrentDict = CreateTorrentDictWithNoTrackers(); + var originalTorrentBytes = torrentDict.Encode(); + + SetupTrackerListGrabber([]); // Grabber returns no new trackers + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + + // Act + var resultBytes = await enricher.EnrichTorrentBytes(originalTorrentBytes); + + // Assert + Assert.Equal(originalTorrentBytes, resultBytes); + + var decodedResult = BEncodedValue.Decode(resultBytes); + Assert.False(decodedResult.ContainsKey("announce")); + Assert.False(decodedResult.ContainsKey("announce-list")); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenNullInput_ThrowsArgumentException() + { + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + await Assert.ThrowsAsync(() => enricher.EnrichTorrentBytes(null!)); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenEmptyInput_ThrowsArgumentException() + { + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + await Assert.ThrowsAsync(() => enricher.EnrichTorrentBytes([])); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenInvalidInput_ThrowsInvalidOperationException() + { + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + + var notTorrent = new Byte[] { - ["info"] = new BEncodedDictionary() + 1, 2, 3, 4, 5 }; + await Assert.ThrowsAsync(() => enricher.EnrichTorrentBytes(notTorrent)); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenNoAnnounceFields_CreatesAnnounceField() + { + // Arrange + var torrentDict = CreateTorrentDictWithNoTrackers(); var originalBytes = torrentDict.Encode(); var newTrackers = new[] @@ -393,9 +518,55 @@ public class EnricherTest : IDisposable var enrichedDict = BEncodedValue.Decode(enrichedBytes); // Assert + Assert.True(enrichedDict.ContainsKey("announce")); Assert.Equal(newTrackers[0], ((BEncodedString)enrichedDict["announce"]).Text); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenNoAnnounceFields_CreatesAnnounceListKey() + { + // Arrange + var torrentDict = CreateTorrentDictWithNoTrackers(); + var originalBytes = torrentDict.Encode(); + + var newTrackers = new[] + { + "udp://tracker1", "udp://tracker2" + }; + + SetupTrackerListGrabber(newTrackers); + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + + // Act + var enrichedBytes = await enricher.EnrichTorrentBytes(originalBytes); + var enrichedDict = BEncodedValue.Decode(enrichedBytes); + + // Assert + Assert.True(enrichedDict.ContainsKey("announce-list")); + } + + [Fact] + public async Task EnrichTorrentBytes_WhenNoAnnounceFields_AddsGrabbedTrackers() + { + // Arrange + var torrentDict = CreateTorrentDictWithNoTrackers(); + var originalBytes = torrentDict.Encode(); + + var newTrackers = new[] + { + "udp://tracker1", "udp://tracker2" + }; + + SetupTrackerListGrabber(newTrackers); + var enricher = new Enricher(_loggerMock.Object, _trackerListGrabberMock.Object); + + // Act + var enrichedBytes = await enricher.EnrichTorrentBytes(originalBytes); + var enrichedDict = BEncodedValue.Decode(enrichedBytes); + + // Assert var announceList = (BEncodedList)enrichedDict["announce-list"]; - Assert.Equal(2, announceList.Count); + Assert.Equal(newTrackers.Length, announceList.Count); Assert.Collection(announceList, tier => Assert.Equal(newTrackers[0], ((BEncodedString)((BEncodedList)tier)[0]).Text), @@ -403,32 +574,24 @@ public class EnricherTest : IDisposable } [Fact] - public async Task EnrichTorrentBytes_WithExistingAnnounceAndAnnounceList() + public async Task EnrichTorrentBytes_WhenExistingTrackers_MergesNewTrackers() { // Arrange - var existingTrackers = new List - { - "http://tier1.com", - "http://existing.com/announce" - }; + var primaryAnnounceUrl = "http://existing.com/announce"; - var torrentDict = new BEncodedDictionary + var announceListTiers = new List> { - ["announce"] = new BEncodedString(existingTrackers[1]), - ["announce-list"] = new BEncodedList + new() { - new BEncodedList - { - new BEncodedString(existingTrackers[0]) - }, - new BEncodedList - { - new BEncodedString(existingTrackers[1]) - } + "http://tier1.com" }, - ["info"] = new BEncodedDictionary() + new() + { + primaryAnnounceUrl + } }; + var torrentDict = CreateTorrentDictWithComplexAnnounceList(primaryAnnounceUrl, announceListTiers); var originalBytes = torrentDict.Encode(); var newTrackersFromGrabber = new[] @@ -456,4 +619,4 @@ public class EnricherTest : IDisposable Assert.Equal(3, finalAnnounceList.Count); Assert.Contains(((BEncodedString)enrichedDict["announce"]).Text, finalAnnounceList); } -} \ No newline at end of file +}