diff --git a/src/BirdsiteLive.ActivityPub/BirdsiteLive.ActivityPub.csproj b/src/BirdsiteLive.ActivityPub/BirdsiteLive.ActivityPub.csproj index 8dfebd7..a690b63 100644 --- a/src/BirdsiteLive.ActivityPub/BirdsiteLive.ActivityPub.csproj +++ b/src/BirdsiteLive.ActivityPub/BirdsiteLive.ActivityPub.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs b/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs index 29bfb12..95fd0c8 100644 --- a/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs +++ b/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs @@ -57,7 +57,7 @@ namespace BirdsiteLive.Pipeline.Processors { try { - await _sendTweetsToSharedInbox.ExecuteAsync(tweets, user, followersPerInstance); + await _sendTweetsToSharedInbox.ExecuteAsync(tweets, user, followersPerInstance.Key, followersPerInstance.ToArray()); } catch (Exception e) { diff --git a/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToSharedInboxTask.cs b/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToSharedInboxTask.cs index 0aeafd6..bdebdcd 100644 --- a/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToSharedInboxTask.cs +++ b/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToSharedInboxTask.cs @@ -11,7 +11,7 @@ namespace BirdsiteLive.Pipeline.Processors.SubTasks { public interface ISendTweetsToSharedInboxTask { - Task ExecuteAsync(ExtractedTweet[] tweets, SyncTwitterUser user, IGrouping followersPerInstance); + Task ExecuteAsync(ExtractedTweet[] tweets, SyncTwitterUser user, string host, Follower[] followersPerInstance); } public class SendTweetsToSharedInboxTask : ISendTweetsToSharedInboxTask @@ -29,14 +29,12 @@ namespace BirdsiteLive.Pipeline.Processors.SubTasks } #endregion - public async Task ExecuteAsync(ExtractedTweet[] tweets, SyncTwitterUser user, IGrouping followersPerInstance) + public async Task ExecuteAsync(ExtractedTweet[] tweets, SyncTwitterUser user, string host, Follower[] followersPerInstance) { var userId = user.Id; - var host = followersPerInstance.Key; - var groupedFollowers = followersPerInstance.ToList(); - var inbox = groupedFollowers.First().SharedInboxRoute; + var inbox = followersPerInstance.First().SharedInboxRoute; - var fromStatusId = groupedFollowers + var fromStatusId = followersPerInstance .Max(x => x.FollowingsSyncStatus[userId]); var tweetsToSend = tweets @@ -63,7 +61,7 @@ namespace BirdsiteLive.Pipeline.Processors.SubTasks { if (syncStatus != fromStatusId) { - foreach (var f in groupedFollowers) + foreach (var f in followersPerInstance) { f.FollowingsSyncStatus[userId] = syncStatus; await _followersDal.UpdateFollowerAsync(f); diff --git a/src/BirdsiteLive.sln b/src/BirdsiteLive.sln index 6ef9eb6..bf78d55 100644 --- a/src/BirdsiteLive.sln +++ b/src/BirdsiteLive.sln @@ -35,7 +35,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pipeline", "Pipeline", "{DA EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Pipeline", "BirdsiteLive.Pipeline\BirdsiteLive.Pipeline.csproj", "{2A8CC30D-D775-47D1-9388-F72A5C32DE2A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.Domain.Tests", "Tests\BirdsiteLive.Domain.Tests\BirdsiteLive.Domain.Tests.csproj", "{F544D745-89A8-4DEA-B61C-A7E6C53C1D63}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Domain.Tests", "Tests\BirdsiteLive.Domain.Tests\BirdsiteLive.Domain.Tests.csproj", "{F544D745-89A8-4DEA-B61C-A7E6C53C1D63}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Pipeline.Tests", "Tests\BirdsiteLive.Pipeline.Tests\BirdsiteLive.Pipeline.Tests.csproj", "{BF51CA81-5A7A-46F8-B4FB-861C6BE59298}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -95,6 +97,10 @@ Global {F544D745-89A8-4DEA-B61C-A7E6C53C1D63}.Debug|Any CPU.Build.0 = Debug|Any CPU {F544D745-89A8-4DEA-B61C-A7E6C53C1D63}.Release|Any CPU.ActiveCfg = Release|Any CPU {F544D745-89A8-4DEA-B61C-A7E6C53C1D63}.Release|Any CPU.Build.0 = Release|Any CPU + {BF51CA81-5A7A-46F8-B4FB-861C6BE59298}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF51CA81-5A7A-46F8-B4FB-861C6BE59298}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF51CA81-5A7A-46F8-B4FB-861C6BE59298}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF51CA81-5A7A-46F8-B4FB-861C6BE59298}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -112,6 +118,7 @@ Global {CD9489BF-69C8-4705-8774-81C45F4F8FE1} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} {2A8CC30D-D775-47D1-9388-F72A5C32DE2A} = {DA3C160C-4811-4E26-A5AD-42B81FAF2D7C} {F544D745-89A8-4DEA-B61C-A7E6C53C1D63} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} + {BF51CA81-5A7A-46F8-B4FB-861C6BE59298} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {69E8DCAD-4C37-4010-858F-5F94E6FBABCE} diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/BirdsiteLive.Pipeline.Tests.csproj b/src/Tests/BirdsiteLive.Pipeline.Tests/BirdsiteLive.Pipeline.Tests.csproj new file mode 100644 index 0000000..3dd6984 --- /dev/null +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/BirdsiteLive.Pipeline.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + + diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToInboxTaskTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToInboxTaskTests.cs new file mode 100644 index 0000000..0193442 --- /dev/null +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToInboxTaskTests.cs @@ -0,0 +1,7 @@ +namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks +{ + public class SendTweetsToInboxTaskTests + { + + } +} \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToSharedInboxTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToSharedInboxTests.cs new file mode 100644 index 0000000..a052a5c --- /dev/null +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToSharedInboxTests.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using BirdsiteLive.ActivityPub.Models; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.Domain; +using BirdsiteLive.Pipeline.Processors.SubTasks; +using BirdsiteLive.Twitter.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks +{ + [TestClass] + public class SendTweetsToSharedInboxTests + { + [TestMethod] + public async Task ExecuteAsync_SingleTweet_Test() + { + #region Stubs + var tweetId = 10; + var tweets = new List + { + new ExtractedTweet + { + Id = tweetId, + } + }; + + var noteId = "noteId"; + var note = new Note() + { + id = noteId + }; + + var twitterHandle = "Test"; + var twitterUserId = 7; + var twitterUser = new SyncTwitterUser + { + Id = twitterUserId, + Acct = twitterHandle + }; + + var host = "domain.ext"; + var inbox = "/inbox"; + var followers = new List + { + new Follower + { + Id = 1, + Host = host, + SharedInboxRoute = inbox, + FollowingsSyncStatus = new Dictionary { { twitterUserId, 9 } } + }, + new Follower + { + Id = 2, + Host = host, + SharedInboxRoute = inbox, + FollowingsSyncStatus = new Dictionary { { twitterUserId, 8 } } + }, + new Follower + { + Id = 3, + Host = host, + SharedInboxRoute = inbox, + FollowingsSyncStatus = new Dictionary { { twitterUserId, 7 } } + } + }; + #endregion + + #region Mocks + var activityPubService = new Mock(MockBehavior.Strict); + activityPubService + .Setup(x => x.PostNewNoteActivity( + It.Is(y => y.id == noteId), + It.Is(y => y == twitterHandle), + It.Is(y => y == tweetId.ToString()), + It.Is(y => y == host), + It.Is(y => y == inbox))) + .ReturnsAsync(HttpStatusCode.Accepted); + + var statusServiceMock = new Mock(MockBehavior.Strict); + statusServiceMock + .Setup(x => x.GetStatus( + It.Is(y => y == twitterHandle), + It.Is(y => y.Id == tweetId))) + .Returns(note); + + var followersDalMock = new Mock(MockBehavior.Strict); + + foreach (var follower in followers) + { + followersDalMock + .Setup(x => x.UpdateFollowerAsync( + It.Is(y => y.Id == follower.Id && y.FollowingsSyncStatus[twitterUserId] == tweetId))) + .Returns(Task.CompletedTask); + } + #endregion + + var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object); + await task.ExecuteAsync(tweets.ToArray(), twitterUser, host, followers.ToArray()); + + #region Validations + activityPubService.VerifyAll(); + statusServiceMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ExecuteAsync_MultipleTweets_Test() + { + #region Stubs + var tweetId1 = 10; + var tweetId2 = 11; + var tweetId3 = 12; + var tweets = new List(); + foreach (var tweetId in new[] { tweetId1, tweetId2, tweetId3 }) + { + tweets.Add(new ExtractedTweet + { + Id = tweetId + }); + } + + var twitterHandle = "Test"; + var twitterUserId = 7; + var twitterUser = new SyncTwitterUser + { + Id = twitterUserId, + Acct = twitterHandle + }; + + var host = "domain.ext"; + var inbox = "/inbox"; + var followers = new List + { + new Follower + { + Id = 1, + Host = host, + SharedInboxRoute = inbox, + FollowingsSyncStatus = new Dictionary {{twitterUserId, 10}} + }, + new Follower + { + Id = 2, + Host = host, + SharedInboxRoute = inbox, + FollowingsSyncStatus = new Dictionary {{twitterUserId, 8}} + }, + new Follower + { + Id = 3, + Host = host, + SharedInboxRoute = inbox, + FollowingsSyncStatus = new Dictionary {{twitterUserId, 7}} + } + }; + #endregion + + #region Mocks + var activityPubService = new Mock(MockBehavior.Strict); + foreach (var tweetId in new[] { tweetId2, tweetId3 }) + { + activityPubService + .Setup(x => x.PostNewNoteActivity( + It.Is(y => y.id == tweetId.ToString()), + It.Is(y => y == twitterHandle), + It.Is(y => y == tweetId.ToString()), + It.Is(y => y == host), + It.Is(y => y == inbox))) + .ReturnsAsync(HttpStatusCode.Accepted); + } + + var statusServiceMock = new Mock(MockBehavior.Strict); + foreach (var tweetId in new[] { tweetId2, tweetId3 }) + { + statusServiceMock + .Setup(x => x.GetStatus( + It.Is(y => y == twitterHandle), + It.Is(y => y.Id == tweetId))) + .Returns(new Note { id = tweetId.ToString() }); + } + + var followersDalMock = new Mock(MockBehavior.Strict); + + foreach (var follower in followers) + { + followersDalMock + .Setup(x => x.UpdateFollowerAsync( + It.Is(y => y.Id == follower.Id && y.FollowingsSyncStatus[twitterUserId] == tweetId3))) + .Returns(Task.CompletedTask); + } + #endregion + + var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object); + await task.ExecuteAsync(tweets.ToArray(), twitterUser, host, followers.ToArray()); + + #region Validations + activityPubService.VerifyAll(); + statusServiceMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + + [TestMethod] + [ExpectedException(typeof(Exception))] + public async Task ExecuteAsync_MultipleTweets_Error_Test() + { + #region Stubs + var tweetId1 = 10; + var tweetId2 = 11; + var tweetId3 = 12; + var tweets = new List(); + foreach (var tweetId in new[] { tweetId1, tweetId2, tweetId3 }) + { + tweets.Add(new ExtractedTweet + { + Id = tweetId + }); + } + + var twitterHandle = "Test"; + var twitterUserId = 7; + var twitterUser = new SyncTwitterUser + { + Id = twitterUserId, + Acct = twitterHandle + }; + + var host = "domain.ext"; + var inbox = "/inbox"; + var followers = new List + { + new Follower + { + Id = 1, + Host = host, + SharedInboxRoute = inbox, + FollowingsSyncStatus = new Dictionary {{twitterUserId, 10}} + }, + new Follower + { + Id = 2, + Host = host, + SharedInboxRoute = inbox, + FollowingsSyncStatus = new Dictionary {{twitterUserId, 8}} + }, + new Follower + { + Id = 3, + Host = host, + SharedInboxRoute = inbox, + FollowingsSyncStatus = new Dictionary {{twitterUserId, 7}} + } + }; + #endregion + + #region Mocks + var activityPubService = new Mock(MockBehavior.Strict); + + activityPubService + .Setup(x => x.PostNewNoteActivity( + It.Is(y => y.id == tweetId2.ToString()), + It.Is(y => y == twitterHandle), + It.Is(y => y == tweetId2.ToString()), + It.Is(y => y == host), + It.Is(y => y == inbox))) + .ReturnsAsync(HttpStatusCode.Accepted); + + activityPubService + .Setup(x => x.PostNewNoteActivity( + It.Is(y => y.id == tweetId3.ToString()), + It.Is(y => y == twitterHandle), + It.Is(y => y == tweetId3.ToString()), + It.Is(y => y == host), + It.Is(y => y == inbox))) + .ReturnsAsync(HttpStatusCode.InternalServerError); + + var statusServiceMock = new Mock(MockBehavior.Strict); + foreach (var tweetId in new[] { tweetId2, tweetId3 }) + { + statusServiceMock + .Setup(x => x.GetStatus( + It.Is(y => y == twitterHandle), + It.Is(y => y.Id == tweetId))) + .Returns(new Note { id = tweetId.ToString() }); + } + + var followersDalMock = new Mock(MockBehavior.Strict); + + foreach (var follower in followers) + { + followersDalMock + .Setup(x => x.UpdateFollowerAsync( + It.Is(y => y.Id == follower.Id && y.FollowingsSyncStatus[twitterUserId] == tweetId2))) + .Returns(Task.CompletedTask); + } + #endregion + + var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object); + + try + { + await task.ExecuteAsync(tweets.ToArray(), twitterUser, host, followers.ToArray()); + } + finally + { + #region Validations + activityPubService.VerifyAll(); + statusServiceMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + } + } +} \ No newline at end of file