From 40d6f694462ed4c8404f49c5324c7e0f0121380e Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 22 Jan 2021 18:31:30 -0500 Subject: [PATCH 1/2] added reply filtering --- VARIABLES.md | 3 +- .../Settings/InstanceSettings.cs | 1 + .../SubTasks/SendTweetsToInboxTask.cs | 14 +- .../SubTasks/SendTweetsToSharedInboxTask.cs | 16 +- .../Extractors/TweetExtractor.cs | 4 +- .../Models/ExtractedTweet.cs | 2 + .../TwitterTweetsService.cs | 2 +- src/BirdsiteLive/appsettings.json | 3 +- .../SubTasks/SendTweetsToInboxTaskTests.cs | 254 +++++++++++++- .../SubTasks/SendTweetsToSharedInboxTests.cs | 318 +++++++++++++++++- 10 files changed, 600 insertions(+), 17 deletions(-) diff --git a/VARIABLES.md b/VARIABLES.md index 3536069..c00da5e 100644 --- a/VARIABLES.md +++ b/VARIABLES.md @@ -10,4 +10,5 @@ You can configure some of BirdsiteLIVE's settings via environment variables (tho ## Instance customization * `Instance:Name` (default: BirdsiteLIVE) the name of the instance -* `Instance:ResolveMentionsInProfiles` (default: true) to enable or disable mentions parsing in profile's description. Resolving it will consume more User's API calls since newly discovered account can also contain references to others accounts as well. On a big instance it is recommended to disable it. \ No newline at end of file +* `Instance:ResolveMentionsInProfiles` (default: true) to enable or disable mentions parsing in profile's description. Resolving it will consume more User's API calls since newly discovered account can also contain references to others accounts as well. On a big instance it is recommended to disable it. +* `Instance:PublishReplies` (default: false) to enable or disable replies publishing. \ No newline at end of file diff --git a/src/BirdsiteLive.Common/Settings/InstanceSettings.cs b/src/BirdsiteLive.Common/Settings/InstanceSettings.cs index fe303a7..428b5eb 100644 --- a/src/BirdsiteLive.Common/Settings/InstanceSettings.cs +++ b/src/BirdsiteLive.Common/Settings/InstanceSettings.cs @@ -6,5 +6,6 @@ public string Domain { get; set; } public string AdminEmail { get; set; } public bool ResolveMentionsInProfiles { get; set; } + public bool PublishReplies { get; set; } } } \ No newline at end of file diff --git a/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToInboxTask.cs b/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToInboxTask.cs index 2fc93fe..a6f6982 100644 --- a/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToInboxTask.cs +++ b/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToInboxTask.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; +using BirdsiteLive.Common.Settings; using BirdsiteLive.DAL.Contracts; using BirdsiteLive.DAL.Models; using BirdsiteLive.Domain; @@ -21,15 +22,17 @@ namespace BirdsiteLive.Pipeline.Processors.SubTasks private readonly IActivityPubService _activityPubService; private readonly IStatusService _statusService; private readonly IFollowersDal _followersDal; + private readonly InstanceSettings _settings; private readonly ILogger _logger; #region Ctor - public SendTweetsToInboxTask(IActivityPubService activityPubService, IStatusService statusService, IFollowersDal followersDal, ILogger logger) + public SendTweetsToInboxTask(IActivityPubService activityPubService, IStatusService statusService, IFollowersDal followersDal, InstanceSettings settings, ILogger logger) { _activityPubService = activityPubService; _statusService = statusService; _followersDal = followersDal; + _settings = settings; _logger = logger; } #endregion @@ -52,8 +55,13 @@ namespace BirdsiteLive.Pipeline.Processors.SubTasks { try { - var note = _statusService.GetStatus(user.Acct, tweet); - await _activityPubService.PostNewNoteActivity(note, user.Acct, tweet.Id.ToString(), follower.Host, inbox); + if (!tweet.IsReply || + tweet.IsReply && tweet.IsThread || + _settings.PublishReplies) + { + var note = _statusService.GetStatus(user.Acct, tweet); + await _activityPubService.PostNewNoteActivity(note, user.Acct, tweet.Id.ToString(), follower.Host, inbox); + } } catch (ArgumentException e) { diff --git a/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToSharedInboxTask.cs b/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToSharedInboxTask.cs index dcc6aca..1abe183 100644 --- a/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToSharedInboxTask.cs +++ b/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToSharedInboxTask.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; +using BirdsiteLive.Common.Settings; using BirdsiteLive.DAL.Contracts; using BirdsiteLive.DAL.Models; using BirdsiteLive.Domain; @@ -20,14 +21,16 @@ namespace BirdsiteLive.Pipeline.Processors.SubTasks private readonly IStatusService _statusService; private readonly IActivityPubService _activityPubService; private readonly IFollowersDal _followersDal; + private readonly InstanceSettings _settings; private readonly ILogger _logger; #region Ctor - public SendTweetsToSharedInboxTask(IActivityPubService activityPubService, IStatusService statusService, IFollowersDal followersDal, ILogger logger) + public SendTweetsToSharedInboxTask(IActivityPubService activityPubService, IStatusService statusService, IFollowersDal followersDal, InstanceSettings settings, ILogger logger) { _activityPubService = activityPubService; _statusService = statusService; _followersDal = followersDal; + _settings = settings; _logger = logger; } #endregion @@ -52,8 +55,13 @@ namespace BirdsiteLive.Pipeline.Processors.SubTasks { try { - var note = _statusService.GetStatus(user.Acct, tweet); - await _activityPubService.PostNewNoteActivity(note, user.Acct, tweet.Id.ToString(), host, inbox); + if (!tweet.IsReply || + tweet.IsReply && tweet.IsThread || + _settings.PublishReplies) + { + var note = _statusService.GetStatus(user.Acct, tweet); + await _activityPubService.PostNewNoteActivity(note, user.Acct, tweet.Id.ToString(), host, inbox); + } } catch (ArgumentException e) { @@ -66,7 +74,7 @@ namespace BirdsiteLive.Pipeline.Processors.SubTasks throw; } } - + syncStatus = tweet.Id; } } diff --git a/src/BirdsiteLive.Twitter/Extractors/TweetExtractor.cs b/src/BirdsiteLive.Twitter/Extractors/TweetExtractor.cs index f4bb8d1..573385c 100644 --- a/src/BirdsiteLive.Twitter/Extractors/TweetExtractor.cs +++ b/src/BirdsiteLive.Twitter/Extractors/TweetExtractor.cs @@ -24,7 +24,9 @@ namespace BirdsiteLive.Twitter.Extractors InReplyToAccount = tweet.InReplyToScreenName, MessageContent = ExtractMessage(tweet), Media = ExtractMedia(tweet.Media), - CreatedAt = tweet.CreatedAt.ToUniversalTime() + CreatedAt = tweet.CreatedAt.ToUniversalTime(), + IsReply = tweet.InReplyToUserId != null, + IsThread = tweet.InReplyToUserId != null && tweet.InReplyToUserId == tweet.CreatedBy.Id }; return extractedTweet; } diff --git a/src/BirdsiteLive.Twitter/Models/ExtractedTweet.cs b/src/BirdsiteLive.Twitter/Models/ExtractedTweet.cs index 0363973..1f6bcea 100644 --- a/src/BirdsiteLive.Twitter/Models/ExtractedTweet.cs +++ b/src/BirdsiteLive.Twitter/Models/ExtractedTweet.cs @@ -11,5 +11,7 @@ namespace BirdsiteLive.Twitter.Models public ExtractedMedia[] Media { get; set; } public DateTime CreatedAt { get; set; } public string InReplyToAccount { get; set; } + public bool IsReply { get; set; } + public bool IsThread { get; set; } } } \ No newline at end of file diff --git a/src/BirdsiteLive.Twitter/TwitterTweetsService.cs b/src/BirdsiteLive.Twitter/TwitterTweetsService.cs index ee104ae..0f82429 100644 --- a/src/BirdsiteLive.Twitter/TwitterTweetsService.cs +++ b/src/BirdsiteLive.Twitter/TwitterTweetsService.cs @@ -40,7 +40,7 @@ namespace BirdsiteLive.Twitter var tweet = Tweet.GetTweet(statusId); _statisticsHandler.CalledTweetApi(); if (tweet == null) return null; //TODO: test this - return _tweetExtractor.Extract(tweet); + return _tweetExtractor.Extract(tweet); } public ExtractedTweet[] GetTimeline(string username, int nberTweets, long fromTweetId = -1) diff --git a/src/BirdsiteLive/appsettings.json b/src/BirdsiteLive/appsettings.json index 57bf945..2e566ff 100644 --- a/src/BirdsiteLive/appsettings.json +++ b/src/BirdsiteLive/appsettings.json @@ -13,7 +13,8 @@ "Name": "BirdsiteLIVE", "Domain": "domain.name", "AdminEmail": "me@domain.name", - "ResolveMentionsInProfiles": true + "ResolveMentionsInProfiles": true, + "PublishReplies": false }, "Db": { "Type": "postgres", diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToInboxTaskTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToInboxTaskTests.cs index 23c9fe3..cf80c85 100644 --- a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToInboxTaskTests.cs +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToInboxTaskTests.cs @@ -4,6 +4,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; using BirdsiteLive.ActivityPub.Models; +using BirdsiteLive.Common.Settings; using BirdsiteLive.DAL.Contracts; using BirdsiteLive.DAL.Models; using BirdsiteLive.Domain; @@ -54,6 +55,11 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks InboxRoute = inbox, FollowingsSyncStatus = new Dictionary { { twitterUserId, 9 } } }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; #endregion #region Mocks @@ -83,7 +89,239 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks var loggerMock = new Mock>(); #endregion - var task = new SendTweetsToInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, loggerMock.Object); + var task = new SendTweetsToInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); + await task.ExecuteAsync(tweets.ToArray(), follower, twitterUser); + + #region Validations + activityPubService.VerifyAll(); + statusServiceMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ExecuteAsync_SingleTweet_Reply_Test() + { + #region Stubs + var tweetId = 10; + var tweets = new List + { + new ExtractedTweet + { + Id = tweetId, + IsReply = true, + IsThread = false + } + }; + + 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 = "/user/inbox"; + var follower = new Follower + { + Id = 1, + Host = host, + InboxRoute = inbox, + FollowingsSyncStatus = new Dictionary { { twitterUserId, 9 } } + }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; + #endregion + + #region Mocks + var activityPubService = new Mock(MockBehavior.Strict); + var statusServiceMock = new Mock(MockBehavior.Strict); + + var followersDalMock = new Mock(MockBehavior.Strict); + followersDalMock + .Setup(x => x.UpdateFollowerAsync( + It.Is(y => y.Id == follower.Id && y.FollowingsSyncStatus[twitterUserId] == tweetId))) + .Returns(Task.CompletedTask); + + var loggerMock = new Mock>(); + #endregion + + var task = new SendTweetsToInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); + await task.ExecuteAsync(tweets.ToArray(), follower, twitterUser); + + #region Validations + activityPubService.VerifyAll(); + statusServiceMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ExecuteAsync_SingleTweet_ReplyThread_Test() + { + #region Stubs + var tweetId = 10; + var tweets = new List + { + new ExtractedTweet + { + Id = tweetId, + IsReply = true, + IsThread = true + } + }; + + 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 = "/user/inbox"; + var follower = new Follower + { + Id = 1, + Host = host, + InboxRoute = inbox, + FollowingsSyncStatus = new Dictionary { { twitterUserId, 9 } } + }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; + #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))) + .Returns(Task.CompletedTask); + + 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); + followersDalMock + .Setup(x => x.UpdateFollowerAsync( + It.Is(y => y.Id == follower.Id && y.FollowingsSyncStatus[twitterUserId] == tweetId))) + .Returns(Task.CompletedTask); + + var loggerMock = new Mock>(); + #endregion + + var task = new SendTweetsToInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); + await task.ExecuteAsync(tweets.ToArray(), follower, twitterUser); + + #region Validations + activityPubService.VerifyAll(); + statusServiceMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ExecuteAsync_SingleTweet_PublishReply_Test() + { + #region Stubs + var tweetId = 10; + var tweets = new List + { + new ExtractedTweet + { + Id = tweetId, + IsReply = true, + IsThread = false + } + }; + + 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 = "/user/inbox"; + var follower = new Follower + { + Id = 1, + Host = host, + InboxRoute = inbox, + FollowingsSyncStatus = new Dictionary { { twitterUserId, 9 } } + }; + + var settings = new InstanceSettings + { + PublishReplies = true + }; + #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))) + .Returns(Task.CompletedTask); + + 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); + followersDalMock + .Setup(x => x.UpdateFollowerAsync( + It.Is(y => y.Id == follower.Id && y.FollowingsSyncStatus[twitterUserId] == tweetId))) + .Returns(Task.CompletedTask); + + var loggerMock = new Mock>(); + #endregion + + var task = new SendTweetsToInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); await task.ExecuteAsync(tweets.ToArray(), follower, twitterUser); #region Validations @@ -126,6 +364,11 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks InboxRoute = inbox, FollowingsSyncStatus = new Dictionary { { twitterUserId, 10 } } }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; #endregion #region Mocks @@ -161,7 +404,7 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks var loggerMock = new Mock>(); #endregion - var task = new SendTweetsToInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, loggerMock.Object); + var task = new SendTweetsToInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); await task.ExecuteAsync(tweets.ToArray(), follower, twitterUser); #region Validations @@ -205,6 +448,11 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks InboxRoute = inbox, FollowingsSyncStatus = new Dictionary { { twitterUserId, 10 } } }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; #endregion #region Mocks @@ -247,7 +495,7 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks var loggerMock = new Mock>(); #endregion - var task = new SendTweetsToInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, loggerMock.Object); + var task = new SendTweetsToInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); try { diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToSharedInboxTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToSharedInboxTests.cs index 5ae9c7f..ed72cbc 100644 --- a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToSharedInboxTests.cs +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToSharedInboxTests.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; using BirdsiteLive.ActivityPub.Models; +using BirdsiteLive.Common.Settings; using BirdsiteLive.DAL.Contracts; using BirdsiteLive.DAL.Models; using BirdsiteLive.Domain; @@ -72,6 +73,11 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks FollowingsSyncStatus = new Dictionary { { twitterUserId, 7 } } } }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; #endregion #region Mocks @@ -105,7 +111,303 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks var loggerMock = new Mock>(); #endregion - var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, loggerMock.Object); + var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); + await task.ExecuteAsync(tweets.ToArray(), twitterUser, host, followers.ToArray()); + + #region Validations + activityPubService.VerifyAll(); + statusServiceMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ExecuteAsync_SingleTweet_Reply_Test() + { + #region Stubs + var tweetId = 10; + var tweets = new List + { + new ExtractedTweet + { + Id = tweetId, + IsReply = true, + IsThread = false + } + }; + + 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 } } + } + }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; + #endregion + + #region Mocks + var activityPubService = new Mock(MockBehavior.Strict); + + var statusServiceMock = new Mock(MockBehavior.Strict); + + 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); + } + + var loggerMock = new Mock>(); + #endregion + + var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); + await task.ExecuteAsync(tweets.ToArray(), twitterUser, host, followers.ToArray()); + + #region Validations + activityPubService.VerifyAll(); + statusServiceMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ExecuteAsync_SingleTweet_ReplyThread_Test() + { + #region Stubs + var tweetId = 10; + var tweets = new List + { + new ExtractedTweet + { + Id = tweetId, + IsReply = true, + IsThread = true + } + }; + + 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 } } + } + }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; + #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))) + .Returns(Task.CompletedTask); + + 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); + } + + var loggerMock = new Mock>(); + #endregion + + var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); + await task.ExecuteAsync(tweets.ToArray(), twitterUser, host, followers.ToArray()); + + #region Validations + activityPubService.VerifyAll(); + statusServiceMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ExecuteAsync_SingleTweet_PublishReply_Test() + { + #region Stubs + var tweetId = 10; + var tweets = new List + { + new ExtractedTweet + { + Id = tweetId, + IsReply = true, + IsThread = false + } + }; + + 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 } } + } + }; + + var settings = new InstanceSettings + { + PublishReplies = true + }; + #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))) + .Returns(Task.CompletedTask); + + 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); + } + + var loggerMock = new Mock>(); + #endregion + + var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); await task.ExecuteAsync(tweets.ToArray(), twitterUser, host, followers.ToArray()); #region Validations @@ -165,6 +467,11 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks FollowingsSyncStatus = new Dictionary {{twitterUserId, 7}} } }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; #endregion #region Mocks @@ -204,7 +511,7 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks var loggerMock = new Mock>(); #endregion - var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, loggerMock.Object); + var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); await task.ExecuteAsync(tweets.ToArray(), twitterUser, host, followers.ToArray()); #region Validations @@ -265,6 +572,11 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks FollowingsSyncStatus = new Dictionary {{twitterUserId, 7}} } }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; #endregion #region Mocks @@ -311,7 +623,7 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks var loggerMock = new Mock>(); #endregion - var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, loggerMock.Object); + var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); try { From 35adfad45ff05c1dcfa331adab5d8731ba0ec1e5 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 22 Jan 2021 18:41:54 -0500 Subject: [PATCH 2/2] better code coverage --- .../RetrieveTwitterUsersProcessorTests.cs | 2 +- .../SubTasks/SendTweetsToInboxTaskTests.cs | 142 ++++++++++++++ .../SubTasks/SendTweetsToSharedInboxTests.cs | 180 ++++++++++++++++++ 3 files changed, 323 insertions(+), 1 deletion(-) diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs index 6cbd008..12c5682 100644 --- a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs @@ -76,7 +76,7 @@ namespace BirdsiteLive.Pipeline.Tests.Processors processor.WaitFactor = 2; processor.GetTwitterUsersAsync(buffer, CancellationToken.None); - await Task.Delay(200); + await Task.Delay(300); #region Validations twitterUserDalMock.VerifyAll(); diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToInboxTaskTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToInboxTaskTests.cs index cf80c85..367a642 100644 --- a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToInboxTaskTests.cs +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToInboxTaskTests.cs @@ -510,5 +510,147 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks #endregion } } + + [TestMethod] + public async Task ExecuteAsync_SingleTweet_ParsingError_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 = "/user/inbox"; + var follower = new Follower + { + Id = 1, + Host = host, + InboxRoute = inbox, + FollowingsSyncStatus = new Dictionary { { twitterUserId, 9 } } + }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; + #endregion + + #region Mocks + var activityPubService = new Mock(MockBehavior.Strict); + + var statusServiceMock = new Mock(MockBehavior.Strict); + statusServiceMock + .Setup(x => x.GetStatus( + It.Is(y => y == twitterHandle), + It.Is(y => y.Id == tweetId))) + .Throws(new ArgumentException("Invalid pattern blabla at offset 9")); + + var followersDalMock = new Mock(MockBehavior.Strict); + followersDalMock + .Setup(x => x.UpdateFollowerAsync( + It.Is(y => y.Id == follower.Id && y.FollowingsSyncStatus[twitterUserId] == tweetId))) + .Returns(Task.CompletedTask); + + var loggerMock = new Mock>(); + #endregion + + var task = new SendTweetsToInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); + await task.ExecuteAsync(tweets.ToArray(), follower, twitterUser); + + #region Validations + activityPubService.VerifyAll(); + statusServiceMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task ExecuteAsync_SingleTweet_ArgumentException_Test() + { + #region Stubs + var tweetId = 10; + var tweets = new List + { + new ExtractedTweet + { + Id = tweetId, + } + }; + + var twitterHandle = "Test"; + var twitterUserId = 7; + var twitterUser = new SyncTwitterUser + { + Id = twitterUserId, + Acct = twitterHandle + }; + + var host = "domain.ext"; + var inbox = "/user/inbox"; + var follower = new Follower + { + Id = 1, + Host = host, + InboxRoute = inbox, + FollowingsSyncStatus = new Dictionary { { twitterUserId, 9 } } + }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; + #endregion + + #region Mocks + var activityPubService = new Mock(MockBehavior.Strict); + + var statusServiceMock = new Mock(MockBehavior.Strict); + statusServiceMock + .Setup(x => x.GetStatus( + It.Is(y => y == twitterHandle), + It.Is(y => y.Id == tweetId))) + .Throws(new ArgumentException()); + + var followersDalMock = new Mock(MockBehavior.Strict); + + var loggerMock = new Mock>(); + #endregion + + var task = new SendTweetsToInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); + + try + { + await task.ExecuteAsync(tweets.ToArray(), follower, twitterUser); + + } + finally + { + #region Validations + activityPubService.VerifyAll(); + statusServiceMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + } } } \ 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 index ed72cbc..7ab06a2 100644 --- a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToSharedInboxTests.cs +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SubTasks/SendTweetsToSharedInboxTests.cs @@ -638,5 +638,185 @@ namespace BirdsiteLive.Pipeline.Tests.Processors.SubTasks #endregion } } + + [TestMethod] + public async Task ExecuteAsync_SingleTweet_ParsingError_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 } } + } + }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; + #endregion + + #region Mocks + var activityPubService = new Mock(MockBehavior.Strict); + + var statusServiceMock = new Mock(MockBehavior.Strict); + statusServiceMock + .Setup(x => x.GetStatus( + It.Is(y => y == twitterHandle), + It.Is(y => y.Id == tweetId))) + .Throws(new ArgumentException("Invalid pattern blabla at offset 9")); + + 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); + } + + var loggerMock = new Mock>(); + #endregion + + var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.Object); + await task.ExecuteAsync(tweets.ToArray(), twitterUser, host, followers.ToArray()); + + #region Validations + activityPubService.VerifyAll(); + statusServiceMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task ExecuteAsync_SingleTweet_ArgumentException_Test() + { + #region Stubs + var tweetId = 10; + var tweets = new List + { + 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, 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 } } + } + }; + + var settings = new InstanceSettings + { + PublishReplies = false + }; + #endregion + + #region Mocks + var activityPubService = new Mock(MockBehavior.Strict); + + var statusServiceMock = new Mock(MockBehavior.Strict); + statusServiceMock + .Setup(x => x.GetStatus( + It.Is(y => y == twitterHandle), + It.Is(y => y.Id == tweetId))) + .Throws(new ArgumentException()); + + var followersDalMock = new Mock(MockBehavior.Strict); + + var loggerMock = new Mock>(); + #endregion + + var task = new SendTweetsToSharedInboxTask(activityPubService.Object, statusServiceMock.Object, followersDalMock.Object, settings, loggerMock.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