From 2258c93e0931b0161239fc842e463ce36ee39752 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 10 Sep 2021 18:53:11 -0400 Subject: [PATCH 1/7] added posting error count --- .../DbInitializerPostgresDal.cs | 10 ++++- .../DataAccessLayers/FollowersPostgresDal.cs | 8 ++-- .../BirdsiteLive.DAL/Models/Follower.cs | 2 + .../FollowersPostgresDalTests.cs | 44 ++++++++++++++++++- 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/DbInitializerPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/DbInitializerPostgresDal.cs index 0d656a7..2e3acea 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/DbInitializerPostgresDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/DbInitializerPostgresDal.cs @@ -23,7 +23,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers public class DbInitializerPostgresDal : PostgresBase, IDbInitializerDal { private readonly PostgresTools _tools; - private readonly Version _currentVersion = new Version(2, 2); + private readonly Version _currentVersion = new Version(2, 3); private const string DbVersionType = "db-version"; #region Ctor @@ -133,7 +133,8 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers { new Tuple(new Version(1,0), new Version(2,0)), new Tuple(new Version(2,0), new Version(2,1)), - new Tuple(new Version(2,1), new Version(2,2)) + new Tuple(new Version(2,1), new Version(2,2)), + new Tuple(new Version(2,2), new Version(2,3)) }; } @@ -157,6 +158,11 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers var addLastSync = $@"ALTER TABLE {_settings.TwitterUserTableName} ADD fetchingErrorCount SMALLINT"; await _tools.ExecuteRequestAsync(addLastSync); } + else if (from == new Version(2, 2) && to == new Version(2, 3)) + { + var addPostingError = $@"ALTER TABLE {_settings.FollowersTableName} ADD postingErrorCount SMALLINT"; + await _tools.ExecuteRequestAsync(addPostingError); + } else { throw new NotImplementedException(); diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs index ec031d4..bcdda0f 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs @@ -103,13 +103,13 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers if (follower.Id == default) throw new ArgumentException("id"); var serializedDic = JsonConvert.SerializeObject(follower.FollowingsSyncStatus); - var query = $"UPDATE {_settings.FollowersTableName} SET followings = @followings, followingsSyncStatus = CAST(@followingsSyncStatus as json) WHERE id = @id"; + var query = $"UPDATE {_settings.FollowersTableName} SET followings = @followings, followingsSyncStatus = CAST(@followingsSyncStatus as json), postingErrorCount = @postingErrorCount WHERE id = @id"; using (var dbConnection = Connection) { dbConnection.Open(); - await dbConnection.QueryAsync(query, new { follower.Id, follower.Followings, followingsSyncStatus = serializedDic }); + await dbConnection.QueryAsync(query, new { follower.Id, follower.Followings, followingsSyncStatus = serializedDic, postingErrorCount = follower.PostingErrorCount }); } } @@ -158,7 +158,8 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers ActorId = follower.ActorId, SharedInboxRoute = follower.SharedInboxRoute, Followings = follower.Followings.ToList(), - FollowingsSyncStatus = JsonConvert.DeserializeObject>(follower.FollowingsSyncStatus) + FollowingsSyncStatus = JsonConvert.DeserializeObject>(follower.FollowingsSyncStatus), + PostingErrorCount = follower.PostingErrorCount }; } } @@ -174,5 +175,6 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers public string InboxRoute { get; set; } public string SharedInboxRoute { get; set; } public string ActorId { get; set; } + public int PostingErrorCount { get; set; } } } \ No newline at end of file diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Models/Follower.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Models/Follower.cs index 274852b..357e32e 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/Models/Follower.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Models/Follower.cs @@ -14,5 +14,7 @@ namespace BirdsiteLive.DAL.Models public string Host { get; set; } public string InboxRoute { get; set; } public string SharedInboxRoute { get; set; } + + public int PostingErrorCount { get; set; } } } \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs index cbaeb72..62cb3e6 100644 --- a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs +++ b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs @@ -54,6 +54,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers Assert.AreEqual(inboxRoute, result.InboxRoute); Assert.AreEqual(sharedInboxRoute, result.SharedInboxRoute); Assert.AreEqual(actorId, result.ActorId); + Assert.AreEqual(0, result.PostingErrorCount); Assert.AreEqual(following.Length, result.Followings.Count); Assert.AreEqual(following[0], result.Followings[0]); Assert.AreEqual(followingSync.Count, result.FollowingsSyncStatus.Count); @@ -83,6 +84,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers Assert.AreEqual(sharedInboxRoute, result.SharedInboxRoute); Assert.AreEqual(0, result.Followings.Count); Assert.AreEqual(0, result.FollowingsSyncStatus.Count); + Assert.AreEqual(0, result.PostingErrorCount); } [TestMethod] @@ -125,6 +127,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers Assert.AreEqual(followingSync.Count, result.FollowingsSyncStatus.Count); Assert.AreEqual(followingSync.First().Key, result.FollowingsSyncStatus.First().Key); Assert.AreEqual(followingSync.First().Value, result.FollowingsSyncStatus.First().Value); + Assert.AreEqual(0, result.PostingErrorCount); } [TestMethod] @@ -276,8 +279,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers }; result.Followings = updatedFollowing.ToList(); result.FollowingsSyncStatus = updatedFollowingSync; - - + result.PostingErrorCount = 10; + await dal.UpdateFollowerAsync(result); result = await dal.GetFollowerAsync(acct, host); @@ -286,6 +289,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers Assert.AreEqual(updatedFollowingSync.Count, result.FollowingsSyncStatus.Count); Assert.AreEqual(updatedFollowingSync.First().Key, result.FollowingsSyncStatus.First().Key); Assert.AreEqual(updatedFollowingSync.First().Value, result.FollowingsSyncStatus.First().Value); + Assert.AreEqual(10, result.PostingErrorCount); } [TestMethod] @@ -316,6 +320,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers }; result.Followings = updatedFollowing.ToList(); result.FollowingsSyncStatus = updatedFollowingSync; + result.PostingErrorCount = 5; await dal.UpdateFollowerAsync(result); result = await dal.GetFollowerAsync(acct, host); @@ -325,6 +330,41 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers Assert.AreEqual(updatedFollowingSync.Count, result.FollowingsSyncStatus.Count); Assert.AreEqual(updatedFollowingSync.First().Key, result.FollowingsSyncStatus.First().Key); Assert.AreEqual(updatedFollowingSync.First().Value, result.FollowingsSyncStatus.First().Value); + Assert.AreEqual(5, result.PostingErrorCount); + } + + [TestMethod] + public async Task CreateUpdateAndGetFollower_ResetErrorCount() + { + var acct = "myhandle"; + var host = "domain.ext"; + var following = new[] { 12, 19, 23 }; + var followingSync = new Dictionary() + { + {12, 165L}, + {19, 166L}, + {23, 167L} + }; + var inboxRoute = "/myhandle/inbox"; + var sharedInboxRoute = "/inbox"; + var actorId = $"https://{host}/{acct}"; + + var dal = new FollowersPostgresDal(_settings); + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); + var result = await dal.GetFollowerAsync(acct, host); + Assert.AreEqual(0, result.PostingErrorCount); + + result.PostingErrorCount = 5; + + await dal.UpdateFollowerAsync(result); + result = await dal.GetFollowerAsync(acct, host); + Assert.AreEqual(5, result.PostingErrorCount); + + result.PostingErrorCount = 0; + + await dal.UpdateFollowerAsync(result); + result = await dal.GetFollowerAsync(acct, host); + Assert.AreEqual(0, result.PostingErrorCount); } [TestMethod] From 713b0b0fd4ed197c7c29347e1a3d75ab1471ee61 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 10 Sep 2021 18:58:48 -0400 Subject: [PATCH 2/7] added error display --- src/BSLManager/App.cs | 12 ++++++++++-- src/BSLManager/Domain/FollowersListState.cs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/BSLManager/App.cs b/src/BSLManager/App.cs index 0e48262..37697cc 100644 --- a/src/BSLManager/App.cs +++ b/src/BSLManager/App.cs @@ -146,23 +146,31 @@ namespace BSLManager Width = Dim.Fill(), Height = 1 }; - var inbox = new Label($"Inbox: {follower.InboxRoute}") + var errors = new Label($"Posting Errors: {follower.PostingErrorCount}") { X = 1, Y = 4, Width = Dim.Fill(), Height = 1 }; - var sharedInbox = new Label($"Shared Inbox: {follower.SharedInboxRoute}") + var inbox = new Label($"Inbox: {follower.InboxRoute}") { X = 1, Y = 5, Width = Dim.Fill(), Height = 1 }; + var sharedInbox = new Label($"Shared Inbox: {follower.SharedInboxRoute}") + { + X = 1, + Y = 6, + Width = Dim.Fill(), + Height = 1 + }; dialog.Add(name); dialog.Add(following); + dialog.Add(errors); dialog.Add(inbox); dialog.Add(sharedInbox); dialog.Add(close); diff --git a/src/BSLManager/Domain/FollowersListState.cs b/src/BSLManager/Domain/FollowersListState.cs index f33acb8..02c2151 100644 --- a/src/BSLManager/Domain/FollowersListState.cs +++ b/src/BSLManager/Domain/FollowersListState.cs @@ -26,7 +26,7 @@ namespace BSLManager.Domain foreach (var follower in _sourceUserList) { - var displayedUser = $"{GetFullHandle(follower)} ({follower.Followings.Count})"; + var displayedUser = $"{GetFullHandle(follower)} ({follower.Followings.Count}) (err:{follower.PostingErrorCount})"; _filteredDisplayableUserList.Add(displayedUser); } } From 77e3caebe08b5aa8adceba7c97edda095f93394c Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 10 Sep 2021 20:47:02 -0400 Subject: [PATCH 3/7] added saving posting errors --- .../SendTweetsToFollowersProcessor.cs | 35 +++++++++++++--- .../SendTweetsToFollowersProcessorTests.cs | 40 ++++++++++++++++--- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs b/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs index cb1efb6..325a6b8 100644 --- a/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs +++ b/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs @@ -22,14 +22,16 @@ namespace BirdsiteLive.Pipeline.Processors { private readonly ISendTweetsToInboxTask _sendTweetsToInboxTask; private readonly ISendTweetsToSharedInboxTask _sendTweetsToSharedInbox; + private readonly IFollowersDal _followersDal; private readonly ILogger _logger; #region Ctor - public SendTweetsToFollowersProcessor(ISendTweetsToInboxTask sendTweetsToInboxTask, ISendTweetsToSharedInboxTask sendTweetsToSharedInbox, ILogger logger) + public SendTweetsToFollowersProcessor(ISendTweetsToInboxTask sendTweetsToInboxTask, ISendTweetsToSharedInboxTask sendTweetsToSharedInbox, IFollowersDal followersDal, ILogger logger) { _sendTweetsToInboxTask = sendTweetsToInboxTask; _sendTweetsToSharedInbox = sendTweetsToSharedInbox; _logger = logger; + _followersDal = followersDal; } #endregion @@ -41,18 +43,18 @@ namespace BirdsiteLive.Pipeline.Processors var followersWtSharedInbox = userWithTweetsToSync.Followers .Where(x => !string.IsNullOrWhiteSpace(x.SharedInboxRoute)) .ToList(); - await ProcessFollowersWithSharedInbox(userWithTweetsToSync.Tweets, followersWtSharedInbox, user); + await ProcessFollowersWithSharedInboxAsync(userWithTweetsToSync.Tweets, followersWtSharedInbox, user); // Process Inbox var followerWtInbox = userWithTweetsToSync.Followers .Where(x => string.IsNullOrWhiteSpace(x.SharedInboxRoute)) .ToList(); - await ProcessFollowersWithInbox(userWithTweetsToSync.Tweets, followerWtInbox, user); + await ProcessFollowersWithInboxAsync(userWithTweetsToSync.Tweets, followerWtInbox, user); return userWithTweetsToSync; } - private async Task ProcessFollowersWithSharedInbox(ExtractedTweet[] tweets, List followers, SyncTwitterUser user) + private async Task ProcessFollowersWithSharedInboxAsync(ExtractedTweet[] tweets, List followers, SyncTwitterUser user) { var followersPerInstances = followers.GroupBy(x => x.Host); @@ -61,28 +63,51 @@ namespace BirdsiteLive.Pipeline.Processors try { await _sendTweetsToSharedInbox.ExecuteAsync(tweets, user, followersPerInstance.Key, followersPerInstance.ToArray()); + + foreach (var f in followers) + await ProcessWorkingUserAsync(f); } catch (Exception e) { var follower = followersPerInstance.First(); _logger.LogError(e, "Posting to {Host}{Route} failed", follower.Host, follower.SharedInboxRoute); + + foreach (var f in followersPerInstance) + await ProcessFailingUserAsync(f); } } } - private async Task ProcessFollowersWithInbox(ExtractedTweet[] tweets, List followerWtInbox, SyncTwitterUser user) + private async Task ProcessFollowersWithInboxAsync(ExtractedTweet[] tweets, List followerWtInbox, SyncTwitterUser user) { foreach (var follower in followerWtInbox) { try { await _sendTweetsToInboxTask.ExecuteAsync(tweets, follower, user); + await ProcessWorkingUserAsync(follower); } catch (Exception e) { _logger.LogError(e, "Posting to {Host}{Route} failed", follower.Host, follower.InboxRoute); + await ProcessFailingUserAsync(follower); } } } + + private async Task ProcessWorkingUserAsync(Follower follower) + { + if (follower.PostingErrorCount > 0) + { + follower.PostingErrorCount = 0; + await _followersDal.UpdateFollowerAsync(follower); + } + } + + private async Task ProcessFailingUserAsync(Follower follower) + { + follower.PostingErrorCount++; + await _followersDal.UpdateFollowerAsync(follower); + } } } \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SendTweetsToFollowersProcessorTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SendTweetsToFollowersProcessorTests.cs index 7715342..5fb4dd9 100644 --- a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SendTweetsToFollowersProcessorTests.cs +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SendTweetsToFollowersProcessorTests.cs @@ -1,6 +1,8 @@ using System; using System.Threading; using System.Threading.Tasks; +using System.Xml; +using BirdsiteLive.DAL.Contracts; using BirdsiteLive.DAL.Models; using BirdsiteLive.Pipeline.Models; using BirdsiteLive.Pipeline.Processors; @@ -69,15 +71,18 @@ namespace BirdsiteLive.Pipeline.Tests.Processors It.Is(y => y.Length == 2))) .Returns(Task.CompletedTask); + var followersDalMock = new Mock(MockBehavior.Strict); + var loggerMock = new Mock>(); #endregion - var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, loggerMock.Object); + var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, followersDalMock.Object, loggerMock.Object); var result = await processor.ProcessAsync(userWithTweets, CancellationToken.None); #region Validations sendTweetsToInboxTaskMock.VerifyAll(); sendTweetsToSharedInboxTaskMock.VerifyAll(); + followersDalMock.VerifyAll(); #endregion } @@ -139,15 +144,18 @@ namespace BirdsiteLive.Pipeline.Tests.Processors .Returns(Task.CompletedTask); } + var followersDalMock = new Mock(MockBehavior.Strict); + var loggerMock = new Mock>(); #endregion - var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, loggerMock.Object); + var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, followersDalMock.Object, loggerMock.Object); var result = await processor.ProcessAsync(userWithTweets, CancellationToken.None); #region Validations sendTweetsToInboxTaskMock.VerifyAll(); sendTweetsToSharedInboxTaskMock.VerifyAll(); + followersDalMock.VerifyAll(); #endregion } @@ -214,15 +222,22 @@ namespace BirdsiteLive.Pipeline.Tests.Processors It.Is(y => y.Length == 1))) .Throws(new Exception()); + var followersDalMock = new Mock(MockBehavior.Strict); + + followersDalMock + .Setup(x => x.UpdateFollowerAsync(It.Is(y => y.Id == userId2 && y.PostingErrorCount == 1))) + .Returns(Task.CompletedTask); + var loggerMock = new Mock>(); #endregion - var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, loggerMock.Object); + var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, followersDalMock.Object, loggerMock.Object); var result = await processor.ProcessAsync(userWithTweets, CancellationToken.None); #region Validations sendTweetsToInboxTaskMock.VerifyAll(); sendTweetsToSharedInboxTaskMock.VerifyAll(); + followersDalMock.VerifyAll(); #endregion } @@ -282,15 +297,18 @@ namespace BirdsiteLive.Pipeline.Tests.Processors var sendTweetsToSharedInboxTaskMock = new Mock(MockBehavior.Strict); + var followersDalMock = new Mock(MockBehavior.Strict); + var loggerMock = new Mock>(); #endregion - var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, loggerMock.Object); + var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, followersDalMock.Object, loggerMock.Object); var result = await processor.ProcessAsync(userWithTweets, CancellationToken.None); #region Validations sendTweetsToInboxTaskMock.VerifyAll(); sendTweetsToSharedInboxTaskMock.VerifyAll(); + followersDalMock.VerifyAll(); #endregion } @@ -351,15 +369,18 @@ namespace BirdsiteLive.Pipeline.Tests.Processors var sendTweetsToSharedInboxTaskMock = new Mock(MockBehavior.Strict); + var followersDalMock = new Mock(MockBehavior.Strict); + var loggerMock = new Mock>(); #endregion - var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, loggerMock.Object); + var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, followersDalMock.Object, loggerMock.Object); var result = await processor.ProcessAsync(userWithTweets, CancellationToken.None); #region Validations sendTweetsToInboxTaskMock.VerifyAll(); sendTweetsToSharedInboxTaskMock.VerifyAll(); + followersDalMock.VerifyAll(); #endregion } @@ -424,15 +445,22 @@ namespace BirdsiteLive.Pipeline.Tests.Processors var sendTweetsToSharedInboxTaskMock = new Mock(MockBehavior.Strict); + var followersDalMock = new Mock(MockBehavior.Strict); + + followersDalMock + .Setup(x => x.UpdateFollowerAsync(It.Is(y => y.Id == userId2 && y.PostingErrorCount == 1))) + .Returns(Task.CompletedTask); + var loggerMock = new Mock>(); #endregion - var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, loggerMock.Object); + var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, followersDalMock.Object, loggerMock.Object); var result = await processor.ProcessAsync(userWithTweets, CancellationToken.None); #region Validations sendTweetsToInboxTaskMock.VerifyAll(); sendTweetsToSharedInboxTaskMock.VerifyAll(); + followersDalMock.VerifyAll(); #endregion } } From d3d330d74e5d9902b8cea8040cc73ce502ec9731 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 10 Sep 2021 20:49:49 -0400 Subject: [PATCH 4/7] added tests for reset errors --- .../SendTweetsToFollowersProcessorTests.cs | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SendTweetsToFollowersProcessorTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SendTweetsToFollowersProcessorTests.cs index 5fb4dd9..899e19f 100644 --- a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SendTweetsToFollowersProcessorTests.cs +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SendTweetsToFollowersProcessorTests.cs @@ -241,6 +241,89 @@ namespace BirdsiteLive.Pipeline.Tests.Processors #endregion } + [TestMethod] + public async Task ProcessAsync_MultiInstances_SharedInbox_OneTweet_ErrorReset_Test() + { + #region Stubs + var tweetId = 1; + var host1 = "domain1.ext"; + var host2 = "domain2.ext"; + var sharedInbox = "/inbox"; + var userId1 = 2; + var userId2 = 3; + var userAcct = "user"; + + var userWithTweets = new UserWithDataToSync() + { + Tweets = new[] + { + new ExtractedTweet + { + Id = tweetId + } + }, + User = new SyncTwitterUser + { + Acct = userAcct + }, + Followers = new[] + { + new Follower + { + Id = userId1, + Host = host1, + SharedInboxRoute = sharedInbox + }, + new Follower + { + Id = userId2, + Host = host2, + SharedInboxRoute = sharedInbox, + PostingErrorCount = 50 + }, + } + }; + #endregion + + #region Mocks + var sendTweetsToInboxTaskMock = new Mock(MockBehavior.Strict); + + var sendTweetsToSharedInboxTaskMock = new Mock(MockBehavior.Strict); + sendTweetsToSharedInboxTaskMock + .Setup(x => x.ExecuteAsync( + It.Is(y => y.Length == 1), + It.Is(y => y.Acct == userAcct), + It.Is(y => y == host1), + It.Is(y => y.Length == 1))) + .Returns(Task.CompletedTask); + + sendTweetsToSharedInboxTaskMock + .Setup(x => x.ExecuteAsync( + It.Is(y => y.Length == 1), + It.Is(y => y.Acct == userAcct), + It.Is(y => y == host2), + It.Is(y => y.Length == 1))) + .Returns(Task.CompletedTask); + + var followersDalMock = new Mock(MockBehavior.Strict); + + followersDalMock + .Setup(x => x.UpdateFollowerAsync(It.Is(y => y.Id == userId2 && y.PostingErrorCount == 0))) + .Returns(Task.CompletedTask); + + var loggerMock = new Mock>(); + #endregion + + var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, followersDalMock.Object, loggerMock.Object); + var result = await processor.ProcessAsync(userWithTweets, CancellationToken.None); + + #region Validations + sendTweetsToInboxTaskMock.VerifyAll(); + sendTweetsToSharedInboxTaskMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + [TestMethod] public async Task ProcessAsync_SameInstance_Inbox_OneTweet_Test() { @@ -463,5 +546,86 @@ namespace BirdsiteLive.Pipeline.Tests.Processors followersDalMock.VerifyAll(); #endregion } + + [TestMethod] + public async Task ProcessAsync_MultiInstances_Inbox_OneTweet_ErrorReset_Test() + { + #region Stubs + var tweetId = 1; + var host1 = "domain1.ext"; + var host2 = "domain2.ext"; + var inbox = "/user/inbox"; + var userId1 = 2; + var userId2 = 3; + var userAcct = "user"; + + var userWithTweets = new UserWithDataToSync() + { + Tweets = new[] + { + new ExtractedTweet + { + Id = tweetId + } + }, + User = new SyncTwitterUser + { + Acct = userAcct + }, + Followers = new[] + { + new Follower + { + Id = userId1, + Host = host1, + InboxRoute = inbox + }, + new Follower + { + Id = userId2, + Host = host2, + InboxRoute = inbox, + PostingErrorCount = 50 + }, + } + }; + #endregion + + #region Mocks + var sendTweetsToInboxTaskMock = new Mock(MockBehavior.Strict); + sendTweetsToInboxTaskMock + .Setup(x => x.ExecuteAsync( + It.Is(y => y.Length == 1), + It.Is(y => y.Id == userId1), + It.Is(y => y.Acct == userAcct))) + .Returns(Task.CompletedTask); + + sendTweetsToInboxTaskMock + .Setup(x => x.ExecuteAsync( + It.Is(y => y.Length == 1), + It.Is(y => y.Id == userId2), + It.Is(y => y.Acct == userAcct))) + .Returns(Task.CompletedTask); + + var sendTweetsToSharedInboxTaskMock = new Mock(MockBehavior.Strict); + + var followersDalMock = new Mock(MockBehavior.Strict); + + followersDalMock + .Setup(x => x.UpdateFollowerAsync(It.Is(y => y.Id == userId2 && y.PostingErrorCount == 0))) + .Returns(Task.CompletedTask); + + var loggerMock = new Mock>(); + #endregion + + var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, followersDalMock.Object, loggerMock.Object); + var result = await processor.ProcessAsync(userWithTweets, CancellationToken.None); + + #region Validations + sendTweetsToInboxTaskMock.VerifyAll(); + sendTweetsToSharedInboxTaskMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } } } \ No newline at end of file From 806463c126957708cddca266b87959b60ef4fa8c Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 10 Sep 2021 20:50:21 -0400 Subject: [PATCH 5/7] road to 0.18.3 --- src/BirdsiteLive/BirdsiteLive.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BirdsiteLive/BirdsiteLive.csproj b/src/BirdsiteLive/BirdsiteLive.csproj index 3b07184..7385e77 100644 --- a/src/BirdsiteLive/BirdsiteLive.csproj +++ b/src/BirdsiteLive/BirdsiteLive.csproj @@ -4,7 +4,7 @@ netcoreapp3.1 d21486de-a812-47eb-a419-05682bb68856 Linux - 0.18.2 + 0.18.3 From 6e978f1cddeee5ef2d9ff51a9ac6fa35d596f956 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 10 Sep 2021 22:54:24 -0400 Subject: [PATCH 6/7] switch to alpine image --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 11a4422..5f69f30 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:3.1-buster-slim AS base +FROM mcr.microsoft.com/dotnet/aspnet:3.1-alpine AS base WORKDIR /app EXPOSE 80 EXPOSE 443 From 18d2096dc3f34c0f19b4f03792d1f3faf1bac535 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 10 Sep 2021 23:28:36 -0400 Subject: [PATCH 7/7] fix follower interator --- .../SendTweetsToFollowersProcessor.cs | 2 +- .../SendTweetsToFollowersProcessorTests.cs | 174 ++++++++++++++++++ 2 files changed, 175 insertions(+), 1 deletion(-) diff --git a/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs b/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs index 325a6b8..65f9610 100644 --- a/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs +++ b/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs @@ -64,7 +64,7 @@ namespace BirdsiteLive.Pipeline.Processors { await _sendTweetsToSharedInbox.ExecuteAsync(tweets, user, followersPerInstance.Key, followersPerInstance.ToArray()); - foreach (var f in followers) + foreach (var f in followersPerInstance) await ProcessWorkingUserAsync(f); } catch (Exception e) diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SendTweetsToFollowersProcessorTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SendTweetsToFollowersProcessorTests.cs index 899e19f..53aa12a 100644 --- a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SendTweetsToFollowersProcessorTests.cs +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/SendTweetsToFollowersProcessorTests.cs @@ -324,6 +324,94 @@ namespace BirdsiteLive.Pipeline.Tests.Processors #endregion } + [TestMethod] + public async Task ProcessAsync_MultiInstances_SharedInbox_OneTweet_ErrorAndReset_Test() + { + #region Stubs + var tweetId = 1; + var host1 = "domain1.ext"; + var host2 = "domain2.ext"; + var sharedInbox = "/inbox"; + var userId1 = 2; + var userId2 = 3; + var userAcct = "user"; + + var userWithTweets = new UserWithDataToSync() + { + Tweets = new[] + { + new ExtractedTweet + { + Id = tweetId + } + }, + User = new SyncTwitterUser + { + Acct = userAcct + }, + Followers = new[] + { + new Follower + { + Id = userId1, + Host = host1, + SharedInboxRoute = sharedInbox, + PostingErrorCount = 50 + }, + new Follower + { + Id = userId2, + Host = host2, + SharedInboxRoute = sharedInbox, + PostingErrorCount = 50 + }, + } + }; + #endregion + + #region Mocks + var sendTweetsToInboxTaskMock = new Mock(MockBehavior.Strict); + + var sendTweetsToSharedInboxTaskMock = new Mock(MockBehavior.Strict); + sendTweetsToSharedInboxTaskMock + .Setup(x => x.ExecuteAsync( + It.Is(y => y.Length == 1), + It.Is(y => y.Acct == userAcct), + It.Is(y => y == host1), + It.Is(y => y.Length == 1))) + .Returns(Task.CompletedTask); + + sendTweetsToSharedInboxTaskMock + .Setup(x => x.ExecuteAsync( + It.Is(y => y.Length == 1), + It.Is(y => y.Acct == userAcct), + It.Is(y => y == host2), + It.Is(y => y.Length == 1))) + .Throws(new Exception()); + + var followersDalMock = new Mock(MockBehavior.Strict); + + followersDalMock + .Setup(x => x.UpdateFollowerAsync(It.Is(y => y.Id == userId1 && y.PostingErrorCount == 0))) + .Returns(Task.CompletedTask); + + followersDalMock + .Setup(x => x.UpdateFollowerAsync(It.Is(y => y.Id == userId2 && y.PostingErrorCount == 51))) + .Returns(Task.CompletedTask); + + var loggerMock = new Mock>(); + #endregion + + var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, followersDalMock.Object, loggerMock.Object); + var result = await processor.ProcessAsync(userWithTweets, CancellationToken.None); + + #region Validations + sendTweetsToInboxTaskMock.VerifyAll(); + sendTweetsToSharedInboxTaskMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } + [TestMethod] public async Task ProcessAsync_SameInstance_Inbox_OneTweet_Test() { @@ -627,5 +715,91 @@ namespace BirdsiteLive.Pipeline.Tests.Processors followersDalMock.VerifyAll(); #endregion } + + [TestMethod] + public async Task ProcessAsync_MultiInstances_Inbox_OneTweet_ErrorAndReset_Test() + { + #region Stubs + var tweetId = 1; + var host1 = "domain1.ext"; + var host2 = "domain2.ext"; + var inbox = "/user/inbox"; + var userId1 = 2; + var userId2 = 3; + var userAcct = "user"; + + var userWithTweets = new UserWithDataToSync() + { + Tweets = new[] + { + new ExtractedTweet + { + Id = tweetId + } + }, + User = new SyncTwitterUser + { + Acct = userAcct + }, + Followers = new[] + { + new Follower + { + Id = userId1, + Host = host1, + InboxRoute = inbox, + PostingErrorCount = 50 + }, + new Follower + { + Id = userId2, + Host = host2, + InboxRoute = inbox, + PostingErrorCount = 50 + }, + } + }; + #endregion + + #region Mocks + var sendTweetsToInboxTaskMock = new Mock(MockBehavior.Strict); + sendTweetsToInboxTaskMock + .Setup(x => x.ExecuteAsync( + It.Is(y => y.Length == 1), + It.Is(y => y.Id == userId1), + It.Is(y => y.Acct == userAcct))) + .Returns(Task.CompletedTask); + + sendTweetsToInboxTaskMock + .Setup(x => x.ExecuteAsync( + It.Is(y => y.Length == 1), + It.Is(y => y.Id == userId2), + It.Is(y => y.Acct == userAcct))) + .Throws(new Exception()); + + var sendTweetsToSharedInboxTaskMock = new Mock(MockBehavior.Strict); + + var followersDalMock = new Mock(MockBehavior.Strict); + + followersDalMock + .Setup(x => x.UpdateFollowerAsync(It.Is(y => y.Id == userId1 && y.PostingErrorCount == 0))) + .Returns(Task.CompletedTask); + + followersDalMock + .Setup(x => x.UpdateFollowerAsync(It.Is(y => y.Id == userId2 && y.PostingErrorCount == 51))) + .Returns(Task.CompletedTask); + + var loggerMock = new Mock>(); + #endregion + + var processor = new SendTweetsToFollowersProcessor(sendTweetsToInboxTaskMock.Object, sendTweetsToSharedInboxTaskMock.Object, followersDalMock.Object, loggerMock.Object); + var result = await processor.ProcessAsync(userWithTweets, CancellationToken.None); + + #region Validations + sendTweetsToInboxTaskMock.VerifyAll(); + sendTweetsToSharedInboxTaskMock.VerifyAll(); + followersDalMock.VerifyAll(); + #endregion + } } } \ No newline at end of file