From f0fce82d27b46dbafcff5ee8e60263bf60fe1e86 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Wed, 3 Feb 2021 01:25:47 -0500 Subject: [PATCH 01/44] added moderation settings --- .../Settings/ModerationSettings.cs | 10 ++++++++++ src/BirdsiteLive/Startup.cs | 5 ++++- src/BirdsiteLive/appsettings.json | 12 +++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 src/BirdsiteLive.Common/Settings/ModerationSettings.cs diff --git a/src/BirdsiteLive.Common/Settings/ModerationSettings.cs b/src/BirdsiteLive.Common/Settings/ModerationSettings.cs new file mode 100644 index 0000000..ad9fd54 --- /dev/null +++ b/src/BirdsiteLive.Common/Settings/ModerationSettings.cs @@ -0,0 +1,10 @@ +namespace BirdsiteLive.Common.Settings +{ + public class ModerationSettings + { + public string FollowersWhiteListing { get; set; } + public string FollowersBlackListing { get; set; } + public string TwitterAccountsWhiteListing { get; set; } + public string TwitterAccountsBlackListing { get; set; } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive/Startup.cs b/src/BirdsiteLive/Startup.cs index ab50932..5686d12 100644 --- a/src/BirdsiteLive/Startup.cs +++ b/src/BirdsiteLive/Startup.cs @@ -66,7 +66,10 @@ namespace BirdsiteLive var logsSettings = Configuration.GetSection("Logging").Get(); services.For().Use(x => logsSettings); - + + var moderationSettings = Configuration.GetSection("Moderation").Get(); + services.For().Use(x => moderationSettings); + if (string.Equals(dbSettings.Type, DbTypes.Postgres, StringComparison.OrdinalIgnoreCase)) { var connString = $"Host={dbSettings.Host};Username={dbSettings.User};Password={dbSettings.Password};Database={dbSettings.Name}"; diff --git a/src/BirdsiteLive/appsettings.json b/src/BirdsiteLive/appsettings.json index 3dc47ff..3b41952 100644 --- a/src/BirdsiteLive/appsettings.json +++ b/src/BirdsiteLive/appsettings.json @@ -10,12 +10,12 @@ }, "AllowedHosts": "*", "Instance": { - "Name": "BirdsiteLIVE", + "Name": "BirdsiteLIVE", "Domain": "domain.name", "AdminEmail": "me@domain.name", "ResolveMentionsInProfiles": true, - "PublishReplies": false, - "MaxUsersCapacity": 1400 + "PublishReplies": false, + "MaxUsersCapacity": 1400 }, "Db": { "Type": "postgres", @@ -27,5 +27,11 @@ "Twitter": { "ConsumerKey": "twitter.api.key", "ConsumerSecret": "twitter.api.key" + }, + "Moderation": { + "FollowersWhiteListing": null, + "FollowersBlackListing": null, + "TwitterAccountsWhiteListing": null, + "TwitterAccountsBlackListing": null } } From bcf207acb5381605efc81fcba31763e8f7930bf0 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Wed, 3 Feb 2021 23:54:02 -0500 Subject: [PATCH 02/44] added moderation repository and parsers --- .../Repository/ModerationRepository.cs | 143 ++++++++++++++++++ .../Tools/ModerationParser.cs | 20 +++ .../Tools/ModerationRegexParser.cs | 28 ++++ .../Tools/ModerationRegexParserTests.cs | 10 ++ 4 files changed, 201 insertions(+) create mode 100644 src/BirdsiteLive.Domain/Repository/ModerationRepository.cs create mode 100644 src/BirdsiteLive.Domain/Tools/ModerationParser.cs create mode 100644 src/BirdsiteLive.Domain/Tools/ModerationRegexParser.cs create mode 100644 src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationRegexParserTests.cs diff --git a/src/BirdsiteLive.Domain/Repository/ModerationRepository.cs b/src/BirdsiteLive.Domain/Repository/ModerationRepository.cs new file mode 100644 index 0000000..90c93ca --- /dev/null +++ b/src/BirdsiteLive.Domain/Repository/ModerationRepository.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using BirdsiteLive.Common.Settings; +using BirdsiteLive.Domain.Tools; +using Newtonsoft.Json.Converters; + +namespace BirdsiteLive.Domain.Repository +{ + public class ModerationRepository + { + private readonly Regex[] _followersWhiteListing; + private readonly Regex[] _followersBlackListing; + private readonly Regex[] _twitterAccountsWhiteListing; + private readonly Regex[] _twitterAccountsBlackListing; + + private readonly Dictionary _modMode = + new Dictionary(); + + #region Ctor + public ModerationRepository(ModerationSettings settings) + { + var parsedFollowersWhiteListing = ModerationParser.Parse(settings.FollowersWhiteListing); + var parsedFollowersBlackListing = ModerationParser.Parse(settings.FollowersBlackListing); + var parsedTwitterAccountsWhiteListing = ModerationParser.Parse(settings.TwitterAccountsWhiteListing); + var parsedTwitterAccountsBlackListing = ModerationParser.Parse(settings.TwitterAccountsBlackListing); + + _followersWhiteListing = parsedFollowersWhiteListing + .Select(x => ModerationRegexParser.Parse(ModerationEntityTypeEnum.Follower, x)) + .ToArray(); + _followersBlackListing = parsedFollowersBlackListing + .Select(x => ModerationRegexParser.Parse(ModerationEntityTypeEnum.Follower, x)) + .ToArray(); + _twitterAccountsWhiteListing = parsedTwitterAccountsWhiteListing + .Select(x => ModerationRegexParser.Parse(ModerationEntityTypeEnum.TwitterAccount, x)) + .ToArray(); + _twitterAccountsBlackListing = parsedTwitterAccountsBlackListing + .Select(x => ModerationRegexParser.Parse(ModerationEntityTypeEnum.TwitterAccount, x)) + .ToArray(); + + // Set Follower moderation politic + if (_followersWhiteListing.Any()) + _modMode.Add(ModerationEntityTypeEnum.Follower, ModerationTypeEnum.WhiteListing); + else if (_followersBlackListing.Any()) + _modMode.Add(ModerationEntityTypeEnum.Follower, ModerationTypeEnum.BlackListing); + else + _modMode.Add(ModerationEntityTypeEnum.Follower, ModerationTypeEnum.None); + + // Set Twitter account moderation politic + if (_twitterAccountsWhiteListing.Any()) + _modMode.Add(ModerationEntityTypeEnum.TwitterAccount, ModerationTypeEnum.WhiteListing); + else if (_twitterAccountsBlackListing.Any()) + _modMode.Add(ModerationEntityTypeEnum.TwitterAccount, ModerationTypeEnum.BlackListing); + else + _modMode.Add(ModerationEntityTypeEnum.TwitterAccount, ModerationTypeEnum.None); + } + #endregion + + public ModerationTypeEnum GetModerationType(ModerationEntityTypeEnum type) + { + return _modMode[type]; + } + + public ModeratedTypeEnum CheckStatus(ModerationEntityTypeEnum type, string entity) + { + if (_modMode[type] == ModerationTypeEnum.None) return ModeratedTypeEnum.None; + + switch (type) + { + case ModerationEntityTypeEnum.Follower: + return ProcessFollower(entity); + case ModerationEntityTypeEnum.TwitterAccount: + return ProcessTwitterAccount(entity); + } + + throw new NotImplementedException($"Type {type} is not supported"); + } + + private ModeratedTypeEnum ProcessFollower(string entity) + { + var politic = _modMode[ModerationEntityTypeEnum.Follower]; + + switch (politic) + { + case ModerationTypeEnum.None: + return ModeratedTypeEnum.None; + case ModerationTypeEnum.BlackListing: + if (_followersBlackListing.Any(x => x.IsMatch(entity))) + return ModeratedTypeEnum.BlackListed; + return ModeratedTypeEnum.None; + case ModerationTypeEnum.WhiteListing: + if (_followersWhiteListing.Any(x => x.IsMatch(entity))) + return ModeratedTypeEnum.WhiteListed; + return ModeratedTypeEnum.None; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private ModeratedTypeEnum ProcessTwitterAccount(string entity) + { + var politic = _modMode[ModerationEntityTypeEnum.TwitterAccount]; + + switch (politic) + { + case ModerationTypeEnum.None: + return ModeratedTypeEnum.None; + case ModerationTypeEnum.BlackListing: + if (_twitterAccountsBlackListing.Any(x => x.IsMatch(entity))) + return ModeratedTypeEnum.BlackListed; + return ModeratedTypeEnum.None; + case ModerationTypeEnum.WhiteListing: + if (_twitterAccountsWhiteListing.Any(x => x.IsMatch(entity))) + return ModeratedTypeEnum.WhiteListed; + return ModeratedTypeEnum.None; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + public enum ModerationEntityTypeEnum + { + Unknown = 0, + Follower = 1, + TwitterAccount = 2 + } + + public enum ModerationTypeEnum + { + None = 0, + BlackListing = 1, + WhiteListing = 2 + } + + public enum ModeratedTypeEnum + { + None = 0, + BlackListed = 1, + WhiteListed = 2 + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Domain/Tools/ModerationParser.cs b/src/BirdsiteLive.Domain/Tools/ModerationParser.cs new file mode 100644 index 0000000..51dd979 --- /dev/null +++ b/src/BirdsiteLive.Domain/Tools/ModerationParser.cs @@ -0,0 +1,20 @@ +using System; +using System.Linq; + +namespace BirdsiteLive.Domain.Tools +{ + public class ModerationParser + { + public static string[] Parse(string entry) + { + var separationChar = '|'; + if (entry.Contains(";")) separationChar = ';'; + else if (entry.Contains(",")) separationChar = ','; + + var splitEntries = entry + .Split(new[] {separationChar}, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.ToLowerInvariant()); + return splitEntries.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Domain/Tools/ModerationRegexParser.cs b/src/BirdsiteLive.Domain/Tools/ModerationRegexParser.cs new file mode 100644 index 0000000..6f4df11 --- /dev/null +++ b/src/BirdsiteLive.Domain/Tools/ModerationRegexParser.cs @@ -0,0 +1,28 @@ +using System; +using System.Text.RegularExpressions; +using BirdsiteLive.Domain.Repository; +using Org.BouncyCastle.Pkcs; + +namespace BirdsiteLive.Domain.Tools +{ + public class ModerationRegexParser + { + public static Regex Parse(ModerationEntityTypeEnum type, string data) + { + data = data.ToLowerInvariant().Trim(); + + if (type == ModerationEntityTypeEnum.Follower) + { + if (data.StartsWith("@")) + return new Regex($@"^{data}$"); + + if (data.StartsWith("*")) + data = data.Replace("*", "(.+)"); + + return new Regex($@"^@(.+)@{data}$"); + } + + return new Regex($@"^{data}$"); + } + } +} \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationRegexParserTests.cs b/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationRegexParserTests.cs new file mode 100644 index 0000000..fce5d71 --- /dev/null +++ b/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationRegexParserTests.cs @@ -0,0 +1,10 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace BirdsiteLive.Domain.Tests.Tools +{ + [TestClass] + public class ModerationRegexParserTests + { + + } +} \ No newline at end of file From 4b0fe657762119a95cc59d75b8d73b9f0a00ac4a Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 4 Feb 2021 00:06:19 -0500 Subject: [PATCH 03/44] added tests for RegexParser --- .../Tools/ModerationRegexParserTests.cs | 72 ++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationRegexParserTests.cs b/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationRegexParserTests.cs index fce5d71..2d780cf 100644 --- a/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationRegexParserTests.cs +++ b/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationRegexParserTests.cs @@ -1,10 +1,78 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using BirdsiteLive.Domain.Repository; +using BirdsiteLive.Domain.Tools; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace BirdsiteLive.Domain.Tests.Tools { [TestClass] public class ModerationRegexParserTests { - + [TestMethod] + public void Parse_TwitterAccount_Simple_Test() + { + #region Stubs + var pattern = "handle"; + #endregion + + var regex = ModerationRegexParser.Parse(ModerationEntityTypeEnum.TwitterAccount, pattern); + + #region Validations + Assert.IsTrue(regex.IsMatch(pattern)); + Assert.IsFalse(regex.IsMatch("handles")); + Assert.IsFalse(regex.IsMatch("andle")); + #endregion + } + + [TestMethod] + public void Parse_Follower_Handle_Test() + { + #region Stubs + var pattern = "@handle@domain.ext"; + #endregion + + var regex = ModerationRegexParser.Parse(ModerationEntityTypeEnum.Follower, pattern); + + #region Validations + Assert.IsTrue(regex.IsMatch(pattern)); + Assert.IsFalse(regex.IsMatch("@handle2@domain.ext")); + Assert.IsFalse(regex.IsMatch("@handle@seb.domain.ext")); + #endregion + } + + [TestMethod] + public void Parse_Follower_Domain_Test() + { + #region Stubs + var pattern = "domain.ext"; + #endregion + + var regex = ModerationRegexParser.Parse(ModerationEntityTypeEnum.Follower, pattern); + + #region Validations + Assert.IsTrue(regex.IsMatch("@handle@domain.ext")); + Assert.IsTrue(regex.IsMatch("@handle2@domain.ext")); + Assert.IsFalse(regex.IsMatch("@handle2@domain2.ext")); + Assert.IsFalse(regex.IsMatch("@handle@seb.domain.ext")); + #endregion + } + + [TestMethod] + public void Parse_Follower_SubDomains_Test() + { + #region Stubs + var pattern = "*.domain.ext"; + #endregion + + var regex = ModerationRegexParser.Parse(ModerationEntityTypeEnum.Follower, pattern); + + #region Validations + Assert.IsTrue(regex.IsMatch("@handle2@sub.domain.ext")); + Assert.IsTrue(regex.IsMatch("@han@sub3.domain.ext")); + Assert.IsFalse(regex.IsMatch("@handle@domain.ext")); + Assert.IsFalse(regex.IsMatch("@handle2@.domain.ext")); + Assert.IsFalse(regex.IsMatch("@handle2@domain2.ext")); + Assert.IsFalse(regex.IsMatch("@handle@seb.domain2.ext")); + #endregion + } } } \ No newline at end of file From 2d61ae9ae34a027bbe94e4bc41b85d37feb4a567 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 4 Feb 2021 00:13:49 -0500 Subject: [PATCH 04/44] added ModerationParser Tests --- .../Tools/ModerationParser.cs | 3 +- .../Tools/ModerationParserTests.cs | 121 ++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationParserTests.cs diff --git a/src/BirdsiteLive.Domain/Tools/ModerationParser.cs b/src/BirdsiteLive.Domain/Tools/ModerationParser.cs index 51dd979..888fe64 100644 --- a/src/BirdsiteLive.Domain/Tools/ModerationParser.cs +++ b/src/BirdsiteLive.Domain/Tools/ModerationParser.cs @@ -13,7 +13,8 @@ namespace BirdsiteLive.Domain.Tools var splitEntries = entry .Split(new[] {separationChar}, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.ToLowerInvariant()); + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.ToLowerInvariant().Trim()); return splitEntries.ToArray(); } } diff --git a/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationParserTests.cs b/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationParserTests.cs new file mode 100644 index 0000000..8284a1b --- /dev/null +++ b/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationParserTests.cs @@ -0,0 +1,121 @@ +using System.Linq; +using BirdsiteLive.Domain.Tools; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace BirdsiteLive.Domain.Tests.Tools +{ + [TestClass] + public class ModerationParserTests + { + [TestMethod] + public void Parse_Simple_Test() + { + #region Stubs + var entry = "test"; + #endregion + + var result = ModerationParser.Parse(entry); + + #region Validations + Assert.AreEqual(1, result.Length); + Assert.AreEqual("test", result.First()); + #endregion + } + + [TestMethod] + public void Parse_PipeSeparator_Test() + { + #region Stubs + var entry = "test|test2"; + #endregion + + var result = ModerationParser.Parse(entry); + + #region Validations + Assert.AreEqual(2, result.Length); + Assert.AreEqual("test", result[0]); + Assert.AreEqual("test2", result[1]); + #endregion + } + + [TestMethod] + public void Parse_SemicolonSeparator_Test() + { + #region Stubs + var entry = "test;test2"; + #endregion + + var result = ModerationParser.Parse(entry); + + #region Validations + Assert.AreEqual(2, result.Length); + Assert.AreEqual("test", result[0]); + Assert.AreEqual("test2", result[1]); + #endregion + } + + [TestMethod] + public void Parse_CommaSeparator_Test() + { + #region Stubs + var entry = "test,test2"; + #endregion + + var result = ModerationParser.Parse(entry); + + #region Validations + Assert.AreEqual(2, result.Length); + Assert.AreEqual("test", result[0]); + Assert.AreEqual("test2", result[1]); + #endregion + } + + [TestMethod] + public void Parse_SemicolonSeparator_EmptyEntry_Test() + { + #region Stubs + var entry = "test;test2;"; + #endregion + + var result = ModerationParser.Parse(entry); + + #region Validations + Assert.AreEqual(2, result.Length); + Assert.AreEqual("test", result[0]); + Assert.AreEqual("test2", result[1]); + #endregion + } + + [TestMethod] + public void Parse_SemicolonSeparator_WhiteSpace_Test() + { + #region Stubs + var entry = "test; test2"; + #endregion + + var result = ModerationParser.Parse(entry); + + #region Validations + Assert.AreEqual(2, result.Length); + Assert.AreEqual("test", result[0]); + Assert.AreEqual("test2", result[1]); + #endregion + } + + [TestMethod] + public void Parse_SemicolonSeparator_EmptyEntry_WhiteSpace_Test() + { + #region Stubs + var entry = "test; test2; "; + #endregion + + var result = ModerationParser.Parse(entry); + + #region Validations + Assert.AreEqual(2, result.Length); + Assert.AreEqual("test", result[0]); + Assert.AreEqual("test2", result[1]); + #endregion + } + } +} \ No newline at end of file From 024327ffe91c7c32c66f73a9266f918cac84f99a Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 4 Feb 2021 01:09:11 -0500 Subject: [PATCH 05/44] added ModerationRepository Tests --- .../Tools/ModerationParser.cs | 2 + .../BirdsiteLive.Domain.Tests.csproj | 4 + .../Repository/ModerationRepositoryTests.cs | 347 ++++++++++++++++++ .../Tools/ModerationParserTests.cs | 28 ++ 4 files changed, 381 insertions(+) create mode 100644 src/Tests/BirdsiteLive.Domain.Tests/Repository/ModerationRepositoryTests.cs diff --git a/src/BirdsiteLive.Domain/Tools/ModerationParser.cs b/src/BirdsiteLive.Domain/Tools/ModerationParser.cs index 888fe64..a338b20 100644 --- a/src/BirdsiteLive.Domain/Tools/ModerationParser.cs +++ b/src/BirdsiteLive.Domain/Tools/ModerationParser.cs @@ -7,6 +7,8 @@ namespace BirdsiteLive.Domain.Tools { public static string[] Parse(string entry) { + if (string.IsNullOrWhiteSpace(entry)) return new string[0]; + var separationChar = '|'; if (entry.Contains(";")) separationChar = ';'; else if (entry.Contains(",")) separationChar = ','; diff --git a/src/Tests/BirdsiteLive.Domain.Tests/BirdsiteLive.Domain.Tests.csproj b/src/Tests/BirdsiteLive.Domain.Tests/BirdsiteLive.Domain.Tests.csproj index 72a94c9..626c941 100644 --- a/src/Tests/BirdsiteLive.Domain.Tests/BirdsiteLive.Domain.Tests.csproj +++ b/src/Tests/BirdsiteLive.Domain.Tests/BirdsiteLive.Domain.Tests.csproj @@ -18,4 +18,8 @@ + + + + diff --git a/src/Tests/BirdsiteLive.Domain.Tests/Repository/ModerationRepositoryTests.cs b/src/Tests/BirdsiteLive.Domain.Tests/Repository/ModerationRepositoryTests.cs new file mode 100644 index 0000000..30ffb8e --- /dev/null +++ b/src/Tests/BirdsiteLive.Domain.Tests/Repository/ModerationRepositoryTests.cs @@ -0,0 +1,347 @@ +using BirdsiteLive.Common.Settings; +using BirdsiteLive.Domain.Repository; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace BirdsiteLive.Domain.Tests.Repository +{ + [TestClass] + public class ModerationRepositoryTests + { + #region GetModerationType + [TestMethod] + public void GetModerationType_Follower_WhiteListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + FollowersWhiteListing = "@me@domain.ext" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModerationTypeEnum.WhiteListing ,repo.GetModerationType(ModerationEntityTypeEnum.Follower)); + Assert.AreEqual(ModerationTypeEnum.None, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount)); + #endregion + } + + [TestMethod] + public void GetModerationType_TwitterAccount_WhiteListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + TwitterAccountsWhiteListing = "@me@domain.ext" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModerationTypeEnum.None, repo.GetModerationType(ModerationEntityTypeEnum.Follower)); + Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount)); + #endregion + } + + [TestMethod] + public void GetModerationType_FollowerTwitterAccount_WhiteListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + FollowersWhiteListing = "@me@domain.ext", + TwitterAccountsWhiteListing = "@me@domain.ext" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.Follower)); + Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount)); + #endregion + } + + [TestMethod] + public void GetModerationType_Follower_BlackListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + FollowersBlackListing = "@me@domain.ext" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModerationTypeEnum.BlackListing, repo.GetModerationType(ModerationEntityTypeEnum.Follower)); + Assert.AreEqual(ModerationTypeEnum.None, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount)); + #endregion + } + + [TestMethod] + public void GetModerationType_TwitterAccount_BlackListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + TwitterAccountsBlackListing = "@me@domain.ext" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModerationTypeEnum.None, repo.GetModerationType(ModerationEntityTypeEnum.Follower)); + Assert.AreEqual(ModerationTypeEnum.BlackListing, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount)); + #endregion + } + + [TestMethod] + public void GetModerationType_FollowerTwitterAccount_BlackListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + FollowersBlackListing = "@me@domain.ext", + TwitterAccountsBlackListing = "@me@domain.ext" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModerationTypeEnum.BlackListing, repo.GetModerationType(ModerationEntityTypeEnum.Follower)); + Assert.AreEqual(ModerationTypeEnum.BlackListing, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount)); + #endregion + } + + [TestMethod] + public void GetModerationType_Follower_BothListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + FollowersBlackListing = "@me@domain.ext", + FollowersWhiteListing = "@me@domain.ext", + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.Follower)); + Assert.AreEqual(ModerationTypeEnum.None, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount)); + #endregion + } + + [TestMethod] + public void GetModerationType_TwitterAccount_BothListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + TwitterAccountsBlackListing = "@me@domain.ext", + TwitterAccountsWhiteListing = "@me@domain.ext" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModerationTypeEnum.None, repo.GetModerationType(ModerationEntityTypeEnum.Follower)); + Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount)); + #endregion + } + + [TestMethod] + public void GetModerationType_FollowerTwitterAccount_BothListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + FollowersBlackListing = "@me@domain.ext", + FollowersWhiteListing = "@me@domain.ext", + TwitterAccountsBlackListing = "@me@domain.ext", + TwitterAccountsWhiteListing = "@me@domain.ext" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.Follower)); + Assert.AreEqual(ModerationTypeEnum.WhiteListing, repo.GetModerationType(ModerationEntityTypeEnum.TwitterAccount)); + #endregion + } + #endregion + + #region CheckStatus + [TestMethod] + public void CheckStatus_Follower_WhiteListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + FollowersWhiteListing = "@me@domain.ext" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me@domain.ext")); + Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain.ext")); + #endregion + } + + [TestMethod] + public void CheckStatus_Follower_WhiteListing_Instance_Test() + { + #region Stubs + var settings = new ModerationSettings + { + FollowersWhiteListing = "domain.ext" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me@domain.ext")); + Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain.ext")); + Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain2.ext")); + #endregion + } + + [TestMethod] + public void CheckStatus_Follower_WhiteListing_SubDomain_Test() + { + #region Stubs + var settings = new ModerationSettings + { + FollowersWhiteListing = "*.domain.ext" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me@s.domain.ext")); + Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@s2.domain.ext")); + Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain.ext")); + Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain2.ext")); + #endregion + } + + [TestMethod] + public void CheckStatus_TwitterAccount_WhiteListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + TwitterAccountsWhiteListing = "handle" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle")); + Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle2")); + #endregion + } + + [TestMethod] + public void CheckStatus_Follower_BlackListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + FollowersBlackListing = "@me@domain.ext" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModeratedTypeEnum.BlackListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me@domain.ext")); + Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain.ext")); + #endregion + } + + [TestMethod] + public void CheckStatus_TwitterAccount_BlackListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + TwitterAccountsBlackListing = "handle" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModeratedTypeEnum.BlackListed, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle")); + Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle2")); + #endregion + } + + [TestMethod] + public void CheckStatus_Follower_BothListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + FollowersWhiteListing = "@me@domain.ext", + FollowersBlackListing = "@me@domain.ext" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me@domain.ext")); + Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@me2@domain.ext")); + #endregion + } + + [TestMethod] + public void CheckStatus_TwitterAccount_BothListing_Test() + { + #region Stubs + var settings = new ModerationSettings + { + TwitterAccountsWhiteListing = "handle", + TwitterAccountsBlackListing = "handle" + }; + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModeratedTypeEnum.WhiteListed, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle")); + Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle2")); + #endregion + } + + [TestMethod] + public void CheckStatus_None_Test() + { + #region Stubs + var settings = new ModerationSettings(); + #endregion + + var repo = new ModerationRepository(settings); + + #region Validations + Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.Follower, "@handle@domain.ext")); + Assert.AreEqual(ModeratedTypeEnum.None, repo.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, "handle")); + #endregion + } + #endregion + } +} \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationParserTests.cs b/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationParserTests.cs index 8284a1b..59030fa 100644 --- a/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationParserTests.cs +++ b/src/Tests/BirdsiteLive.Domain.Tests/Tools/ModerationParserTests.cs @@ -22,6 +22,34 @@ namespace BirdsiteLive.Domain.Tests.Tools #endregion } + [TestMethod] + public void Parse_Null_Test() + { + #region Stubs + string entry = null; + #endregion + + var result = ModerationParser.Parse(entry); + + #region Validations + Assert.AreEqual(0, result.Length); + #endregion + } + + [TestMethod] + public void Parse_WhiteSpace_Test() + { + #region Stubs + var entry = " "; + #endregion + + var result = ModerationParser.Parse(entry); + + #region Validations + Assert.AreEqual(0, result.Length); + #endregion + } + [TestMethod] public void Parse_PipeSeparator_Test() { From 3e772f2cd4fac1314e28bcade29cebfce839cf7e Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 4 Feb 2021 01:10:04 -0500 Subject: [PATCH 06/44] extracted ModerationRepository interface --- .../Repository/ModerationRepository.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/BirdsiteLive.Domain/Repository/ModerationRepository.cs b/src/BirdsiteLive.Domain/Repository/ModerationRepository.cs index 90c93ca..35e9fcb 100644 --- a/src/BirdsiteLive.Domain/Repository/ModerationRepository.cs +++ b/src/BirdsiteLive.Domain/Repository/ModerationRepository.cs @@ -8,7 +8,13 @@ using Newtonsoft.Json.Converters; namespace BirdsiteLive.Domain.Repository { - public class ModerationRepository + public interface IModerationRepository + { + ModerationTypeEnum GetModerationType(ModerationEntityTypeEnum type); + ModeratedTypeEnum CheckStatus(ModerationEntityTypeEnum type, string entity); + } + + public class ModerationRepository : IModerationRepository { private readonly Regex[] _followersWhiteListing; private readonly Regex[] _followersBlackListing; From 392c7ca4948575e632e7bb3c1d7f32037c1d467b Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 4 Feb 2021 18:56:14 -0500 Subject: [PATCH 07/44] added whitelisting checks in follows --- src/BirdsiteLive.Domain/UserService.cs | 112 ++++++++++++++++--------- 1 file changed, 74 insertions(+), 38 deletions(-) diff --git a/src/BirdsiteLive.Domain/UserService.cs b/src/BirdsiteLive.Domain/UserService.cs index b666c29..03abf21 100644 --- a/src/BirdsiteLive.Domain/UserService.cs +++ b/src/BirdsiteLive.Domain/UserService.cs @@ -10,6 +10,7 @@ using BirdsiteLive.ActivityPub.Converters; using BirdsiteLive.Common.Settings; using BirdsiteLive.Cryptography; using BirdsiteLive.Domain.BusinessUseCases; +using BirdsiteLive.Domain.Repository; using BirdsiteLive.Domain.Statistics; using BirdsiteLive.Domain.Tools; using BirdsiteLive.Twitter; @@ -39,8 +40,10 @@ namespace BirdsiteLive.Domain private readonly ITwitterUserService _twitterUserService; + private readonly IModerationRepository _moderationRepository; + #region Ctor - public UserService(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IProcessFollowUser processFollowUser, IProcessUndoFollowUser processUndoFollowUser, IStatusExtractor statusExtractor, IExtractionStatisticsHandler statisticsHandler, ITwitterUserService twitterUserService) + public UserService(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IProcessFollowUser processFollowUser, IProcessUndoFollowUser processUndoFollowUser, IStatusExtractor statusExtractor, IExtractionStatisticsHandler statisticsHandler, ITwitterUserService twitterUserService, IModerationRepository moderationRepository) { _instanceSettings = instanceSettings; _cryptoService = cryptoService; @@ -50,6 +53,7 @@ namespace BirdsiteLive.Domain _statusExtractor = statusExtractor; _statisticsHandler = statisticsHandler; _twitterUserService = twitterUserService; + _moderationRepository = moderationRepository; } #endregion @@ -118,62 +122,94 @@ namespace BirdsiteLive.Domain var sigValidation = await ValidateSignature(activity.actor, signature, method, path, queryString, requestHeaders, body); if (!sigValidation.SignatureIsValidated) return false; - // Save Follow in DB - var followerUserName = sigValidation.User.preferredUsername.ToLowerInvariant(); + // Prepare data + var followerUserName = sigValidation.User.preferredUsername.ToLowerInvariant().Trim(); var followerHost = sigValidation.User.url.Replace("https://", string.Empty).Split('/').First(); var followerInbox = sigValidation.User.inbox; var followerSharedInbox = sigValidation.User?.endpoints?.sharedInbox; - var twitterUser = activity.apObject.Split('/').Last().Replace("@", string.Empty); + var twitterUser = activity.apObject.Split('/').Last().Replace("@", string.Empty).ToLowerInvariant().Trim(); // Make sure to only keep routes followerInbox = OnlyKeepRoute(followerInbox, followerHost); followerSharedInbox = OnlyKeepRoute(followerSharedInbox, followerHost); + // Validate Moderation status + var followerModPolicy = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.Follower); + if (followerModPolicy != ModerationTypeEnum.None) + { + var followerStatus = _moderationRepository.CheckStatus(ModerationEntityTypeEnum.Follower, $"@{followerUserName}@{followerHost}"); + + if(followerModPolicy == ModerationTypeEnum.WhiteListing && followerStatus != ModeratedTypeEnum.WhiteListed || + followerModPolicy == ModerationTypeEnum.BlackListing && followerStatus == ModeratedTypeEnum.BlackListed) + return await SendRejectFollowAsync(activity, followerHost); + } + + // Validate TwitterAccount status + var twitterAccountModPolicy = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.TwitterAccount); + if (twitterAccountModPolicy != ModerationTypeEnum.None) + { + var twitterUserStatus = _moderationRepository.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, twitterUser); + if (twitterAccountModPolicy == ModerationTypeEnum.WhiteListing && twitterUserStatus != ModeratedTypeEnum.WhiteListed || + twitterAccountModPolicy == ModerationTypeEnum.BlackListing && twitterUserStatus == ModeratedTypeEnum.BlackListed) + return await SendRejectFollowAsync(activity, followerHost); + } + + // Validate User Protected var user = _twitterUserService.GetUser(twitterUser); if (!user.Protected) { // Execute await _processFollowUser.ExecuteAsync(followerUserName, followerHost, twitterUser, followerInbox, followerSharedInbox); - // Send Accept Activity - var acceptFollow = new ActivityAcceptFollow() - { - context = "https://www.w3.org/ns/activitystreams", - id = $"{activity.apObject}#accepts/follows/{Guid.NewGuid()}", - type = "Accept", - actor = activity.apObject, - apObject = new ActivityFollow() - { - id = activity.id, - type = activity.type, - actor = activity.actor, - apObject = activity.apObject - } - }; - var result = await _activityPubService.PostDataAsync(acceptFollow, followerHost, activity.apObject); - return result == HttpStatusCode.Accepted || result == HttpStatusCode.OK; //TODO: revamp this for better error handling + return await SendAcceptFollowAsync(activity, followerHost); } else { - // Send Reject Activity - var acceptFollow = new ActivityRejectFollow() - { - context = "https://www.w3.org/ns/activitystreams", - id = $"{activity.apObject}#rejects/follows/{Guid.NewGuid()}", - type = "Reject", - actor = activity.apObject, - apObject = new ActivityFollow() - { - id = activity.id, - type = activity.type, - actor = activity.actor, - apObject = activity.apObject - } - }; - var result = await _activityPubService.PostDataAsync(acceptFollow, followerHost, activity.apObject); - return result == HttpStatusCode.Accepted || result == HttpStatusCode.OK; //TODO: revamp this for better error handling + return await SendRejectFollowAsync(activity, followerHost); } } + + private async Task SendAcceptFollowAsync(ActivityFollow activity, string followerHost) + { + var acceptFollow = new ActivityAcceptFollow() + { + context = "https://www.w3.org/ns/activitystreams", + id = $"{activity.apObject}#accepts/follows/{Guid.NewGuid()}", + type = "Accept", + actor = activity.apObject, + apObject = new ActivityFollow() + { + id = activity.id, + type = activity.type, + actor = activity.actor, + apObject = activity.apObject + } + }; + var result = await _activityPubService.PostDataAsync(acceptFollow, followerHost, activity.apObject); + return result == HttpStatusCode.Accepted || + result == HttpStatusCode.OK; //TODO: revamp this for better error handling + } + + private async Task SendRejectFollowAsync(ActivityFollow activity, string followerHost) + { + var acceptFollow = new ActivityRejectFollow() + { + context = "https://www.w3.org/ns/activitystreams", + id = $"{activity.apObject}#rejects/follows/{Guid.NewGuid()}", + type = "Reject", + actor = activity.apObject, + apObject = new ActivityFollow() + { + id = activity.id, + type = activity.type, + actor = activity.actor, + apObject = activity.apObject + } + }; + var result = await _activityPubService.PostDataAsync(acceptFollow, followerHost, activity.apObject); + return result == HttpStatusCode.Accepted || + result == HttpStatusCode.OK; //TODO: revamp this for better error handling + } private string OnlyKeepRoute(string inbox, string host) { From 99203168632edb8bcc603068180e72f0306d1e48 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 5 Feb 2021 01:12:54 -0500 Subject: [PATCH 08/44] first iteration of logic to apply moderation policy --- .../Actions/RemoveFollowerAction.cs | 44 +++++++++++++ .../Actions/RemoveTwitterAccountAction.cs | 55 +++++++++++++++++ .../BirdsiteLive.Moderation.csproj | 16 +++++ .../ModerationPipeline.cs | 61 +++++++++++++++++++ .../Processors/FollowerModerationProcessor.cs | 44 +++++++++++++ .../TwitterAccountModerationProcessor.cs | 43 +++++++++++++ src/BirdsiteLive.sln | 9 ++- .../DataAccessLayers/FollowersPostgresDal.cs | 5 ++ .../TwitterUserPostgresDal.cs | 10 +++ .../Contracts/IFollowersDal.cs | 1 + .../Contracts/ITwitterUserDal.cs | 2 + 11 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs create mode 100644 src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs create mode 100644 src/BirdsiteLive.Moderation/BirdsiteLive.Moderation.csproj create mode 100644 src/BirdsiteLive.Moderation/ModerationPipeline.cs create mode 100644 src/BirdsiteLive.Moderation/Processors/FollowerModerationProcessor.cs create mode 100644 src/BirdsiteLive.Moderation/Processors/TwitterAccountModerationProcessor.cs diff --git a/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs b/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs new file mode 100644 index 0000000..8f0261a --- /dev/null +++ b/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs @@ -0,0 +1,44 @@ +using System.Linq; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; + +namespace BirdsiteLive.Moderation.Actions +{ + public interface IRemoveFollowerAction + { + Task ProcessAsync(Follower follower); + } + + public class RemoveFollowerAction : IRemoveFollowerAction + { + private readonly IFollowersDal _followersDal; + private readonly ITwitterUserDal _twitterUserDal; + + #region Ctor + public RemoveFollowerAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal) + { + _followersDal = followersDal; + _twitterUserDal = twitterUserDal; + } + #endregion + + public async Task ProcessAsync(Follower follower) + { + // Perform undo following to user instance + // TODO: Insert ActivityPub magic here + + // Remove twitter users if no more followers + var followings = follower.Followings; + foreach (var following in followings) + { + var followers = await _followersDal.GetFollowersAsync(following); + if (followers.Length == 1 && followers.First().Id == follower.Id) + await _twitterUserDal.DeleteTwitterUserAsync(following); + } + + // Remove follower from DB + await _followersDal.DeleteFollowerAsync(follower.Id); + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs b/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs new file mode 100644 index 0000000..cfadd15 --- /dev/null +++ b/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs @@ -0,0 +1,55 @@ +using System.Linq; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; + +namespace BirdsiteLive.Moderation.Actions +{ + public interface IRemoveTwitterAccountAction + { + Task ProcessAsync(SyncTwitterUser twitterUser); + } + + public class RemoveTwitterAccountAction : IRemoveTwitterAccountAction + { + private readonly IFollowersDal _followersDal; + private readonly ITwitterUserDal _twitterUserDal; + + #region Ctor + public RemoveTwitterAccountAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal) + { + _followersDal = followersDal; + _twitterUserDal = twitterUserDal; + } + #endregion + + public async Task ProcessAsync(SyncTwitterUser twitterUser) + { + // Check Followers + var twitterUserId = twitterUser.Id; + var followers = await _followersDal.GetFollowersAsync(twitterUserId); + + // Remove all Followers + foreach (var follower in followers) + { + // Perform undo following to user instance + // TODO: Insert ActivityPub magic here + + // Remove following from DB + if (follower.Followings.Contains(twitterUserId)) + follower.Followings.Remove(twitterUserId); + + if (follower.FollowingsSyncStatus.ContainsKey(twitterUserId)) + follower.FollowingsSyncStatus.Remove(twitterUserId); + + if (follower.Followings.Any()) + await _followersDal.UpdateFollowerAsync(follower); + else + await _followersDal.DeleteFollowerAsync(follower.Id); + } + + // Remove twitter user + await _twitterUserDal.DeleteTwitterUserAsync(twitterUser.Acct); + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Moderation/BirdsiteLive.Moderation.csproj b/src/BirdsiteLive.Moderation/BirdsiteLive.Moderation.csproj new file mode 100644 index 0000000..b7bbeea --- /dev/null +++ b/src/BirdsiteLive.Moderation/BirdsiteLive.Moderation.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.0 + + + + + + + + + + + + diff --git a/src/BirdsiteLive.Moderation/ModerationPipeline.cs b/src/BirdsiteLive.Moderation/ModerationPipeline.cs new file mode 100644 index 0000000..bc92a17 --- /dev/null +++ b/src/BirdsiteLive.Moderation/ModerationPipeline.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; +using BirdsiteLive.Domain.Repository; +using BirdsiteLive.Moderation.Processors; +using Microsoft.Extensions.Logging; + +namespace BirdsiteLive.Moderation +{ + public interface IModerationPipeline + { + Task ApplyModerationSettingsAsync(); + } + + public class ModerationPipeline : IModerationPipeline + { + private readonly IModerationRepository _moderationRepository; + private readonly IFollowerModerationProcessor _followerModerationProcessor; + private readonly ITwitterAccountModerationProcessor _twitterAccountModerationProcessor; + + private readonly ILogger _logger; + + #region Ctor + public ModerationPipeline(IModerationRepository moderationRepository, IFollowerModerationProcessor followerModerationProcessor, ITwitterAccountModerationProcessor twitterAccountModerationProcessor, ILogger logger) + { + _moderationRepository = moderationRepository; + _followerModerationProcessor = followerModerationProcessor; + _twitterAccountModerationProcessor = twitterAccountModerationProcessor; + _logger = logger; + } + #endregion + + public async Task ApplyModerationSettingsAsync() + { + try + { + await CheckFollowerModerationPolicyAsync(); + await CheckTwitterAccountModerationPolicyAsync(); + } + catch (Exception e) + { + _logger.LogCritical(e, "ModerationPipeline execution failed."); + } + } + + private async Task CheckFollowerModerationPolicyAsync() + { + var followerPolicy = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.Follower); + if (followerPolicy == ModerationTypeEnum.None) return; + + await _followerModerationProcessor.ProcessAsync(followerPolicy); + } + + private async Task CheckTwitterAccountModerationPolicyAsync() + { + var twitterAccountPolicy = _moderationRepository.GetModerationType(ModerationEntityTypeEnum.TwitterAccount); + if (twitterAccountPolicy == ModerationTypeEnum.None) return; + + await _twitterAccountModerationProcessor.ProcessAsync(twitterAccountPolicy); + } + } +} diff --git a/src/BirdsiteLive.Moderation/Processors/FollowerModerationProcessor.cs b/src/BirdsiteLive.Moderation/Processors/FollowerModerationProcessor.cs new file mode 100644 index 0000000..18c9b14 --- /dev/null +++ b/src/BirdsiteLive.Moderation/Processors/FollowerModerationProcessor.cs @@ -0,0 +1,44 @@ +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.Domain.Repository; +using BirdsiteLive.Moderation.Actions; + +namespace BirdsiteLive.Moderation.Processors +{ + public interface IFollowerModerationProcessor + { + Task ProcessAsync(ModerationTypeEnum type); + } + + public class FollowerModerationProcessor : IFollowerModerationProcessor + { + private readonly IFollowersDal _followersDal; + private readonly IModerationRepository _moderationRepository; + private readonly IRemoveFollowerAction _removeFollowerAction; + + #region Ctor + public FollowerModerationProcessor(IFollowersDal followersDal, IModerationRepository moderationRepository, IRemoveFollowerAction removeFollowerAction) + { + _followersDal = followersDal; + _moderationRepository = moderationRepository; + _removeFollowerAction = removeFollowerAction; + } + #endregion + + public async Task ProcessAsync(ModerationTypeEnum type) + { + var followers = await _followersDal.GetAllFollowersAsync(); + + foreach (var follower in followers) + { + var followerHandle = $"@{follower.Acct.Trim()}@{follower.Host.Trim()}".ToLowerInvariant(); + var status = _moderationRepository.CheckStatus(ModerationEntityTypeEnum.Follower, followerHandle); + + if (type == ModerationTypeEnum.WhiteListing && status != ModeratedTypeEnum.WhiteListed || + type == ModerationTypeEnum.BlackListing && status == ModeratedTypeEnum.BlackListed) + await _removeFollowerAction.ProcessAsync(follower); + } + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Moderation/Processors/TwitterAccountModerationProcessor.cs b/src/BirdsiteLive.Moderation/Processors/TwitterAccountModerationProcessor.cs new file mode 100644 index 0000000..3c267bb --- /dev/null +++ b/src/BirdsiteLive.Moderation/Processors/TwitterAccountModerationProcessor.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.Domain.Repository; +using BirdsiteLive.Moderation.Actions; + +namespace BirdsiteLive.Moderation.Processors +{ + public interface ITwitterAccountModerationProcessor + { + Task ProcessAsync(ModerationTypeEnum type); + } + + public class TwitterAccountModerationProcessor : ITwitterAccountModerationProcessor + { + private readonly ITwitterUserDal _twitterUserDal; + private readonly IModerationRepository _moderationRepository; + private readonly IRemoveTwitterAccountAction _removeTwitterAccountAction; + + #region Ctor + public TwitterAccountModerationProcessor(ITwitterUserDal twitterUserDal, IModerationRepository moderationRepository, IRemoveTwitterAccountAction removeTwitterAccountAction) + { + _twitterUserDal = twitterUserDal; + _moderationRepository = moderationRepository; + _removeTwitterAccountAction = removeTwitterAccountAction; + } + #endregion + + public async Task ProcessAsync(ModerationTypeEnum type) + { + var twitterUsers = await _twitterUserDal.GetAllTwitterUsersAsync(); + + foreach (var user in twitterUsers) + { + var userHandle = user.Acct.ToLowerInvariant().Trim(); + var status = _moderationRepository.CheckStatus(ModerationEntityTypeEnum.TwitterAccount, userHandle); + + if (type == ModerationTypeEnum.WhiteListing && status != ModeratedTypeEnum.WhiteListed || + type == ModerationTypeEnum.BlackListing && status == ModeratedTypeEnum.BlackListed) + await _removeTwitterAccountAction.ProcessAsync(user); + } + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.sln b/src/BirdsiteLive.sln index 0a35bf6..d345e68 100644 --- a/src/BirdsiteLive.sln +++ b/src/BirdsiteLive.sln @@ -39,7 +39,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Domain.Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Pipeline.Tests", "Tests\BirdsiteLive.Pipeline.Tests\BirdsiteLive.Pipeline.Tests.csproj", "{BF51CA81-5A7A-46F8-B4FB-861C6BE59298}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.DAL.Tests", "Tests\BirdsiteLive.DAL.Tests\BirdsiteLive.DAL.Tests.csproj", "{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.DAL.Tests", "Tests\BirdsiteLive.DAL.Tests\BirdsiteLive.DAL.Tests.csproj", "{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.Moderation", "BirdsiteLive.Moderation\BirdsiteLive.Moderation.csproj", "{4BE541AC-8A93-4FA3-98AC-956CC2D5B748}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -107,6 +109,10 @@ Global {5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}.Debug|Any CPU.Build.0 = Debug|Any CPU {5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}.Release|Any CPU.Build.0 = Release|Any CPU + {4BE541AC-8A93-4FA3-98AC-956CC2D5B748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BE541AC-8A93-4FA3-98AC-956CC2D5B748}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BE541AC-8A93-4FA3-98AC-956CC2D5B748}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BE541AC-8A93-4FA3-98AC-956CC2D5B748}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -126,6 +132,7 @@ Global {F544D745-89A8-4DEA-B61C-A7E6C53C1D63} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} {BF51CA81-5A7A-46F8-B4FB-861C6BE59298} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} {5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} + {4BE541AC-8A93-4FA3-98AC-956CC2D5B748} = {DA3C160C-4811-4E26-A5AD-42B81FAF2D7C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {69E8DCAD-4C37-4010-858F-5F94E6FBABCE} diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs index 39c07cb..07e1578 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs @@ -84,6 +84,11 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers } } + public async Task GetAllFollowersAsync() + { + throw new NotImplementedException(); + } + public async Task UpdateFollowerAsync(Follower follower) { if (follower == default) throw new ArgumentException("follower"); diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs index afbf7d1..847ae2c 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs @@ -49,6 +49,11 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers } } + public async Task DeleteTwitterUserAsync(int id) + { + throw new NotImplementedException(); + } + public async Task GetTwitterUsersCountAsync() { var query = $"SELECT COUNT(*) FROM {_settings.TwitterUserTableName}"; @@ -75,6 +80,11 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers } } + public async Task GetAllTwitterUsersAsync() + { + throw new NotImplementedException(); + } + public async Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, DateTime lastSync) { if(id == default) throw new ArgumentException("id"); diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IFollowersDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IFollowersDal.cs index 23cf2b2..6d20ce4 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IFollowersDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IFollowersDal.cs @@ -10,6 +10,7 @@ namespace BirdsiteLive.DAL.Contracts Task CreateFollowerAsync(string acct, string host, string inboxRoute, string sharedInboxRoute, int[] followings = null, Dictionary followingSyncStatus = null); Task GetFollowersAsync(int followedUserId); + Task GetAllFollowersAsync(); Task UpdateFollowerAsync(Follower follower); Task DeleteFollowerAsync(int id); Task DeleteFollowerAsync(string acct, string host); diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs index 1fa8127..3af2aa4 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs @@ -9,8 +9,10 @@ namespace BirdsiteLive.DAL.Contracts Task CreateTwitterUserAsync(string acct, long lastTweetPostedId); Task GetTwitterUserAsync(string acct); Task GetAllTwitterUsersAsync(int maxNumber); + Task GetAllTwitterUsersAsync(); Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, DateTime lastSync); Task DeleteTwitterUserAsync(string acct); + Task DeleteTwitterUserAsync(int id); Task GetTwitterUsersCountAsync(); } } \ No newline at end of file From 39419fd50c42cca02b3ef6b2724fc76ea6940ce6 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 5 Feb 2021 01:14:46 -0500 Subject: [PATCH 09/44] wirering moderation pipeline to service-worker --- src/BirdsiteLive/BirdsiteLive.csproj | 1 + src/BirdsiteLive/Services/FederationService.cs | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/BirdsiteLive/BirdsiteLive.csproj b/src/BirdsiteLive/BirdsiteLive.csproj index 607a195..a99c88c 100644 --- a/src/BirdsiteLive/BirdsiteLive.csproj +++ b/src/BirdsiteLive/BirdsiteLive.csproj @@ -18,6 +18,7 @@ + diff --git a/src/BirdsiteLive/Services/FederationService.cs b/src/BirdsiteLive/Services/FederationService.cs index 9acab41..2997ac3 100644 --- a/src/BirdsiteLive/Services/FederationService.cs +++ b/src/BirdsiteLive/Services/FederationService.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using BirdsiteLive.DAL; using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.Moderation; using BirdsiteLive.Pipeline; using Microsoft.Extensions.Hosting; @@ -12,12 +13,14 @@ namespace BirdsiteLive.Services public class FederationService : BackgroundService { private readonly IDatabaseInitializer _databaseInitializer; + private readonly IModerationPipeline _moderationPipeline; private readonly IStatusPublicationPipeline _statusPublicationPipeline; #region Ctor - public FederationService(IDatabaseInitializer databaseInitializer, IStatusPublicationPipeline statusPublicationPipeline) + public FederationService(IDatabaseInitializer databaseInitializer, IModerationPipeline moderationPipeline, IStatusPublicationPipeline statusPublicationPipeline) { _databaseInitializer = databaseInitializer; + _moderationPipeline = moderationPipeline; _statusPublicationPipeline = statusPublicationPipeline; } #endregion @@ -25,6 +28,7 @@ namespace BirdsiteLive.Services protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await _databaseInitializer.InitAndMigrateDbAsync(); + await _moderationPipeline.ApplyModerationSettingsAsync(); await _statusPublicationPipeline.ExecuteAsync(stoppingToken); } } From 0e1178f12857d80f7b28077b8884fa3799abbea4 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 11 Feb 2021 23:01:43 -0500 Subject: [PATCH 10/44] fix DI registration --- src/BirdsiteLive/Startup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BirdsiteLive/Startup.cs b/src/BirdsiteLive/Startup.cs index 5686d12..16c7ee4 100644 --- a/src/BirdsiteLive/Startup.cs +++ b/src/BirdsiteLive/Startup.cs @@ -99,6 +99,7 @@ namespace BirdsiteLive _.Assembly("BirdsiteLive.Domain"); _.Assembly("BirdsiteLive.DAL"); _.Assembly("BirdsiteLive.DAL.Postgres"); + _.Assembly("BirdsiteLive.Moderation"); _.Assembly("BirdsiteLive.Pipeline"); _.TheCallingAssembly(); From 4b1aa7aa5c3b1c9981f3343f87739b86be339cc1 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 11 Feb 2021 23:02:06 -0500 Subject: [PATCH 11/44] testing unfollow --- src/BirdsiteLive.Domain/UserService.cs | 4 +++- .../Controllers/DebugingController.cs | 18 +++++++++++++++++- src/BirdsiteLive/Views/Debuging/Index.cshtml | 7 +++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/BirdsiteLive.Domain/UserService.cs b/src/BirdsiteLive.Domain/UserService.cs index 03abf21..ab435a7 100644 --- a/src/BirdsiteLive.Domain/UserService.cs +++ b/src/BirdsiteLive.Domain/UserService.cs @@ -25,6 +25,8 @@ namespace BirdsiteLive.Domain Actor GetUser(TwitterUser twitterUser); Task FollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary requestHeaders, ActivityFollow activity, string body); Task UndoFollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary requestHeaders, ActivityUndoFollow activity, string body); + + Task SendRejectFollowAsync(ActivityFollow activity, string followerHost); } public class UserService : IUserService @@ -190,7 +192,7 @@ namespace BirdsiteLive.Domain result == HttpStatusCode.OK; //TODO: revamp this for better error handling } - private async Task SendRejectFollowAsync(ActivityFollow activity, string followerHost) + public async Task SendRejectFollowAsync(ActivityFollow activity, string followerHost) { var acceptFollow = new ActivityRejectFollow() { diff --git a/src/BirdsiteLive/Controllers/DebugingController.cs b/src/BirdsiteLive/Controllers/DebugingController.cs index 12ac90e..977687c 100644 --- a/src/BirdsiteLive/Controllers/DebugingController.cs +++ b/src/BirdsiteLive/Controllers/DebugingController.cs @@ -19,13 +19,15 @@ namespace BirdsiteLive.Controllers private readonly InstanceSettings _instanceSettings; private readonly ICryptoService _cryptoService; private readonly IActivityPubService _activityPubService; + private readonly IUserService _userService; #region Ctor - public DebugingController(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService) + public DebugingController(InstanceSettings instanceSettings, ICryptoService cryptoService, IActivityPubService activityPubService, IUserService userService) { _instanceSettings = instanceSettings; _cryptoService = cryptoService; _activityPubService = activityPubService; + _userService = userService; } #endregion @@ -102,6 +104,20 @@ namespace BirdsiteLive.Controllers return View("Index"); } + + [HttpPost] + public async Task PostRejectFollow() + { + var activityFollow = new ActivityFollow + { + type = "Follow", + actor = "https://mastodon.technology/users/testtest", + apObject = $"https://{_instanceSettings.Domain}/users/afp" + }; + + await _userService.SendRejectFollowAsync(activityFollow, "mastodon.technology"); + return View("Index"); + } } public static class HtmlHelperExtensions diff --git a/src/BirdsiteLive/Views/Debuging/Index.cshtml b/src/BirdsiteLive/Views/Debuging/Index.cshtml index cb56c56..5bcde75 100644 --- a/src/BirdsiteLive/Views/Debuging/Index.cshtml +++ b/src/BirdsiteLive/Views/Debuging/Index.cshtml @@ -16,4 +16,11 @@ + + + +
+ + +
\ No newline at end of file From eaae2f1f4799e6d440b01ef7bc63ac456122e4d4 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 12 Feb 2021 00:31:00 -0500 Subject: [PATCH 12/44] added AP unfollow + tests + db update --- .../BusinessUseCases/ProcessFollowUser.cs | 6 ++-- src/BirdsiteLive.Domain/UserService.cs | 2 +- .../Actions/RemoveFollowerAction.cs | 35 ++++++++++++++++-- .../Actions/RemoveTwitterAccountAction.cs | 30 ++++++++++++++-- .../DbInitializerPostgresDal.cs | 20 +++++++---- .../DataAccessLayers/FollowersPostgresDal.cs | 6 ++-- .../TwitterUserPostgresDal.cs | 13 +++++++ .../Contracts/IFollowersDal.cs | 2 +- .../Contracts/ITwitterUserDal.cs | 1 + .../BirdsiteLive.DAL/Models/Follower.cs | 1 + .../FollowersPostgresDalTests.cs | 36 ++++++++++++------- .../ProcessFollowUserTests.cs | 9 +++-- 12 files changed, 126 insertions(+), 35 deletions(-) diff --git a/src/BirdsiteLive.Domain/BusinessUseCases/ProcessFollowUser.cs b/src/BirdsiteLive.Domain/BusinessUseCases/ProcessFollowUser.cs index ac657e4..c6618d9 100644 --- a/src/BirdsiteLive.Domain/BusinessUseCases/ProcessFollowUser.cs +++ b/src/BirdsiteLive.Domain/BusinessUseCases/ProcessFollowUser.cs @@ -5,7 +5,7 @@ namespace BirdsiteLive.Domain.BusinessUseCases { public interface IProcessFollowUser { - Task ExecuteAsync(string followerUsername, string followerDomain, string twitterUsername, string followerInbox, string sharedInbox); + Task ExecuteAsync(string followerUsername, string followerDomain, string twitterUsername, string followerInbox, string sharedInbox, string followerActorId); } public class ProcessFollowUser : IProcessFollowUser @@ -21,13 +21,13 @@ namespace BirdsiteLive.Domain.BusinessUseCases } #endregion - public async Task ExecuteAsync(string followerUsername, string followerDomain, string twitterUsername, string followerInbox, string sharedInbox) + public async Task ExecuteAsync(string followerUsername, string followerDomain, string twitterUsername, string followerInbox, string sharedInbox, string followerActorId) { // Get Follower and Twitter Users var follower = await _followerDal.GetFollowerAsync(followerUsername, followerDomain); if (follower == null) { - await _followerDal.CreateFollowerAsync(followerUsername, followerDomain, followerInbox, sharedInbox); + await _followerDal.CreateFollowerAsync(followerUsername, followerDomain, followerInbox, sharedInbox, followerActorId); follower = await _followerDal.GetFollowerAsync(followerUsername, followerDomain); } diff --git a/src/BirdsiteLive.Domain/UserService.cs b/src/BirdsiteLive.Domain/UserService.cs index ab435a7..22b8784 100644 --- a/src/BirdsiteLive.Domain/UserService.cs +++ b/src/BirdsiteLive.Domain/UserService.cs @@ -161,7 +161,7 @@ namespace BirdsiteLive.Domain if (!user.Protected) { // Execute - await _processFollowUser.ExecuteAsync(followerUserName, followerHost, twitterUser, followerInbox, followerSharedInbox); + await _processFollowUser.ExecuteAsync(followerUserName, followerHost, twitterUser, followerInbox, followerSharedInbox, activity.actor); return await SendAcceptFollowAsync(activity, followerHost); } diff --git a/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs b/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs index 8f0261a..cf1d454 100644 --- a/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs +++ b/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs @@ -1,7 +1,12 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading.Tasks; +using BirdsiteLive.ActivityPub; +using BirdsiteLive.ActivityPub.Converters; +using BirdsiteLive.Common.Settings; using BirdsiteLive.DAL.Contracts; using BirdsiteLive.DAL.Models; +using BirdsiteLive.Domain; namespace BirdsiteLive.Moderation.Actions { @@ -14,19 +19,23 @@ namespace BirdsiteLive.Moderation.Actions { private readonly IFollowersDal _followersDal; private readonly ITwitterUserDal _twitterUserDal; + private readonly IUserService _userService; + private readonly InstanceSettings _instanceSettings; #region Ctor - public RemoveFollowerAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal) + public RemoveFollowerAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal, IUserService userService, InstanceSettings instanceSettings) { _followersDal = followersDal; _twitterUserDal = twitterUserDal; + _userService = userService; + _instanceSettings = instanceSettings; } #endregion public async Task ProcessAsync(Follower follower) { // Perform undo following to user instance - // TODO: Insert ActivityPub magic here + await RejectAllFollowingsAsync(follower); // Remove twitter users if no more followers var followings = follower.Followings; @@ -40,5 +49,25 @@ namespace BirdsiteLive.Moderation.Actions // Remove follower from DB await _followersDal.DeleteFollowerAsync(follower.Id); } + + private async Task RejectAllFollowingsAsync(Follower follower) + { + foreach (var following in follower.Followings) + { + try + { + var f = await _twitterUserDal.GetTwitterUserAsync(following); + var activityFollowing = new ActivityFollow + { + type = "Follow", + actor = follower.ActorId, + apObject = UrlFactory.GetActorUrl(_instanceSettings.Domain, f.Acct) + }; + + await _userService.SendRejectFollowAsync(activityFollowing, follower.Host); + } + catch (Exception) { } + } + } } } \ No newline at end of file diff --git a/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs b/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs index cfadd15..a689401 100644 --- a/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs +++ b/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs @@ -1,7 +1,12 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading.Tasks; +using BirdsiteLive.ActivityPub; +using BirdsiteLive.ActivityPub.Converters; +using BirdsiteLive.Common.Settings; using BirdsiteLive.DAL.Contracts; using BirdsiteLive.DAL.Models; +using BirdsiteLive.Domain; namespace BirdsiteLive.Moderation.Actions { @@ -14,12 +19,16 @@ namespace BirdsiteLive.Moderation.Actions { private readonly IFollowersDal _followersDal; private readonly ITwitterUserDal _twitterUserDal; + private readonly IUserService _userService; + private readonly InstanceSettings _instanceSettings; #region Ctor - public RemoveTwitterAccountAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal) + public RemoveTwitterAccountAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal, InstanceSettings instanceSettings, IUserService userService) { _followersDal = followersDal; _twitterUserDal = twitterUserDal; + _instanceSettings = instanceSettings; + _userService = userService; } #endregion @@ -33,7 +42,7 @@ namespace BirdsiteLive.Moderation.Actions foreach (var follower in followers) { // Perform undo following to user instance - // TODO: Insert ActivityPub magic here + await RejectFollowingAsync(follower, twitterUser); // Remove following from DB if (follower.Followings.Contains(twitterUserId)) @@ -51,5 +60,20 @@ namespace BirdsiteLive.Moderation.Actions // Remove twitter user await _twitterUserDal.DeleteTwitterUserAsync(twitterUser.Acct); } + + private async Task RejectFollowingAsync(Follower follower, SyncTwitterUser twitterUser) + { + try + { + var activityFollowing = new ActivityFollow + { + type = "Follow", + actor = follower.ActorId, + apObject = UrlFactory.GetActorUrl(_instanceSettings.Domain, twitterUser.Acct) + }; + await _userService.SendRejectFollowAsync(activityFollowing, follower.Host); + } + catch (Exception) { } + } } } \ No newline at end of file diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/DbInitializerPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/DbInitializerPostgresDal.cs index ca883ff..814578e 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, 0); + private readonly Version _currentVersion = new Version(2, 1); private const string DbVersionType = "db-version"; #region Ctor @@ -131,7 +131,8 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers { return new[] { - new Tuple(new Version(1,0), new Version(2,0)) + new Tuple(new Version(1,0), new Version(2,0)), + new Tuple(new Version(2,0), new Version(2,1)) }; } @@ -144,12 +145,19 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers var addIndex = $@"CREATE INDEX IF NOT EXISTS lastsync_twitteruser ON {_settings.TwitterUserTableName}(lastSync)"; await _tools.ExecuteRequestAsync(addIndex); - - await UpdateDbVersionAsync(to); - return to; + } + else if (from == new Version(2, 0) && to == new Version(2, 1)) + { + var addActorId = $@"ALTER TABLE {_settings.FollowersTableName} ADD actorId VARCHAR(2048)"; + await _tools.ExecuteRequestAsync(addActorId); + } + else + { + throw new NotImplementedException(); } - throw new NotImplementedException(); + await UpdateDbVersionAsync(to); + return to; } private async Task UpdateDbVersionAsync(Version newVersion) diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs index 07e1578..0a67cc7 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs @@ -20,7 +20,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers } #endregion - public async Task CreateFollowerAsync(string acct, string host, string inboxRoute, string sharedInboxRoute, int[] followings = null, Dictionary followingSyncStatus = null) + public async Task CreateFollowerAsync(string acct, string host, string inboxRoute, string sharedInboxRoute, string actorId, int[] followings = null, Dictionary followingSyncStatus = null) { if(followings == null) followings = new int[0]; if(followingSyncStatus == null) followingSyncStatus = new Dictionary(); @@ -35,8 +35,8 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers dbConnection.Open(); await dbConnection.ExecuteAsync( - $"INSERT INTO {_settings.FollowersTableName} (acct,host,inboxRoute,sharedInboxRoute,followings,followingsSyncStatus) VALUES(@acct,@host,@inboxRoute,@sharedInboxRoute,@followings,CAST(@followingsSyncStatus as json))", - new { acct, host, inboxRoute, sharedInboxRoute, followings, followingsSyncStatus = serializedDic }); + $"INSERT INTO {_settings.FollowersTableName} (acct,host,inboxRoute,sharedInboxRoute,followings,followingsSyncStatus,actorId) VALUES(@acct,@host,@inboxRoute,@sharedInboxRoute,@followings,CAST(@followingsSyncStatus as json),@actorId)", + new { acct, host, inboxRoute, sharedInboxRoute, followings, followingsSyncStatus = serializedDic, actorId }); } } diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs index 847ae2c..a782525 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs @@ -49,6 +49,19 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers } } + public async Task GetTwitterUserAsync(int id) + { + var query = $"SELECT * FROM {_settings.TwitterUserTableName} WHERE id = @id"; + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + var result = (await dbConnection.QueryAsync(query, new { id })).FirstOrDefault(); + return result; + } + } + public async Task DeleteTwitterUserAsync(int id) { throw new NotImplementedException(); diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IFollowersDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IFollowersDal.cs index 6d20ce4..86caa02 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IFollowersDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IFollowersDal.cs @@ -7,7 +7,7 @@ namespace BirdsiteLive.DAL.Contracts public interface IFollowersDal { Task GetFollowerAsync(string acct, string host); - Task CreateFollowerAsync(string acct, string host, string inboxRoute, string sharedInboxRoute, int[] followings = null, + Task CreateFollowerAsync(string acct, string host, string inboxRoute, string sharedInboxRoute, string actorId, int[] followings = null, Dictionary followingSyncStatus = null); Task GetFollowersAsync(int followedUserId); Task GetAllFollowersAsync(); diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs index 3af2aa4..cfa422a 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs @@ -8,6 +8,7 @@ namespace BirdsiteLive.DAL.Contracts { Task CreateTwitterUserAsync(string acct, long lastTweetPostedId); Task GetTwitterUserAsync(string acct); + Task GetTwitterUserAsync(int id); Task GetAllTwitterUsersAsync(int maxNumber); Task GetAllTwitterUsersAsync(); Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, DateTime lastSync); diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Models/Follower.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Models/Follower.cs index 8fbc97b..274852b 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/Models/Follower.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Models/Follower.cs @@ -9,6 +9,7 @@ namespace BirdsiteLive.DAL.Models public List Followings { get; set; } public Dictionary FollowingsSyncStatus { get; set; } + public string ActorId { get; set; } public string Acct { get; set; } public string Host { get; set; } public string InboxRoute { get; set; } diff --git a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs index cf08856..cc413c0 100644 --- a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs +++ b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs @@ -41,9 +41,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers }; var inboxRoute = "/myhandle/inbox"; var sharedInboxRoute = "/inbox"; + var actorId = $"https://{host}/{acct}"; var dal = new FollowersPostgresDal(_settings); - await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync); + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); var result = await dal.GetFollowerAsync(acct, host); @@ -73,9 +74,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers }; var inboxRoute = "/myhandle/inbox"; string sharedInboxRoute = null; + var actorId = $"https://{host}/{acct}"; var dal = new FollowersPostgresDal(_settings); - await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync); + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); var result = await dal.GetFollowerAsync(acct, host); @@ -103,7 +105,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers var followingSync = new Dictionary(); var inboxRoute = "/myhandle1/inbox"; var sharedInboxRoute = "/inbox"; - await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync); + var actorId = $"https://{host}/{acct}"; + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); //User 2 acct = "myhandle2"; @@ -111,7 +114,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers following = new[] { 2, 4, 5 }; inboxRoute = "/myhandle2/inbox"; sharedInboxRoute = "/inbox2"; - await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync); + actorId = $"https://{host}/{acct}"; + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); //User 2 acct = "myhandle3"; @@ -119,7 +123,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers following = new[] { 1 }; inboxRoute = "/myhandle3/inbox"; sharedInboxRoute = "/inbox3"; - await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync); + actorId = $"https://{host}/{acct}"; + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); var result = await dal.GetFollowersAsync(2); Assert.AreEqual(2, result.Length); @@ -146,7 +151,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers var followingSync = new Dictionary(); var inboxRoute = "/myhandle1/inbox"; var sharedInboxRoute = "/inbox"; - await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync); + var actorId = $"https://{host}/{acct}"; + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); //User 2 acct = "myhandle2"; @@ -154,7 +160,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers following = new[] { 2, 4, 5 }; inboxRoute = "/myhandle2/inbox"; sharedInboxRoute = "/inbox2"; - await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync); + actorId = $"https://{host}/{acct}"; + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); //User 2 acct = "myhandle3"; @@ -162,7 +169,8 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers following = new[] { 1 }; inboxRoute = "/myhandle3/inbox"; sharedInboxRoute = "/inbox3"; - await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync); + actorId = $"https://{host}/{acct}"; + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); result = await dal.GetFollowersCountAsync(); Assert.AreEqual(3, result); @@ -182,9 +190,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers }; var inboxRoute = "/myhandle/inbox"; var sharedInboxRoute = "/inbox"; + var actorId = $"https://{host}/{acct}"; var dal = new FollowersPostgresDal(_settings); - await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync); + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); var result = await dal.GetFollowerAsync(acct, host); var updatedFollowing = new List { 12, 19, 23, 24 }; @@ -222,9 +231,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers }; var inboxRoute = "/myhandle/inbox"; var sharedInboxRoute = "/inbox"; + var actorId = $"https://{host}/{acct}"; var dal = new FollowersPostgresDal(_settings); - await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync); + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); var result = await dal.GetFollowerAsync(acct, host); var updatedFollowing = new[] { 12, 19 }; @@ -260,9 +270,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers }; var inboxRoute = "/myhandle/inbox"; var sharedInboxRoute = "/inbox"; + var actorId = $"https://{host}/{acct}"; var dal = new FollowersPostgresDal(_settings); - await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync); + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); var result = await dal.GetFollowerAsync(acct, host); Assert.IsNotNull(result); @@ -286,9 +297,10 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers }; var inboxRoute = "/myhandle/inbox"; var sharedInboxRoute = "/inbox"; + var actorId = $"https://{host}/{acct}"; var dal = new FollowersPostgresDal(_settings); - await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, following, followingSync); + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); var result = await dal.GetFollowerAsync(acct, host); Assert.IsNotNull(result); diff --git a/src/Tests/BirdsiteLive.Domain.Tests/BusinessUseCases/ProcessFollowUserTests.cs b/src/Tests/BirdsiteLive.Domain.Tests/BusinessUseCases/ProcessFollowUserTests.cs index d5d735e..0fb03ae 100644 --- a/src/Tests/BirdsiteLive.Domain.Tests/BusinessUseCases/ProcessFollowUserTests.cs +++ b/src/Tests/BirdsiteLive.Domain.Tests/BusinessUseCases/ProcessFollowUserTests.cs @@ -21,6 +21,7 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases var twitterName = "handle"; var followerInbox = "/user/testest"; var inbox = "/inbox"; + var actorId = "actorUrl"; var follower = new Follower { @@ -55,6 +56,7 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases It.Is(y => y == domain), It.Is(y => y == followerInbox), It.Is(y => y == inbox), + It.Is(y => y == actorId), null, null)) .Returns(Task.CompletedTask); @@ -80,7 +82,7 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases #endregion var action = new ProcessFollowUser(followersDalMock.Object, twitterUserDalMock.Object); - await action.ExecuteAsync(username, domain, twitterName, followerInbox, inbox); + await action.ExecuteAsync(username, domain, twitterName, followerInbox, inbox, actorId); #region Validations followersDalMock.VerifyAll(); @@ -97,7 +99,8 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases var twitterName = "handle"; var followerInbox = "/user/testest"; var inbox = "/inbox"; - + var actorId = "actorUrl"; + var follower = new Follower { Id = 1, @@ -138,7 +141,7 @@ namespace BirdsiteLive.Domain.Tests.BusinessUseCases #endregion var action = new ProcessFollowUser(followersDalMock.Object, twitterUserDalMock.Object); - await action.ExecuteAsync(username, domain, twitterName, followerInbox, inbox); + await action.ExecuteAsync(username, domain, twitterName, followerInbox, inbox, actorId); #region Validations followersDalMock.VerifyAll(); From 90ca06b48bcb18a44989c0cfe53c686e10498fa9 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 12 Feb 2021 18:32:21 -0500 Subject: [PATCH 13/44] clean up --- .../DataAccessLayers/TwitterUserPostgresDal.cs | 7 +------ .../BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs index a782525..edee63d 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs @@ -61,12 +61,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers return result; } } - - public async Task DeleteTwitterUserAsync(int id) - { - throw new NotImplementedException(); - } - + public async Task GetTwitterUsersCountAsync() { var query = $"SELECT COUNT(*) FROM {_settings.TwitterUserTableName}"; diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs index cfa422a..4aa9eb6 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs @@ -13,7 +13,6 @@ namespace BirdsiteLive.DAL.Contracts Task GetAllTwitterUsersAsync(); Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, DateTime lastSync); Task DeleteTwitterUserAsync(string acct); - Task DeleteTwitterUserAsync(int id); Task GetTwitterUsersCountAsync(); } } \ No newline at end of file From c0049696bf925e9a62eeab293aa10c508de981b3 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 12 Feb 2021 19:05:35 -0500 Subject: [PATCH 14/44] TwitterUserDal implementation + tests --- .../TwitterUserPostgresDal.cs | 16 ++-- .../TwitterUserPostgresDalTests.cs | 80 ++++++++++++++++++- 2 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs index edee63d..714eb8f 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs @@ -5,9 +5,7 @@ using BirdsiteLive.DAL.Contracts; using BirdsiteLive.DAL.Models; using BirdsiteLive.DAL.Postgres.DataAccessLayers.Base; using BirdsiteLive.DAL.Postgres.Settings; -using BirdsiteLive.DAL.Postgres.Tools; using Dapper; -using Npgsql; namespace BirdsiteLive.DAL.Postgres.DataAccessLayers { @@ -44,7 +42,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers { dbConnection.Open(); - var result = (await dbConnection.QueryAsync(query, new { acct = acct })).FirstOrDefault(); + var result = (await dbConnection.QueryAsync(query, new { acct })).FirstOrDefault(); return result; } } @@ -90,7 +88,15 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers public async Task GetAllTwitterUsersAsync() { - throw new NotImplementedException(); + var query = $"SELECT * FROM {_settings.TwitterUserTableName}"; + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + var result = await dbConnection.QueryAsync(query); + return result.ToArray(); + } } public async Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, DateTime lastSync) @@ -112,7 +118,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers public async Task DeleteTwitterUserAsync(string acct) { - if (acct == default) throw new ArgumentException("acct"); + if (string.IsNullOrWhiteSpace(acct)) throw new ArgumentException("acct"); acct = acct.ToLowerInvariant(); diff --git a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/TwitterUserPostgresDalTests.cs b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/TwitterUserPostgresDalTests.cs index d71842f..b290735 100644 --- a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/TwitterUserPostgresDalTests.cs +++ b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/TwitterUserPostgresDalTests.cs @@ -50,6 +50,24 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers Assert.IsTrue(result.Id > 0); } + [TestMethod] + public async Task CreateAndGetUser_byId() + { + var acct = "myid"; + var lastTweetId = 1548L; + + var dal = new TwitterUserPostgresDal(_settings); + + await dal.CreateTwitterUserAsync(acct, lastTweetId); + var result = await dal.GetTwitterUserAsync(acct); + var resultById = await dal.GetTwitterUserAsync(result.Id); + + Assert.AreEqual(acct, resultById.Acct); + Assert.AreEqual(lastTweetId, resultById.LastTweetPostedId); + Assert.AreEqual(lastTweetId, resultById.LastTweetSynchronizedForAllFollowersId); + Assert.AreEqual(result.Id, resultById.Id); + } + [TestMethod] public async Task CreateUpdateAndGetUser() { @@ -75,6 +93,38 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers Assert.IsTrue(Math.Abs((now.ToUniversalTime() - result.LastSync).Milliseconds) < 100); } + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task Update_NoId() + { + var dal = new TwitterUserPostgresDal(_settings); + await dal.UpdateTwitterUserAsync(default, default, default, DateTime.UtcNow); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task Update_NoLastTweetPostedId() + { + var dal = new TwitterUserPostgresDal(_settings); + await dal.UpdateTwitterUserAsync(12, default, default, DateTime.UtcNow); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task Update_NoLastTweetSynchronizedForAllFollowersId() + { + var dal = new TwitterUserPostgresDal(_settings); + await dal.UpdateTwitterUserAsync(12, 9556, default, DateTime.UtcNow); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task Update_NoLastSync() + { + var dal = new TwitterUserPostgresDal(_settings); + await dal.UpdateTwitterUserAsync(12, 9556, 65, default); + } + [TestMethod] public async Task CreateAndDeleteUser() { @@ -93,7 +143,15 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers } [TestMethod] - public async Task GetAllTwitterUsers() + [ExpectedException(typeof(ArgumentException))] + public async Task DeleteUser_NotAcct() + { + var dal = new TwitterUserPostgresDal(_settings); + await dal.DeleteTwitterUserAsync(string.Empty); + } + + [TestMethod] + public async Task GetAllTwitterUsers_Top() { var dal = new TwitterUserPostgresDal(_settings); for (var i = 0; i < 1000; i++) @@ -147,6 +205,26 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers Assert.IsTrue(Math.Abs((acc.LastSync - oldest.ToUniversalTime()).TotalMilliseconds) < 1000); } + [TestMethod] + public async Task GetAllTwitterUsers() + { + var dal = new TwitterUserPostgresDal(_settings); + for (var i = 0; i < 1000; i++) + { + var acct = $"myid{i}"; + var lastTweetId = 1548L; + + await dal.CreateTwitterUserAsync(acct, lastTweetId); + } + + var result = await dal.GetAllTwitterUsersAsync(); + Assert.AreEqual(1000, result.Length); + Assert.IsFalse(result[0].Id == default); + Assert.IsFalse(result[0].Acct == default); + Assert.IsFalse(result[0].LastTweetPostedId == default); + Assert.IsFalse(result[0].LastTweetSynchronizedForAllFollowersId == default); + } + [TestMethod] public async Task CountTwitterUsers() { From 2f6eacc524034ce5cf9b575a053eb9778d27ef7e Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 12 Feb 2021 20:10:03 -0500 Subject: [PATCH 15/44] FollowersDal implementation + tests --- .../DataAccessLayers/FollowersPostgresDal.cs | 16 ++- .../FollowersPostgresDalTests.cs | 118 +++++++++++++++++- 2 files changed, 130 insertions(+), 4 deletions(-) diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs index 0a67cc7..ec031d4 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs @@ -86,7 +86,15 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers public async Task GetAllFollowersAsync() { - throw new NotImplementedException(); + var query = $"SELECT * FROM {_settings.FollowersTableName}"; + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + var result = await dbConnection.QueryAsync(query); + return result.Select(Convert).ToArray(); + } } public async Task UpdateFollowerAsync(Follower follower) @@ -121,8 +129,8 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers public async Task DeleteFollowerAsync(string acct, string host) { - if (acct == default) throw new ArgumentException("acct"); - if (host == default) throw new ArgumentException("host"); + if (string.IsNullOrWhiteSpace(acct)) throw new ArgumentException("acct"); + if (string.IsNullOrWhiteSpace(host)) throw new ArgumentException("host"); acct = acct.ToLowerInvariant(); host = host.ToLowerInvariant(); @@ -147,6 +155,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers Acct = follower.Acct, Host = follower.Host, InboxRoute = follower.InboxRoute, + ActorId = follower.ActorId, SharedInboxRoute = follower.SharedInboxRoute, Followings = follower.Followings.ToList(), FollowingsSyncStatus = JsonConvert.DeserializeObject>(follower.FollowingsSyncStatus) @@ -164,5 +173,6 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers public string Host { get; set; } public string InboxRoute { get; set; } public string SharedInboxRoute { get; set; } + public string ActorId { 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 cc413c0..cbaeb72 100644 --- a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs +++ b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Threading.Tasks; +using BirdsiteLive.DAL.Models; using BirdsiteLive.DAL.Postgres.DataAccessLayers; using BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers.Base; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -53,6 +53,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers Assert.AreEqual(host, result.Host); Assert.AreEqual(inboxRoute, result.InboxRoute); Assert.AreEqual(sharedInboxRoute, result.SharedInboxRoute); + Assert.AreEqual(actorId, result.ActorId); Assert.AreEqual(following.Length, result.Followings.Count); Assert.AreEqual(following[0], result.Followings[0]); Assert.AreEqual(followingSync.Count, result.FollowingsSyncStatus.Count); @@ -60,6 +61,38 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers Assert.AreEqual(followingSync.First().Value, result.FollowingsSyncStatus.First().Value); } + [TestMethod] + public async Task CreateAndGetFollower_NoFollowings() + { + var acct = "myhandle"; + var host = "domain.ext"; + 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, null, null); + + var result = await dal.GetFollowerAsync(acct, host); + + Assert.IsNotNull(result); + Assert.AreEqual(acct, result.Acct); + Assert.AreEqual(host, result.Host); + Assert.AreEqual(actorId, result.ActorId); + Assert.AreEqual(inboxRoute, result.InboxRoute); + Assert.AreEqual(sharedInboxRoute, result.SharedInboxRoute); + Assert.AreEqual(0, result.Followings.Count); + Assert.AreEqual(0, result.FollowingsSyncStatus.Count); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task GetFollowers_NoId() + { + var dal = new FollowersPostgresDal(_settings); + await dal.GetFollowersAsync(default); + } + [TestMethod] public async Task CreateAndGetFollower_NoSharedInbox() { @@ -85,6 +118,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers Assert.AreEqual(acct, result.Acct); Assert.AreEqual(host, result.Host); Assert.AreEqual(inboxRoute, result.InboxRoute); + Assert.AreEqual(actorId, result.ActorId); Assert.AreEqual(sharedInboxRoute, result.SharedInboxRoute); Assert.AreEqual(following.Length, result.Followings.Count); Assert.AreEqual(following[0], result.Followings[0]); @@ -136,6 +170,43 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers Assert.AreEqual(0, result.Length); } + [TestMethod] + public async Task GetAllFollowersAsync() + { + var dal = new FollowersPostgresDal(_settings); + + //User 1 + var acct = "myhandle1"; + var host = "domain.ext"; + var following = new[] { 1, 2, 3 }; + var followingSync = new Dictionary(); + var inboxRoute = "/myhandle1/inbox"; + var sharedInboxRoute = "/inbox"; + var actorId = $"https://{host}/{acct}"; + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); + + //User 2 + acct = "myhandle2"; + host = "domain.ext"; + following = new[] { 2, 4, 5 }; + inboxRoute = "/myhandle2/inbox"; + sharedInboxRoute = "/inbox2"; + actorId = $"https://{host}/{acct}"; + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); + + //User 2 + acct = "myhandle3"; + host = "domain.ext"; + following = new[] { 1 }; + inboxRoute = "/myhandle3/inbox"; + sharedInboxRoute = "/inbox3"; + actorId = $"https://{host}/{acct}"; + await dal.CreateFollowerAsync(acct, host, inboxRoute, sharedInboxRoute, actorId, following, followingSync); + + var result = await dal.GetAllFollowersAsync(); + Assert.AreEqual(3, result.Length); + } + [TestMethod] public async Task CountFollowersAsync() { @@ -256,6 +327,27 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers Assert.AreEqual(updatedFollowingSync.First().Value, result.FollowingsSyncStatus.First().Value); } + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task Update_NoFollower() + { + var dal = new FollowersPostgresDal(_settings); + await dal.UpdateFollowerAsync(null); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task Update_NoFollowerId() + { + var follower = new Follower + { + Id = default + }; + + var dal = new FollowersPostgresDal(_settings); + await dal.UpdateFollowerAsync(follower); + } + [TestMethod] public async Task CreateAndDeleteFollower_ById() { @@ -309,5 +401,29 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers result = await dal.GetFollowerAsync(acct, host); Assert.IsNull(result); } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task Delete_NoFollowerId() + { + var dal = new FollowersPostgresDal(_settings); + await dal.DeleteFollowerAsync(default); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task Delete_NoAcct() + { + var dal = new FollowersPostgresDal(_settings); + await dal.DeleteFollowerAsync(string.Empty, string.Empty); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task Delete_NoHost() + { + var dal = new FollowersPostgresDal(_settings); + await dal.DeleteFollowerAsync("acct", string.Empty); + } } } \ No newline at end of file From a2597b72a9852cd29d4e329ae4b3ffea81e384da Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Sat, 13 Feb 2021 20:59:01 -0500 Subject: [PATCH 16/44] added pipeline test --- .../StatusPublicationPipelineTests.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/Tests/BirdsiteLive.Pipeline.Tests/StatusPublicationPipelineTests.cs diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/StatusPublicationPipelineTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/StatusPublicationPipelineTests.cs new file mode 100644 index 0000000..2a47b95 --- /dev/null +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/StatusPublicationPipelineTests.cs @@ -0,0 +1,50 @@ +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.Pipeline.Contracts; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace BirdsiteLive.Pipeline.Tests +{ + [TestClass] + public class StatusPublicationPipelineTests + { + [TestMethod] + public async Task ExecuteAsync_Test() + { + #region Stubs + var ct = new CancellationTokenSource(10); + #endregion + + #region Mocks + var retrieveTwitterUsersProcessor = new Mock(MockBehavior.Strict); + retrieveTwitterUsersProcessor + .Setup(x => x.GetTwitterUsersAsync( + It.IsAny>(), + It.IsAny())) + .Returns(Task.Delay(0)); + + var retrieveTweetsProcessor = new Mock(MockBehavior.Strict); + var retrieveFollowersProcessor = new Mock(MockBehavior.Strict); + var sendTweetsToFollowersProcessor = new Mock(MockBehavior.Strict); + var saveProgressionProcessor = new Mock(MockBehavior.Strict); + var logger = new Mock>(); + #endregion + + var pipeline = new StatusPublicationPipeline(retrieveTweetsProcessor.Object, retrieveTwitterUsersProcessor.Object, retrieveFollowersProcessor.Object, sendTweetsToFollowersProcessor.Object, saveProgressionProcessor.Object, logger.Object); + await pipeline.ExecuteAsync(ct.Token); + + #region Validations + retrieveTwitterUsersProcessor.VerifyAll(); + retrieveTweetsProcessor.VerifyAll(); + retrieveFollowersProcessor.VerifyAll(); + sendTweetsToFollowersProcessor.VerifyAll(); + saveProgressionProcessor.VerifyAll(); + logger.VerifyAll(); + #endregion + } + } +} \ No newline at end of file From 537270cceb563f11910806395b05a7b9a12a87d1 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Sun, 14 Feb 2021 01:29:06 -0500 Subject: [PATCH 17/44] implement Delete TwitterUser by Id + tests --- .../TwitterUserPostgresDal.cs | 16 ++++++++++- .../Contracts/ITwitterUserDal.cs | 1 + .../TwitterUserPostgresDalTests.cs | 27 ++++++++++++++++++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs index 714eb8f..855df5e 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs @@ -59,7 +59,7 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers return result; } } - + public async Task GetTwitterUsersCountAsync() { var query = $"SELECT COUNT(*) FROM {_settings.TwitterUserTableName}"; @@ -131,5 +131,19 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers await dbConnection.QueryAsync(query, new { acct }); } } + + public async Task DeleteTwitterUserAsync(int id) + { + if (id == default) throw new ArgumentException("id"); + + var query = $"DELETE FROM {_settings.TwitterUserTableName} WHERE id = @id"; + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + await dbConnection.QueryAsync(query, new { id }); + } + } } } \ No newline at end of file diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs index 4aa9eb6..cfa422a 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs @@ -13,6 +13,7 @@ namespace BirdsiteLive.DAL.Contracts Task GetAllTwitterUsersAsync(); Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId, DateTime lastSync); Task DeleteTwitterUserAsync(string acct); + Task DeleteTwitterUserAsync(int id); Task GetTwitterUsersCountAsync(); } } \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/TwitterUserPostgresDalTests.cs b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/TwitterUserPostgresDalTests.cs index b290735..0cf3ca1 100644 --- a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/TwitterUserPostgresDalTests.cs +++ b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/TwitterUserPostgresDalTests.cs @@ -128,7 +128,7 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers [TestMethod] public async Task CreateAndDeleteUser() { - var acct = "myid"; + var acct = "myacct"; var lastTweetId = 1548L; var dal = new TwitterUserPostgresDal(_settings); @@ -150,6 +150,31 @@ namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers await dal.DeleteTwitterUserAsync(string.Empty); } + [TestMethod] + public async Task CreateAndDeleteUser_byId() + { + var acct = "myacct"; + var lastTweetId = 1548L; + + var dal = new TwitterUserPostgresDal(_settings); + + await dal.CreateTwitterUserAsync(acct, lastTweetId); + var result = await dal.GetTwitterUserAsync(acct); + Assert.IsNotNull(result); + + await dal.DeleteTwitterUserAsync(result.Id); + result = await dal.GetTwitterUserAsync(acct); + Assert.IsNull(result); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public async Task DeleteUser_NotAcct_byId() + { + var dal = new TwitterUserPostgresDal(_settings); + await dal.DeleteTwitterUserAsync(default(int)); + } + [TestMethod] public async Task GetAllTwitterUsers_Top() { From af092d942d7801b58380a6dc80995c6956a5c360 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Sun, 14 Feb 2021 01:51:54 -0500 Subject: [PATCH 18/44] refactoring --- .../Actions/RejectAllFollowingsAction.cs | 52 +++++++++++++++++++ .../Actions/RejectFollowingAction.cs | 44 ++++++++++++++++ .../Actions/RemoveFollowerAction.cs | 30 ++--------- .../Actions/RemoveTwitterAccountAction.cs | 25 ++------- 4 files changed, 104 insertions(+), 47 deletions(-) create mode 100644 src/BirdsiteLive.Moderation/Actions/RejectAllFollowingsAction.cs create mode 100644 src/BirdsiteLive.Moderation/Actions/RejectFollowingAction.cs diff --git a/src/BirdsiteLive.Moderation/Actions/RejectAllFollowingsAction.cs b/src/BirdsiteLive.Moderation/Actions/RejectAllFollowingsAction.cs new file mode 100644 index 0000000..9a1b1bc --- /dev/null +++ b/src/BirdsiteLive.Moderation/Actions/RejectAllFollowingsAction.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading.Tasks; +using BirdsiteLive.ActivityPub; +using BirdsiteLive.ActivityPub.Converters; +using BirdsiteLive.Common.Settings; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.Domain; + +namespace BirdsiteLive.Moderation.Actions +{ + public interface IRejectAllFollowingsAction + { + Task ProcessAsync(Follower follower); + } + + public class RejectAllFollowingsAction : IRejectAllFollowingsAction + { + private readonly ITwitterUserDal _twitterUserDal; + private readonly IUserService _userService; + private readonly InstanceSettings _instanceSettings; + + #region Ctor + public RejectAllFollowingsAction(ITwitterUserDal twitterUserDal, IUserService userService, InstanceSettings instanceSettings) + { + _twitterUserDal = twitterUserDal; + _userService = userService; + _instanceSettings = instanceSettings; + } + #endregion + + public async Task ProcessAsync(Follower follower) + { + foreach (var following in follower.Followings) + { + try + { + var f = await _twitterUserDal.GetTwitterUserAsync(following); + var activityFollowing = new ActivityFollow + { + type = "Follow", + actor = follower.ActorId, + apObject = UrlFactory.GetActorUrl(_instanceSettings.Domain, f.Acct) + }; + + await _userService.SendRejectFollowAsync(activityFollowing, follower.Host); + } + catch (Exception) { } + } + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Moderation/Actions/RejectFollowingAction.cs b/src/BirdsiteLive.Moderation/Actions/RejectFollowingAction.cs new file mode 100644 index 0000000..c5963eb --- /dev/null +++ b/src/BirdsiteLive.Moderation/Actions/RejectFollowingAction.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading.Tasks; +using BirdsiteLive.ActivityPub; +using BirdsiteLive.ActivityPub.Converters; +using BirdsiteLive.Common.Settings; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.Domain; + +namespace BirdsiteLive.Moderation.Actions +{ + public interface IRejectFollowingAction + { + Task ProcessAsync(Follower follower, SyncTwitterUser twitterUser); + } + + public class RejectFollowingAction : IRejectFollowingAction + { + private readonly IUserService _userService; + private readonly InstanceSettings _instanceSettings; + + #region Ctor + public RejectFollowingAction(IUserService userService, InstanceSettings instanceSettings) + { + _userService = userService; + _instanceSettings = instanceSettings; + } + #endregion + + public async Task ProcessAsync(Follower follower, SyncTwitterUser twitterUser) + { + try + { + var activityFollowing = new ActivityFollow + { + type = "Follow", + actor = follower.ActorId, + apObject = UrlFactory.GetActorUrl(_instanceSettings.Domain, twitterUser.Acct) + }; + await _userService.SendRejectFollowAsync(activityFollowing, follower.Host); + } + catch (Exception) { } + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs b/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs index cf1d454..8ab3132 100644 --- a/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs +++ b/src/BirdsiteLive.Moderation/Actions/RemoveFollowerAction.cs @@ -19,23 +19,21 @@ namespace BirdsiteLive.Moderation.Actions { private readonly IFollowersDal _followersDal; private readonly ITwitterUserDal _twitterUserDal; - private readonly IUserService _userService; - private readonly InstanceSettings _instanceSettings; + private readonly IRejectAllFollowingsAction _rejectAllFollowingsAction; #region Ctor - public RemoveFollowerAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal, IUserService userService, InstanceSettings instanceSettings) + public RemoveFollowerAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal, IRejectAllFollowingsAction rejectAllFollowingsAction) { _followersDal = followersDal; _twitterUserDal = twitterUserDal; - _userService = userService; - _instanceSettings = instanceSettings; + _rejectAllFollowingsAction = rejectAllFollowingsAction; } #endregion public async Task ProcessAsync(Follower follower) { // Perform undo following to user instance - await RejectAllFollowingsAsync(follower); + await _rejectAllFollowingsAction.ProcessAsync(follower); // Remove twitter users if no more followers var followings = follower.Followings; @@ -49,25 +47,5 @@ namespace BirdsiteLive.Moderation.Actions // Remove follower from DB await _followersDal.DeleteFollowerAsync(follower.Id); } - - private async Task RejectAllFollowingsAsync(Follower follower) - { - foreach (var following in follower.Followings) - { - try - { - var f = await _twitterUserDal.GetTwitterUserAsync(following); - var activityFollowing = new ActivityFollow - { - type = "Follow", - actor = follower.ActorId, - apObject = UrlFactory.GetActorUrl(_instanceSettings.Domain, f.Acct) - }; - - await _userService.SendRejectFollowAsync(activityFollowing, follower.Host); - } - catch (Exception) { } - } - } } } \ No newline at end of file diff --git a/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs b/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs index a689401..1babf23 100644 --- a/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs +++ b/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs @@ -19,16 +19,14 @@ namespace BirdsiteLive.Moderation.Actions { private readonly IFollowersDal _followersDal; private readonly ITwitterUserDal _twitterUserDal; - private readonly IUserService _userService; - private readonly InstanceSettings _instanceSettings; + private readonly IRejectFollowingAction _rejectFollowingAction; #region Ctor - public RemoveTwitterAccountAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal, InstanceSettings instanceSettings, IUserService userService) + public RemoveTwitterAccountAction(IFollowersDal followersDal, ITwitterUserDal twitterUserDal, IRejectFollowingAction rejectFollowingAction) { _followersDal = followersDal; _twitterUserDal = twitterUserDal; - _instanceSettings = instanceSettings; - _userService = userService; + _rejectFollowingAction = rejectFollowingAction; } #endregion @@ -42,7 +40,7 @@ namespace BirdsiteLive.Moderation.Actions foreach (var follower in followers) { // Perform undo following to user instance - await RejectFollowingAsync(follower, twitterUser); + await _rejectFollowingAction.ProcessAsync(follower, twitterUser); // Remove following from DB if (follower.Followings.Contains(twitterUserId)) @@ -60,20 +58,5 @@ namespace BirdsiteLive.Moderation.Actions // Remove twitter user await _twitterUserDal.DeleteTwitterUserAsync(twitterUser.Acct); } - - private async Task RejectFollowingAsync(Follower follower, SyncTwitterUser twitterUser) - { - try - { - var activityFollowing = new ActivityFollow - { - type = "Follow", - actor = follower.ActorId, - apObject = UrlFactory.GetActorUrl(_instanceSettings.Domain, twitterUser.Acct) - }; - await _userService.SendRejectFollowAsync(activityFollowing, follower.Host); - } - catch (Exception) { } - } } } \ No newline at end of file From 5d2d5ffd52d39efa795669ba466e7618440088be Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Sun, 14 Feb 2021 02:42:23 -0500 Subject: [PATCH 19/44] added ModerationPipeline tests --- src/BirdsiteLive.sln | 9 +- .../BirdsiteLive.Moderation.Tests.csproj | 21 ++++ .../ModerationPipelineTests.cs | 106 ++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/Tests/BirdsiteLive.Moderation.Tests/BirdsiteLive.Moderation.Tests.csproj create mode 100644 src/Tests/BirdsiteLive.Moderation.Tests/ModerationPipelineTests.cs diff --git a/src/BirdsiteLive.sln b/src/BirdsiteLive.sln index d345e68..4868b12 100644 --- a/src/BirdsiteLive.sln +++ b/src/BirdsiteLive.sln @@ -41,7 +41,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Pipeline.Tests EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.DAL.Tests", "Tests\BirdsiteLive.DAL.Tests\BirdsiteLive.DAL.Tests.csproj", "{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.Moderation", "BirdsiteLive.Moderation\BirdsiteLive.Moderation.csproj", "{4BE541AC-8A93-4FA3-98AC-956CC2D5B748}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Moderation", "BirdsiteLive.Moderation\BirdsiteLive.Moderation.csproj", "{4BE541AC-8A93-4FA3-98AC-956CC2D5B748}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.Moderation.Tests", "Tests\BirdsiteLive.Moderation.Tests\BirdsiteLive.Moderation.Tests.csproj", "{0A311BF3-4FD9-4303-940A-A3778890561C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -113,6 +115,10 @@ Global {4BE541AC-8A93-4FA3-98AC-956CC2D5B748}.Debug|Any CPU.Build.0 = Debug|Any CPU {4BE541AC-8A93-4FA3-98AC-956CC2D5B748}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BE541AC-8A93-4FA3-98AC-956CC2D5B748}.Release|Any CPU.Build.0 = Release|Any CPU + {0A311BF3-4FD9-4303-940A-A3778890561C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A311BF3-4FD9-4303-940A-A3778890561C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A311BF3-4FD9-4303-940A-A3778890561C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A311BF3-4FD9-4303-940A-A3778890561C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -133,6 +139,7 @@ Global {BF51CA81-5A7A-46F8-B4FB-861C6BE59298} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} {5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} {4BE541AC-8A93-4FA3-98AC-956CC2D5B748} = {DA3C160C-4811-4E26-A5AD-42B81FAF2D7C} + {0A311BF3-4FD9-4303-940A-A3778890561C} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {69E8DCAD-4C37-4010-858F-5F94E6FBABCE} diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/BirdsiteLive.Moderation.Tests.csproj b/src/Tests/BirdsiteLive.Moderation.Tests/BirdsiteLive.Moderation.Tests.csproj new file mode 100644 index 0000000..e85b592 --- /dev/null +++ b/src/Tests/BirdsiteLive.Moderation.Tests/BirdsiteLive.Moderation.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + + diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/ModerationPipelineTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/ModerationPipelineTests.cs new file mode 100644 index 0000000..7928b34 --- /dev/null +++ b/src/Tests/BirdsiteLive.Moderation.Tests/ModerationPipelineTests.cs @@ -0,0 +1,106 @@ +using System; +using System.Threading.Tasks; +using BirdsiteLive.Domain.Repository; +using BirdsiteLive.Moderation.Processors; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace BirdsiteLive.Moderation.Tests +{ + [TestClass] + public class ModerationPipelineTests + { + [TestMethod] + public async Task ApplyModerationSettingsAsync_None() + { + #region Mocks + var moderationRepositoryMock = new Mock(MockBehavior.Strict); + moderationRepositoryMock + .Setup(x => x.GetModerationType(ModerationEntityTypeEnum.Follower)) + .Returns(ModerationTypeEnum.None); + moderationRepositoryMock + .Setup(x => x.GetModerationType(ModerationEntityTypeEnum.TwitterAccount)) + .Returns(ModerationTypeEnum.None); + + var followerModerationProcessorMock = new Mock(MockBehavior.Strict); + var twitterAccountModerationProcessorMock = new Mock(MockBehavior.Strict); + var loggerMock = new Mock>(MockBehavior.Strict); + #endregion + + var pipeline = new ModerationPipeline(moderationRepositoryMock.Object, followerModerationProcessorMock.Object, twitterAccountModerationProcessorMock.Object, loggerMock.Object); + await pipeline.ApplyModerationSettingsAsync(); + + #region Validations + moderationRepositoryMock.VerifyAll(); + followerModerationProcessorMock.VerifyAll(); + twitterAccountModerationProcessorMock.VerifyAll(); + loggerMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ApplyModerationSettingsAsync_Process() + { + #region Mocks + var moderationRepositoryMock = new Mock(MockBehavior.Strict); + moderationRepositoryMock + .Setup(x => x.GetModerationType(ModerationEntityTypeEnum.Follower)) + .Returns(ModerationTypeEnum.WhiteListing); + moderationRepositoryMock + .Setup(x => x.GetModerationType(ModerationEntityTypeEnum.TwitterAccount)) + .Returns(ModerationTypeEnum.BlackListing); + + var followerModerationProcessorMock = new Mock(MockBehavior.Strict); + followerModerationProcessorMock + .Setup(x => x.ProcessAsync( + It.Is(y => y == ModerationTypeEnum.WhiteListing))) + .Returns(Task.CompletedTask); + + var twitterAccountModerationProcessorMock = new Mock(MockBehavior.Strict); + twitterAccountModerationProcessorMock + .Setup(x => x.ProcessAsync( + It.Is(y => y == ModerationTypeEnum.BlackListing))) + .Returns(Task.CompletedTask); + + var loggerMock = new Mock>(MockBehavior.Strict); + #endregion + + var pipeline = new ModerationPipeline(moderationRepositoryMock.Object, followerModerationProcessorMock.Object, twitterAccountModerationProcessorMock.Object, loggerMock.Object); + await pipeline.ApplyModerationSettingsAsync(); + + #region Validations + moderationRepositoryMock.VerifyAll(); + followerModerationProcessorMock.VerifyAll(); + twitterAccountModerationProcessorMock.VerifyAll(); + loggerMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ApplyModerationSettingsAsync_Exception() + { + #region Mocks + var moderationRepositoryMock = new Mock(MockBehavior.Strict); + moderationRepositoryMock + .Setup(x => x.GetModerationType(ModerationEntityTypeEnum.Follower)) + .Throws(new Exception()); + + var followerModerationProcessorMock = new Mock(MockBehavior.Strict); + var twitterAccountModerationProcessorMock = new Mock(MockBehavior.Strict); + + var loggerMock = new Mock>(); + #endregion + + var pipeline = new ModerationPipeline(moderationRepositoryMock.Object, followerModerationProcessorMock.Object, twitterAccountModerationProcessorMock.Object, loggerMock.Object); + await pipeline.ApplyModerationSettingsAsync(); + + #region Validations + moderationRepositoryMock.VerifyAll(); + followerModerationProcessorMock.VerifyAll(); + twitterAccountModerationProcessorMock.VerifyAll(); + loggerMock.VerifyAll(); + #endregion + } + } +} From e39c22696537063d315bceabb5161865629f6b59 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Sun, 14 Feb 2021 12:19:31 -0500 Subject: [PATCH 20/44] changed test verbosity --- .github/workflows/dotnet-core.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index b1fc70d..d28cb08 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -24,5 +24,5 @@ jobs: run: dotnet build --configuration Release --no-restore working-directory: ${{env.working-directory}} - name: Test - run: dotnet test --no-restore --verbosity quiet + run: dotnet test --no-restore --verbosity minimal working-directory: ${{env.working-directory}} From 9b25a86d1d529f7d0400c6e8ecc131b0818da172 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Sun, 14 Feb 2021 12:28:38 -0500 Subject: [PATCH 21/44] clean up warnings --- src/BirdsiteLive.ActivityPub/ApDeserializer.cs | 1 - src/BirdsiteLive.Domain/StatusService.cs | 5 +---- .../Controllers/DebugingController.cs | 5 ++--- .../Controllers/UsersController.cs | 4 +--- .../MagicKeyTests.cs | 6 +++--- .../RetrieveTwitterUsersProcessorTests.cs | 18 +++++++++--------- 6 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/BirdsiteLive.ActivityPub/ApDeserializer.cs b/src/BirdsiteLive.ActivityPub/ApDeserializer.cs index e577fbb..17dadbe 100644 --- a/src/BirdsiteLive.ActivityPub/ApDeserializer.cs +++ b/src/BirdsiteLive.ActivityPub/ApDeserializer.cs @@ -41,7 +41,6 @@ namespace BirdsiteLive.ActivityPub } }; return acceptFollow; - break; } break; } diff --git a/src/BirdsiteLive.Domain/StatusService.cs b/src/BirdsiteLive.Domain/StatusService.cs index 4983de1..e25d52b 100644 --- a/src/BirdsiteLive.Domain/StatusService.cs +++ b/src/BirdsiteLive.Domain/StatusService.cs @@ -41,7 +41,6 @@ namespace BirdsiteLive.Domain var noteUrl = UrlFactory.GetNoteUrl(_instanceSettings.Domain, username, tweet.Id.ToString()); var to = $"{actorUrl}/followers"; - var apPublic = "https://www.w3.org/ns/activitystreams#Public"; var extractedTags = _statusExtractor.Extract(tweet.MessageContent); _statisticsHandler.ExtractedStatus(extractedTags.tags.Count(x => x.type == "Mention")); @@ -70,11 +69,9 @@ namespace BirdsiteLive.Domain attributedTo = actorUrl, inReplyTo = inReplyTo, - //to = new [] {to}, - //cc = new [] { apPublic }, to = new[] { to }, - //cc = new[] { apPublic }, + //cc = new[] { "https://www.w3.org/ns/activitystreams#Public" }, cc = new string[0], sensitive = false, diff --git a/src/BirdsiteLive/Controllers/DebugingController.cs b/src/BirdsiteLive/Controllers/DebugingController.cs index 977687c..ff8f0b4 100644 --- a/src/BirdsiteLive/Controllers/DebugingController.cs +++ b/src/BirdsiteLive/Controllers/DebugingController.cs @@ -69,7 +69,6 @@ namespace BirdsiteLive.Controllers var noteUrl = $"https://{_instanceSettings.Domain}/@{username}/{noteGuid}"; var to = $"{actor}/followers"; - var apPublic = "https://www.w3.org/ns/activitystreams#Public"; var now = DateTime.UtcNow; var nowString = now.ToString("s") + "Z"; @@ -82,7 +81,7 @@ namespace BirdsiteLive.Controllers actor = actor, published = nowString, to = new []{ to }, - //cc = new [] { apPublic }, + //cc = new [] { "https://www.w3.org/ns/activitystreams#Public" }, apObject = new Note() { id = noteId, @@ -92,7 +91,7 @@ namespace BirdsiteLive.Controllers url = noteUrl, attributedTo = actor, to = new[] { to }, - //cc = new [] { apPublic }, + //cc = new [] { "https://www.w3.org/ns/activitystreams#Public" }, sensitive = false, content = "

Woooot

", attachment = new Attachment[0], diff --git a/src/BirdsiteLive/Controllers/UsersController.cs b/src/BirdsiteLive/Controllers/UsersController.cs index 0fbefcd..a06b1e9 100644 --- a/src/BirdsiteLive/Controllers/UsersController.cs +++ b/src/BirdsiteLive/Controllers/UsersController.cs @@ -159,13 +159,11 @@ namespace BirdsiteLive.Controllers return Accepted(); } } - - return Accepted(); } [Route("/users/{id}/followers")] [HttpGet] - public async Task Followers(string id) + public IActionResult Followers(string id) { var r = Request.Headers["Accept"].First(); if (!r.Contains("application/activity+json")) return NotFound(); diff --git a/src/Tests/BirdsiteLive.Cryptography.Tests/MagicKeyTests.cs b/src/Tests/BirdsiteLive.Cryptography.Tests/MagicKeyTests.cs index d77b67a..413aa53 100644 --- a/src/Tests/BirdsiteLive.Cryptography.Tests/MagicKeyTests.cs +++ b/src/Tests/BirdsiteLive.Cryptography.Tests/MagicKeyTests.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; +using NuGet.Frameworks; namespace BirdsiteLive.Cryptography.Tests { @@ -7,13 +8,12 @@ namespace BirdsiteLive.Cryptography.Tests public class MagicKeyTests { [TestMethod] - public async Task Test() + public void Test() { var g = MagicKey.Generate(); - var magicKey = new MagicKey(g.PrivateKey); - + Assert.IsNotNull(magicKey); } } } \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs index 5600e1c..a504549 100644 --- a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs @@ -48,9 +48,9 @@ namespace BirdsiteLive.Pipeline.Tests.Processors var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object); processor.WaitFactor = 10; - processor.GetTwitterUsersAsync(buffer, CancellationToken.None); + var t = processor.GetTwitterUsersAsync(buffer, CancellationToken.None); - await Task.Delay(50); + await Task.WhenAny(t, Task.Delay(50)); #region Validations maxUsersNumberProviderMock.VerifyAll(); @@ -95,10 +95,10 @@ namespace BirdsiteLive.Pipeline.Tests.Processors var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object); processor.WaitFactor = 2; - processor.GetTwitterUsersAsync(buffer, CancellationToken.None); - - await Task.Delay(300); + var t = processor.GetTwitterUsersAsync(buffer, CancellationToken.None); + await Task.WhenAny(t, Task.Delay(300)); + #region Validations maxUsersNumberProviderMock.VerifyAll(); twitterUserDalMock.VerifyAll(); @@ -142,9 +142,9 @@ namespace BirdsiteLive.Pipeline.Tests.Processors var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object); processor.WaitFactor = 2; - processor.GetTwitterUsersAsync(buffer, CancellationToken.None); + var t = processor.GetTwitterUsersAsync(buffer, CancellationToken.None); - await Task.Delay(200); + await Task.WhenAny(t, Task.Delay(200)); #region Validations maxUsersNumberProviderMock.VerifyAll(); @@ -181,9 +181,9 @@ namespace BirdsiteLive.Pipeline.Tests.Processors var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object); processor.WaitFactor = 1; - processor.GetTwitterUsersAsync(buffer, CancellationToken.None); + var t =processor.GetTwitterUsersAsync(buffer, CancellationToken.None); - await Task.Delay(50); + await Task.WhenAny(t, Task.Delay(50)); #region Validations maxUsersNumberProviderMock.VerifyAll(); From 5781af6b3b10a7e722786d026f1a8e8c2293f8ce Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Sun, 14 Feb 2021 12:34:57 -0500 Subject: [PATCH 22/44] fix test --- .../Processors/RetrieveTwitterUsersProcessorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs index a504549..62ff93d 100644 --- a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs @@ -144,7 +144,7 @@ namespace BirdsiteLive.Pipeline.Tests.Processors processor.WaitFactor = 2; var t = processor.GetTwitterUsersAsync(buffer, CancellationToken.None); - await Task.WhenAny(t, Task.Delay(200)); + await Task.WhenAny(t, Task.Delay(400)); #region Validations maxUsersNumberProviderMock.VerifyAll(); From be9bf5ebc5a14f8258dca594ac110395e9799138 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Sun, 14 Feb 2021 20:09:01 -0500 Subject: [PATCH 23/44] fix tests --- .../Processors/RetrieveTwitterUsersProcessorTests.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs index 62ff93d..e38ca78 100644 --- a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs @@ -143,8 +143,13 @@ namespace BirdsiteLive.Pipeline.Tests.Processors var processor = new RetrieveTwitterUsersProcessor(twitterUserDalMock.Object, maxUsersNumberProviderMock.Object, loggerMock.Object); processor.WaitFactor = 2; var t = processor.GetTwitterUsersAsync(buffer, CancellationToken.None); + var t2 = Task.Run(async () => + { + while (buffer.Count < 11) + await Task.Delay(50); + }); - await Task.WhenAny(t, Task.Delay(400)); + await Task.WhenAny(t, t2, Task.Delay(2000)); #region Validations maxUsersNumberProviderMock.VerifyAll(); From 0871243a18f2ffb0045a74f1a1cb4b8fc0dea5a5 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Sun, 14 Feb 2021 20:23:28 -0500 Subject: [PATCH 24/44] fix test delay --- .../Processors/RetrieveTwitterUsersProcessorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs index e38ca78..4d0e465 100644 --- a/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs +++ b/src/Tests/BirdsiteLive.Pipeline.Tests/Processors/RetrieveTwitterUsersProcessorTests.cs @@ -149,7 +149,7 @@ namespace BirdsiteLive.Pipeline.Tests.Processors await Task.Delay(50); }); - await Task.WhenAny(t, t2, Task.Delay(2000)); + await Task.WhenAny(t, t2, Task.Delay(5000)); #region Validations maxUsersNumberProviderMock.VerifyAll(); From aea0244b2aedf2ec30cc6e070203c82f74ee7e8d Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Mon, 15 Feb 2021 18:27:24 -0500 Subject: [PATCH 25/44] tests creation --- .../Actions/RejectAllFollowingsActionTests.cs | 114 ++++++++++++++++++ .../Actions/RejectFollowingActionTests.cs | 7 ++ .../Actions/RemoveFollowerActionTests.cs | 7 ++ .../RemoveTwitterAccountActionTests.cs | 7 ++ .../FollowerModerationProcessorTests.cs | 7 ++ .../TwitterAccountModerationProcessorTests.cs | 7 ++ 6 files changed, 149 insertions(+) create mode 100644 src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectAllFollowingsActionTests.cs create mode 100644 src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectFollowingActionTests.cs create mode 100644 src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs create mode 100644 src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveTwitterAccountActionTests.cs create mode 100644 src/Tests/BirdsiteLive.Moderation.Tests/Processors/FollowerModerationProcessorTests.cs create mode 100644 src/Tests/BirdsiteLive.Moderation.Tests/Processors/TwitterAccountModerationProcessorTests.cs diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectAllFollowingsActionTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectAllFollowingsActionTests.cs new file mode 100644 index 0000000..3fda47b --- /dev/null +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectAllFollowingsActionTests.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using BirdsiteLive.ActivityPub; +using BirdsiteLive.Common.Settings; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.Domain; +using BirdsiteLive.Moderation.Actions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace BirdsiteLive.Moderation.Tests.Actions +{ + [TestClass] + public class RejectAllFollowingsActionTests + { + [TestMethod] + public async Task ProcessAsync() + { + #region Stubs + var follower = new Follower + { + Followings = new List + { + 24 + }, + Host = "host" + }; + + var settings = new InstanceSettings + { + Domain = "domain" + }; + #endregion + + #region Mocks + var twitterUserDalMock = new Mock(); + twitterUserDalMock + .Setup(x => x.GetTwitterUserAsync( + It.Is(y => y == 24))) + .ReturnsAsync(new SyncTwitterUser + { + Id = 24, + Acct = "acct" + }); + + var userServiceMock = new Mock(); + userServiceMock + .Setup(x => x.SendRejectFollowAsync( + It.Is(y => y.type == "Follow"), + It.IsNotNull() + )) + .ReturnsAsync(true); + #endregion + + var action = new RejectAllFollowingsAction(twitterUserDalMock.Object, userServiceMock.Object, settings); + await action.ProcessAsync(follower); + + #region Validations + twitterUserDalMock.VerifyAll(); + userServiceMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ProcessAsync_Exception() + { + #region Stubs + var follower = new Follower + { + Followings = new List + { + 24 + }, + Host = "host" + }; + + var settings = new InstanceSettings + { + Domain = "domain" + }; + #endregion + + #region Mocks + var twitterUserDalMock = new Mock(); + twitterUserDalMock + .Setup(x => x.GetTwitterUserAsync( + It.Is(y => y == 24))) + .ReturnsAsync(new SyncTwitterUser + { + Id = 24, + Acct = "acct" + }); + + var userServiceMock = new Mock(); + userServiceMock + .Setup(x => x.SendRejectFollowAsync( + It.Is(y => y.type == "Follow"), + It.IsNotNull() + )) + .Throws(new Exception()); + #endregion + + var action = new RejectAllFollowingsAction(twitterUserDalMock.Object, userServiceMock.Object, settings); + await action.ProcessAsync(follower); + + #region Validations + twitterUserDalMock.VerifyAll(); + userServiceMock.VerifyAll(); + #endregion + } + } +} \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectFollowingActionTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectFollowingActionTests.cs new file mode 100644 index 0000000..870f1bd --- /dev/null +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectFollowingActionTests.cs @@ -0,0 +1,7 @@ +namespace BirdsiteLive.Moderation.Tests.Actions +{ + public class RejectFollowingActionTests + { + + } +} \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs new file mode 100644 index 0000000..a2f8751 --- /dev/null +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs @@ -0,0 +1,7 @@ +namespace BirdsiteLive.Moderation.Tests.Actions +{ + public class RemoveFollowerActionTests + { + + } +} \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveTwitterAccountActionTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveTwitterAccountActionTests.cs new file mode 100644 index 0000000..a0451c7 --- /dev/null +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveTwitterAccountActionTests.cs @@ -0,0 +1,7 @@ +namespace BirdsiteLive.Moderation.Tests.Actions +{ + public class RemoveTwitterAccountActionTests + { + + } +} \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Processors/FollowerModerationProcessorTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Processors/FollowerModerationProcessorTests.cs new file mode 100644 index 0000000..3acaa4f --- /dev/null +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Processors/FollowerModerationProcessorTests.cs @@ -0,0 +1,7 @@ +namespace BirdsiteLive.Moderation.Tests.Processors +{ + public class FollowerModerationProcessorTests + { + + } +} \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Processors/TwitterAccountModerationProcessorTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Processors/TwitterAccountModerationProcessorTests.cs new file mode 100644 index 0000000..8cf2020 --- /dev/null +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Processors/TwitterAccountModerationProcessorTests.cs @@ -0,0 +1,7 @@ +namespace BirdsiteLive.Moderation.Tests.Processors +{ + public class TwitterAccountModerationProcessorTests + { + + } +} \ No newline at end of file From abce8e5ab542f73e51b2b63b5ed322a02910e2ad Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Mon, 15 Feb 2021 23:31:22 -0500 Subject: [PATCH 26/44] added actions tests --- .../Actions/RemoveTwitterAccountAction.cs | 9 +- .../Actions/RejectFollowingActionTests.cs | 100 +++++++++++++- .../Actions/RemoveFollowerActionTests.cs | 108 ++++++++++++++- .../RemoveTwitterAccountActionTests.cs | 128 +++++++++++++++++- 4 files changed, 332 insertions(+), 13 deletions(-) diff --git a/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs b/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs index 1babf23..714cca6 100644 --- a/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs +++ b/src/BirdsiteLive.Moderation/Actions/RemoveTwitterAccountAction.cs @@ -1,12 +1,7 @@ -using System; -using System.Linq; +using System.Linq; using System.Threading.Tasks; -using BirdsiteLive.ActivityPub; -using BirdsiteLive.ActivityPub.Converters; -using BirdsiteLive.Common.Settings; using BirdsiteLive.DAL.Contracts; using BirdsiteLive.DAL.Models; -using BirdsiteLive.Domain; namespace BirdsiteLive.Moderation.Actions { @@ -56,7 +51,7 @@ namespace BirdsiteLive.Moderation.Actions } // Remove twitter user - await _twitterUserDal.DeleteTwitterUserAsync(twitterUser.Acct); + await _twitterUserDal.DeleteTwitterUserAsync(twitterUserId); } } } \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectFollowingActionTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectFollowingActionTests.cs index 870f1bd..7d7b1bf 100644 --- a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectFollowingActionTests.cs +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectFollowingActionTests.cs @@ -1,7 +1,103 @@ -namespace BirdsiteLive.Moderation.Tests.Actions +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using BirdsiteLive.ActivityPub; +using BirdsiteLive.Common.Settings; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.Domain; +using BirdsiteLive.Moderation.Actions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace BirdsiteLive.Moderation.Tests.Actions { + [TestClass] public class RejectFollowingActionTests { - + [TestMethod] + public async Task ProcessAsync() + { + #region Stubs + var follower = new Follower + { + Followings = new List + { + 24 + }, + Host = "host" + }; + + var settings = new InstanceSettings + { + Domain = "domain" + }; + + var twitterUser = new SyncTwitterUser + { + Id = 24, + Acct = "acct" + }; + #endregion + + #region Mocks + var userServiceMock = new Mock(); + userServiceMock + .Setup(x => x.SendRejectFollowAsync( + It.Is(y => y.type == "Follow"), + It.IsNotNull() + )) + .ReturnsAsync(true); + #endregion + + var action = new RejectFollowingAction(userServiceMock.Object, settings); + await action.ProcessAsync(follower, twitterUser); + + #region Validations + userServiceMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ProcessAsync_Exception() + { + #region Stubs + var follower = new Follower + { + Followings = new List + { + 24 + }, + Host = "host" + }; + + var settings = new InstanceSettings + { + Domain = "domain" + }; + + var twitterUser = new SyncTwitterUser + { + Id = 24, + Acct = "acct" + }; + #endregion + + #region Mocks + var userServiceMock = new Mock(); + userServiceMock + .Setup(x => x.SendRejectFollowAsync( + It.Is(y => y.type == "Follow"), + It.IsNotNull() + )) + .Throws(new Exception()); + #endregion + + var action = new RejectFollowingAction(userServiceMock.Object, settings); + await action.ProcessAsync(follower, twitterUser); + + #region Validations + userServiceMock.VerifyAll(); + #endregion + } } } \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs index a2f8751..4def0ed 100644 --- a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs @@ -1,7 +1,111 @@ -namespace BirdsiteLive.Moderation.Tests.Actions +using System.Collections.Generic; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.Moderation.Actions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace BirdsiteLive.Moderation.Tests.Actions { + [TestClass] public class RemoveFollowerActionTests { - + [TestMethod] + public async Task ProcessAsync_NoMoreFollowings() + { + #region Stubs + var follower = new Follower + { + Id = 12, + Followings = new List { 1 } + }; + #endregion + + #region Mocks + var rejectAllFollowingsActionMock = new Mock(); + rejectAllFollowingsActionMock + .Setup(x => x.ProcessAsync( + It.Is(y => y.Id == follower.Id))) + .Returns(Task.CompletedTask); + + var followersDalMock = new Mock(); + followersDalMock + .Setup(x => x.GetFollowersAsync( + It.Is(y => y == 1))) + .ReturnsAsync(new[] {follower}); + + followersDalMock + .Setup(x => x.DeleteFollowerAsync( + It.Is(y => y == 12))) + .Returns(Task.CompletedTask); + + var twitterUserDalMock = new Mock(); + twitterUserDalMock + .Setup(x => x.DeleteTwitterUserAsync( + It.Is(y => y == 1))) + .Returns(Task.CompletedTask); + #endregion + + var action = new RemoveFollowerAction(followersDalMock.Object, twitterUserDalMock.Object, rejectAllFollowingsActionMock.Object); + await action.ProcessAsync(follower); + + #region Validations + followersDalMock.VerifyAll(); + twitterUserDalMock.VerifyAll(); + rejectAllFollowingsActionMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ProcessAsync_HaveFollowings() + { + #region Stubs + var follower = new Follower + { + Id = 12, + Followings = new List { 1 } + }; + + var followers = new List + { + follower, + new Follower + { + Id = 11 + } + }; + #endregion + + #region Mocks + var rejectAllFollowingsActionMock = new Mock(); + rejectAllFollowingsActionMock + .Setup(x => x.ProcessAsync( + It.Is(y => y.Id == follower.Id))) + .Returns(Task.CompletedTask); + + var followersDalMock = new Mock(); + followersDalMock + .Setup(x => x.GetFollowersAsync( + It.Is(y => y == 1))) + .ReturnsAsync(followers.ToArray()); + + followersDalMock + .Setup(x => x.DeleteFollowerAsync( + It.Is(y => y == 12))) + .Returns(Task.CompletedTask); + + var twitterUserDalMock = new Mock(); + #endregion + + var action = new RemoveFollowerAction(followersDalMock.Object, twitterUserDalMock.Object, rejectAllFollowingsActionMock.Object); + await action.ProcessAsync(follower); + + #region Validations + followersDalMock.VerifyAll(); + twitterUserDalMock.VerifyAll(); + rejectAllFollowingsActionMock.VerifyAll(); + #endregion + } } } \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveTwitterAccountActionTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveTwitterAccountActionTests.cs index a0451c7..5e9342b 100644 --- a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveTwitterAccountActionTests.cs +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveTwitterAccountActionTests.cs @@ -1,7 +1,131 @@ -namespace BirdsiteLive.Moderation.Tests.Actions +using System.Collections.Generic; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.Moderation.Actions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace BirdsiteLive.Moderation.Tests.Actions { + [TestClass] public class RemoveTwitterAccountActionTests { - + [TestMethod] + public async Task ProcessAsync_RemoveFollower() + { + #region Stubs + var twitter = new SyncTwitterUser + { + Id = 24, + Acct = "my-acct" + }; + + var followers = new List + { + new Follower + { + Id = 48, + Followings = new List{ 24 }, + FollowingsSyncStatus = new Dictionary { { 24, 1024 } } + } + }; + #endregion + + #region Mocks + var followersDalMock = new Mock(); + followersDalMock + .Setup(x => x.GetFollowersAsync( + It.Is(y => y == 24))) + .ReturnsAsync(followers.ToArray()); + + followersDalMock + .Setup(x => x.DeleteFollowerAsync( + It.Is(y => y == 48))) + .Returns(Task.CompletedTask); + + var twitterUserDalMock = new Mock(); + twitterUserDalMock + .Setup(x => x.DeleteTwitterUserAsync( + It.Is(y => y == 24))) + .Returns(Task.CompletedTask); + + var rejectFollowingActionMock = new Mock(); + rejectFollowingActionMock + .Setup(x => x.ProcessAsync( + It.Is(y => y.Id == 48), + It.Is(y => y.Acct == twitter.Acct))) + .Returns(Task.CompletedTask); + #endregion + + var action = new RemoveTwitterAccountAction(followersDalMock.Object, twitterUserDalMock.Object, rejectFollowingActionMock.Object); + await action.ProcessAsync(twitter); + + #region Validations + followersDalMock.VerifyAll(); + twitterUserDalMock.VerifyAll(); + rejectFollowingActionMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ProcessAsync_KeepFollower() + { + #region Stubs + var twitter = new SyncTwitterUser + { + Id = 24, + Acct = "my-acct" + }; + + var followers = new List + { + new Follower + { + Id = 48, + Followings = new List{ 24, 36 }, + FollowingsSyncStatus = new Dictionary { { 24, 1024 }, { 36, 24 } } + } + }; + #endregion + + #region Mocks + var followersDalMock = new Mock(); + followersDalMock + .Setup(x => x.GetFollowersAsync( + It.Is(y => y == 24))) + .ReturnsAsync(followers.ToArray()); + + followersDalMock + .Setup(x => x.UpdateFollowerAsync( + It.Is(y => y.Id == 48 + && y.Followings.Count == 1 + && y.FollowingsSyncStatus.Count == 1 + ))) + .Returns(Task.CompletedTask); + + var twitterUserDalMock = new Mock(); + twitterUserDalMock + .Setup(x => x.DeleteTwitterUserAsync( + It.Is(y => y == 24))) + .Returns(Task.CompletedTask); + + var rejectFollowingActionMock = new Mock(); + rejectFollowingActionMock + .Setup(x => x.ProcessAsync( + It.Is(y => y.Id == 48), + It.Is(y => y.Acct == twitter.Acct))) + .Returns(Task.CompletedTask); + #endregion + + var action = new RemoveTwitterAccountAction(followersDalMock.Object, twitterUserDalMock.Object, rejectFollowingActionMock.Object); + await action.ProcessAsync(twitter); + + #region Validations + followersDalMock.VerifyAll(); + twitterUserDalMock.VerifyAll(); + rejectFollowingActionMock.VerifyAll(); + #endregion + } } } \ No newline at end of file From 2ff6128d701f4c07ac1e252340284dc6e0ced94f Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Tue, 16 Feb 2021 00:20:35 -0500 Subject: [PATCH 27/44] added processor tests --- .../Processors/FollowerModerationProcessor.cs | 3 +- .../TwitterAccountModerationProcessor.cs | 2 + .../Actions/RejectAllFollowingsActionTests.cs | 8 +- .../Actions/RejectFollowingActionTests.cs | 4 +- .../Actions/RemoveFollowerActionTests.cs | 12 +- .../RemoveTwitterAccountActionTests.cs | 12 +- .../FollowerModerationProcessorTests.cs | 201 +++++++++++++++++- .../TwitterAccountModerationProcessorTests.cs | 196 ++++++++++++++++- 8 files changed, 415 insertions(+), 23 deletions(-) diff --git a/src/BirdsiteLive.Moderation/Processors/FollowerModerationProcessor.cs b/src/BirdsiteLive.Moderation/Processors/FollowerModerationProcessor.cs index 18c9b14..99d72f6 100644 --- a/src/BirdsiteLive.Moderation/Processors/FollowerModerationProcessor.cs +++ b/src/BirdsiteLive.Moderation/Processors/FollowerModerationProcessor.cs @@ -1,6 +1,5 @@ using System.Threading.Tasks; using BirdsiteLive.DAL.Contracts; -using BirdsiteLive.DAL.Models; using BirdsiteLive.Domain.Repository; using BirdsiteLive.Moderation.Actions; @@ -28,6 +27,8 @@ namespace BirdsiteLive.Moderation.Processors public async Task ProcessAsync(ModerationTypeEnum type) { + if (type == ModerationTypeEnum.None) return; + var followers = await _followersDal.GetAllFollowersAsync(); foreach (var follower in followers) diff --git a/src/BirdsiteLive.Moderation/Processors/TwitterAccountModerationProcessor.cs b/src/BirdsiteLive.Moderation/Processors/TwitterAccountModerationProcessor.cs index 3c267bb..91e3931 100644 --- a/src/BirdsiteLive.Moderation/Processors/TwitterAccountModerationProcessor.cs +++ b/src/BirdsiteLive.Moderation/Processors/TwitterAccountModerationProcessor.cs @@ -27,6 +27,8 @@ namespace BirdsiteLive.Moderation.Processors public async Task ProcessAsync(ModerationTypeEnum type) { + if (type == ModerationTypeEnum.None) return; + var twitterUsers = await _twitterUserDal.GetAllTwitterUsersAsync(); foreach (var user in twitterUsers) diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectAllFollowingsActionTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectAllFollowingsActionTests.cs index 3fda47b..3ed5c78 100644 --- a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectAllFollowingsActionTests.cs +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectAllFollowingsActionTests.cs @@ -35,7 +35,7 @@ namespace BirdsiteLive.Moderation.Tests.Actions #endregion #region Mocks - var twitterUserDalMock = new Mock(); + var twitterUserDalMock = new Mock(MockBehavior.Strict); twitterUserDalMock .Setup(x => x.GetTwitterUserAsync( It.Is(y => y == 24))) @@ -45,7 +45,7 @@ namespace BirdsiteLive.Moderation.Tests.Actions Acct = "acct" }); - var userServiceMock = new Mock(); + var userServiceMock = new Mock(MockBehavior.Strict); userServiceMock .Setup(x => x.SendRejectFollowAsync( It.Is(y => y.type == "Follow"), @@ -83,7 +83,7 @@ namespace BirdsiteLive.Moderation.Tests.Actions #endregion #region Mocks - var twitterUserDalMock = new Mock(); + var twitterUserDalMock = new Mock(MockBehavior.Strict); twitterUserDalMock .Setup(x => x.GetTwitterUserAsync( It.Is(y => y == 24))) @@ -93,7 +93,7 @@ namespace BirdsiteLive.Moderation.Tests.Actions Acct = "acct" }); - var userServiceMock = new Mock(); + var userServiceMock = new Mock(MockBehavior.Strict); userServiceMock .Setup(x => x.SendRejectFollowAsync( It.Is(y => y.type == "Follow"), diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectFollowingActionTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectFollowingActionTests.cs index 7d7b1bf..74f037e 100644 --- a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectFollowingActionTests.cs +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RejectFollowingActionTests.cs @@ -40,7 +40,7 @@ namespace BirdsiteLive.Moderation.Tests.Actions #endregion #region Mocks - var userServiceMock = new Mock(); + var userServiceMock = new Mock(MockBehavior.Strict); userServiceMock .Setup(x => x.SendRejectFollowAsync( It.Is(y => y.type == "Follow"), @@ -83,7 +83,7 @@ namespace BirdsiteLive.Moderation.Tests.Actions #endregion #region Mocks - var userServiceMock = new Mock(); + var userServiceMock = new Mock(MockBehavior.Strict); userServiceMock .Setup(x => x.SendRejectFollowAsync( It.Is(y => y.type == "Follow"), diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs index 4def0ed..3b83739 100644 --- a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveFollowerActionTests.cs @@ -23,13 +23,13 @@ namespace BirdsiteLive.Moderation.Tests.Actions #endregion #region Mocks - var rejectAllFollowingsActionMock = new Mock(); + var rejectAllFollowingsActionMock = new Mock(MockBehavior.Strict); rejectAllFollowingsActionMock .Setup(x => x.ProcessAsync( It.Is(y => y.Id == follower.Id))) .Returns(Task.CompletedTask); - var followersDalMock = new Mock(); + var followersDalMock = new Mock(MockBehavior.Strict); followersDalMock .Setup(x => x.GetFollowersAsync( It.Is(y => y == 1))) @@ -40,7 +40,7 @@ namespace BirdsiteLive.Moderation.Tests.Actions It.Is(y => y == 12))) .Returns(Task.CompletedTask); - var twitterUserDalMock = new Mock(); + var twitterUserDalMock = new Mock(MockBehavior.Strict); twitterUserDalMock .Setup(x => x.DeleteTwitterUserAsync( It.Is(y => y == 1))) @@ -78,13 +78,13 @@ namespace BirdsiteLive.Moderation.Tests.Actions #endregion #region Mocks - var rejectAllFollowingsActionMock = new Mock(); + var rejectAllFollowingsActionMock = new Mock(MockBehavior.Strict); rejectAllFollowingsActionMock .Setup(x => x.ProcessAsync( It.Is(y => y.Id == follower.Id))) .Returns(Task.CompletedTask); - var followersDalMock = new Mock(); + var followersDalMock = new Mock(MockBehavior.Strict); followersDalMock .Setup(x => x.GetFollowersAsync( It.Is(y => y == 1))) @@ -95,7 +95,7 @@ namespace BirdsiteLive.Moderation.Tests.Actions It.Is(y => y == 12))) .Returns(Task.CompletedTask); - var twitterUserDalMock = new Mock(); + var twitterUserDalMock = new Mock(MockBehavior.Strict); #endregion var action = new RemoveFollowerAction(followersDalMock.Object, twitterUserDalMock.Object, rejectAllFollowingsActionMock.Object); diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveTwitterAccountActionTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveTwitterAccountActionTests.cs index 5e9342b..c0f5920 100644 --- a/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveTwitterAccountActionTests.cs +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Actions/RemoveTwitterAccountActionTests.cs @@ -33,7 +33,7 @@ namespace BirdsiteLive.Moderation.Tests.Actions #endregion #region Mocks - var followersDalMock = new Mock(); + var followersDalMock = new Mock(MockBehavior.Strict); followersDalMock .Setup(x => x.GetFollowersAsync( It.Is(y => y == 24))) @@ -44,13 +44,13 @@ namespace BirdsiteLive.Moderation.Tests.Actions It.Is(y => y == 48))) .Returns(Task.CompletedTask); - var twitterUserDalMock = new Mock(); + var twitterUserDalMock = new Mock(MockBehavior.Strict); twitterUserDalMock .Setup(x => x.DeleteTwitterUserAsync( It.Is(y => y == 24))) .Returns(Task.CompletedTask); - var rejectFollowingActionMock = new Mock(); + var rejectFollowingActionMock = new Mock(MockBehavior.Strict); rejectFollowingActionMock .Setup(x => x.ProcessAsync( It.Is(y => y.Id == 48), @@ -90,7 +90,7 @@ namespace BirdsiteLive.Moderation.Tests.Actions #endregion #region Mocks - var followersDalMock = new Mock(); + var followersDalMock = new Mock(MockBehavior.Strict); followersDalMock .Setup(x => x.GetFollowersAsync( It.Is(y => y == 24))) @@ -104,13 +104,13 @@ namespace BirdsiteLive.Moderation.Tests.Actions ))) .Returns(Task.CompletedTask); - var twitterUserDalMock = new Mock(); + var twitterUserDalMock = new Mock(MockBehavior.Strict); twitterUserDalMock .Setup(x => x.DeleteTwitterUserAsync( It.Is(y => y == 24))) .Returns(Task.CompletedTask); - var rejectFollowingActionMock = new Mock(); + var rejectFollowingActionMock = new Mock(MockBehavior.Strict); rejectFollowingActionMock .Setup(x => x.ProcessAsync( It.Is(y => y.Id == 48), diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Processors/FollowerModerationProcessorTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Processors/FollowerModerationProcessorTests.cs index 3acaa4f..3bc4946 100644 --- a/src/Tests/BirdsiteLive.Moderation.Tests/Processors/FollowerModerationProcessorTests.cs +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Processors/FollowerModerationProcessorTests.cs @@ -1,7 +1,204 @@ -namespace BirdsiteLive.Moderation.Tests.Processors +using System.Collections.Generic; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.Domain.Repository; +using BirdsiteLive.Moderation.Actions; +using BirdsiteLive.Moderation.Processors; +using Castle.DynamicProxy.Generators.Emitters; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace BirdsiteLive.Moderation.Tests.Processors { + [TestClass] public class FollowerModerationProcessorTests { - + [TestMethod] + public async Task ProcessAsync_None() + { + #region Mocks + var followersDalMock = new Mock(MockBehavior.Strict); + var moderationRepositoryMock = new Mock(MockBehavior.Strict); + var removeFollowerActionMock = new Mock(MockBehavior.Strict); + #endregion + + var processor = new FollowerModerationProcessor(followersDalMock.Object, moderationRepositoryMock.Object, removeFollowerActionMock.Object); + await processor.ProcessAsync(ModerationTypeEnum.None); + + #region Validations + followersDalMock.VerifyAll(); + moderationRepositoryMock.VerifyAll(); + removeFollowerActionMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ProcessAsync_WhiteListing_WhiteListed() + { + #region Stubs + var allFollowers = new List + { + new Follower + { + Acct = "acct", + Host = "host" + } + }; + #endregion + + #region Mocks + var followersDalMock = new Mock(MockBehavior.Strict); + followersDalMock + .Setup(x => x.GetAllFollowersAsync()) + .ReturnsAsync(allFollowers.ToArray()); + + var moderationRepositoryMock = new Mock(MockBehavior.Strict); + moderationRepositoryMock + .Setup(x => x.CheckStatus( + It.Is(y => y == ModerationEntityTypeEnum.Follower), + It.Is(y => y == "@acct@host"))) + .Returns(ModeratedTypeEnum.WhiteListed); + + var removeFollowerActionMock = new Mock(MockBehavior.Strict); + #endregion + + var processor = new FollowerModerationProcessor(followersDalMock.Object, moderationRepositoryMock.Object, removeFollowerActionMock.Object); + await processor.ProcessAsync(ModerationTypeEnum.WhiteListing); + + #region Validations + followersDalMock.VerifyAll(); + moderationRepositoryMock.VerifyAll(); + removeFollowerActionMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ProcessAsync_WhiteListing_NotWhiteListed() + { + #region Stubs + var allFollowers = new List + { + new Follower + { + Acct = "acct", + Host = "host" + } + }; + #endregion + + #region Mocks + var followersDalMock = new Mock(MockBehavior.Strict); + followersDalMock + .Setup(x => x.GetAllFollowersAsync()) + .ReturnsAsync(allFollowers.ToArray()); + + var moderationRepositoryMock = new Mock(MockBehavior.Strict); + moderationRepositoryMock + .Setup(x => x.CheckStatus( + It.Is(y => y == ModerationEntityTypeEnum.Follower), + It.Is(y => y == "@acct@host"))) + .Returns(ModeratedTypeEnum.None); + + var removeFollowerActionMock = new Mock(MockBehavior.Strict); + removeFollowerActionMock + .Setup(x => x.ProcessAsync( + It.Is(y => y.Acct == "acct"))) + .Returns(Task.CompletedTask); + #endregion + + var processor = new FollowerModerationProcessor(followersDalMock.Object, moderationRepositoryMock.Object, removeFollowerActionMock.Object); + await processor.ProcessAsync(ModerationTypeEnum.WhiteListing); + + #region Validations + followersDalMock.VerifyAll(); + moderationRepositoryMock.VerifyAll(); + removeFollowerActionMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ProcessAsync_BlackListing_BlackListed() + { + #region Stubs + var allFollowers = new List + { + new Follower + { + Acct = "acct", + Host = "host" + } + }; + #endregion + + #region Mocks + var followersDalMock = new Mock(MockBehavior.Strict); + followersDalMock + .Setup(x => x.GetAllFollowersAsync()) + .ReturnsAsync(allFollowers.ToArray()); + + var moderationRepositoryMock = new Mock(MockBehavior.Strict); + moderationRepositoryMock + .Setup(x => x.CheckStatus( + It.Is(y => y == ModerationEntityTypeEnum.Follower), + It.Is(y => y == "@acct@host"))) + .Returns(ModeratedTypeEnum.BlackListed); + + var removeFollowerActionMock = new Mock(MockBehavior.Strict); + removeFollowerActionMock + .Setup(x => x.ProcessAsync( + It.Is(y => y.Acct == "acct"))) + .Returns(Task.CompletedTask); + #endregion + + var processor = new FollowerModerationProcessor(followersDalMock.Object, moderationRepositoryMock.Object, removeFollowerActionMock.Object); + await processor.ProcessAsync(ModerationTypeEnum.BlackListing); + + #region Validations + followersDalMock.VerifyAll(); + moderationRepositoryMock.VerifyAll(); + removeFollowerActionMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ProcessAsync_BlackListing_NotBlackListed() + { + #region Stubs + var allFollowers = new List + { + new Follower + { + Acct = "acct", + Host = "host" + } + }; + #endregion + + #region Mocks + var followersDalMock = new Mock(MockBehavior.Strict); + followersDalMock + .Setup(x => x.GetAllFollowersAsync()) + .ReturnsAsync(allFollowers.ToArray()); + + var moderationRepositoryMock = new Mock(MockBehavior.Strict); + moderationRepositoryMock + .Setup(x => x.CheckStatus( + It.Is(y => y == ModerationEntityTypeEnum.Follower), + It.Is(y => y == "@acct@host"))) + .Returns(ModeratedTypeEnum.None); + + var removeFollowerActionMock = new Mock(MockBehavior.Strict); + #endregion + + var processor = new FollowerModerationProcessor(followersDalMock.Object, moderationRepositoryMock.Object, removeFollowerActionMock.Object); + await processor.ProcessAsync(ModerationTypeEnum.BlackListing); + + #region Validations + followersDalMock.VerifyAll(); + moderationRepositoryMock.VerifyAll(); + removeFollowerActionMock.VerifyAll(); + #endregion + } } } \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Moderation.Tests/Processors/TwitterAccountModerationProcessorTests.cs b/src/Tests/BirdsiteLive.Moderation.Tests/Processors/TwitterAccountModerationProcessorTests.cs index 8cf2020..21d1288 100644 --- a/src/Tests/BirdsiteLive.Moderation.Tests/Processors/TwitterAccountModerationProcessorTests.cs +++ b/src/Tests/BirdsiteLive.Moderation.Tests/Processors/TwitterAccountModerationProcessorTests.cs @@ -1,7 +1,199 @@ -namespace BirdsiteLive.Moderation.Tests.Processors +using System.Collections.Generic; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.Domain.Repository; +using BirdsiteLive.Moderation.Actions; +using BirdsiteLive.Moderation.Processors; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace BirdsiteLive.Moderation.Tests.Processors { + [TestClass] public class TwitterAccountModerationProcessorTests { - + [TestMethod] + public async Task ProcessAsync_None() + { + #region Mocks + var twitterUserDalMock = new Mock(MockBehavior.Strict); + var moderationRepositoryMock = new Mock(MockBehavior.Strict); + var removeTwitterAccountActionMock = new Mock(MockBehavior.Strict); + #endregion + + var processor = new TwitterAccountModerationProcessor(twitterUserDalMock.Object, moderationRepositoryMock.Object, removeTwitterAccountActionMock.Object); + await processor.ProcessAsync(ModerationTypeEnum.None); + + #region Validations + twitterUserDalMock.VerifyAll(); + moderationRepositoryMock.VerifyAll(); + removeTwitterAccountActionMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ProcessAsync_WhiteListing_WhiteListed() + { + #region Stubs + var allUsers = new List + { + new SyncTwitterUser + { + Acct = "acct" + } + }; + #endregion + + #region Mocks + var twitterUserDalMock = new Mock(MockBehavior.Strict); + twitterUserDalMock + .Setup(x => x.GetAllTwitterUsersAsync()) + .ReturnsAsync(allUsers.ToArray()); + + var moderationRepositoryMock = new Mock(MockBehavior.Strict); + moderationRepositoryMock + .Setup(x => x.CheckStatus( + It.Is(y => y == ModerationEntityTypeEnum.TwitterAccount), + It.Is(y => y == "acct"))) + .Returns(ModeratedTypeEnum.WhiteListed); + + var removeTwitterAccountActionMock = new Mock(MockBehavior.Strict); + #endregion + + var processor = new TwitterAccountModerationProcessor(twitterUserDalMock.Object, moderationRepositoryMock.Object, removeTwitterAccountActionMock.Object); + await processor.ProcessAsync(ModerationTypeEnum.WhiteListing); + + #region Validations + twitterUserDalMock.VerifyAll(); + moderationRepositoryMock.VerifyAll(); + removeTwitterAccountActionMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ProcessAsync_WhiteListing_NotWhiteListed() + { + #region Stubs + var allUsers = new List + { + new SyncTwitterUser + { + Acct = "acct" + } + }; + #endregion + + #region Mocks + var twitterUserDalMock = new Mock(MockBehavior.Strict); + twitterUserDalMock + .Setup(x => x.GetAllTwitterUsersAsync()) + .ReturnsAsync(allUsers.ToArray()); + + var moderationRepositoryMock = new Mock(MockBehavior.Strict); + moderationRepositoryMock + .Setup(x => x.CheckStatus( + It.Is(y => y == ModerationEntityTypeEnum.TwitterAccount), + It.Is(y => y == "acct"))) + .Returns(ModeratedTypeEnum.None); + + var removeTwitterAccountActionMock = new Mock(MockBehavior.Strict); + removeTwitterAccountActionMock + .Setup(x => x.ProcessAsync( + It.Is(y => y.Acct == "acct"))) + .Returns(Task.CompletedTask); + #endregion + + var processor = new TwitterAccountModerationProcessor(twitterUserDalMock.Object, moderationRepositoryMock.Object, removeTwitterAccountActionMock.Object); + await processor.ProcessAsync(ModerationTypeEnum.WhiteListing); + + #region Validations + twitterUserDalMock.VerifyAll(); + moderationRepositoryMock.VerifyAll(); + removeTwitterAccountActionMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ProcessAsync_BlackListing_BlackListed() + { + #region Stubs + var allUsers = new List + { + new SyncTwitterUser + { + Acct = "acct" + } + }; + #endregion + + #region Mocks + var twitterUserDalMock = new Mock(MockBehavior.Strict); + twitterUserDalMock + .Setup(x => x.GetAllTwitterUsersAsync()) + .ReturnsAsync(allUsers.ToArray()); + + var moderationRepositoryMock = new Mock(MockBehavior.Strict); + moderationRepositoryMock + .Setup(x => x.CheckStatus( + It.Is(y => y == ModerationEntityTypeEnum.TwitterAccount), + It.Is(y => y == "acct"))) + .Returns(ModeratedTypeEnum.BlackListed); + + var removeTwitterAccountActionMock = new Mock(MockBehavior.Strict); + removeTwitterAccountActionMock + .Setup(x => x.ProcessAsync( + It.Is(y => y.Acct == "acct"))) + .Returns(Task.CompletedTask); + #endregion + + var processor = new TwitterAccountModerationProcessor(twitterUserDalMock.Object, moderationRepositoryMock.Object, removeTwitterAccountActionMock.Object); + await processor.ProcessAsync(ModerationTypeEnum.BlackListing); + + #region Validations + twitterUserDalMock.VerifyAll(); + moderationRepositoryMock.VerifyAll(); + removeTwitterAccountActionMock.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task ProcessAsync_BlackListing_NotBlackListed() + { + #region Stubs + var allUsers = new List + { + new SyncTwitterUser + { + Acct = "acct" + } + }; + #endregion + + #region Mocks + var twitterUserDalMock = new Mock(MockBehavior.Strict); + twitterUserDalMock + .Setup(x => x.GetAllTwitterUsersAsync()) + .ReturnsAsync(allUsers.ToArray()); + + var moderationRepositoryMock = new Mock(MockBehavior.Strict); + moderationRepositoryMock + .Setup(x => x.CheckStatus( + It.Is(y => y == ModerationEntityTypeEnum.TwitterAccount), + It.Is(y => y == "acct"))) + .Returns(ModeratedTypeEnum.None); + + var removeTwitterAccountActionMock = new Mock(MockBehavior.Strict); + #endregion + + var processor = new TwitterAccountModerationProcessor(twitterUserDalMock.Object, moderationRepositoryMock.Object, removeTwitterAccountActionMock.Object); + await processor.ProcessAsync(ModerationTypeEnum.BlackListing); + + #region Validations + twitterUserDalMock.VerifyAll(); + moderationRepositoryMock.VerifyAll(); + removeTwitterAccountActionMock.VerifyAll(); + #endregion + } } } \ No newline at end of file From 2de826a0234d41b76960a8faf10aca460ae163a0 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Tue, 16 Feb 2021 00:32:47 -0500 Subject: [PATCH 28/44] clean up --- src/BirdsiteLive.Domain/Repository/ModerationRepository.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/BirdsiteLive.Domain/Repository/ModerationRepository.cs b/src/BirdsiteLive.Domain/Repository/ModerationRepository.cs index 35e9fcb..f3e23f5 100644 --- a/src/BirdsiteLive.Domain/Repository/ModerationRepository.cs +++ b/src/BirdsiteLive.Domain/Repository/ModerationRepository.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Text.RegularExpressions; using BirdsiteLive.Common.Settings; using BirdsiteLive.Domain.Tools; -using Newtonsoft.Json.Converters; namespace BirdsiteLive.Domain.Repository { From 56f0a3396a6197e448960f3b0716e3db04118e92 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Tue, 16 Feb 2021 23:45:49 -0500 Subject: [PATCH 29/44] added warning log level for insights --- src/BirdsiteLive/appsettings.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/BirdsiteLive/appsettings.json b/src/BirdsiteLive/appsettings.json index 3b41952..4969fe1 100644 --- a/src/BirdsiteLive/appsettings.json +++ b/src/BirdsiteLive/appsettings.json @@ -2,6 +2,11 @@ "Logging": { "Type": "none", "InstrumentationKey": "key", + "ApplicationInsights": { + "LogLevel": { + "Default": "Warning" + } + }, "LogLevel": { "Default": "Information", "Microsoft": "Warning", From 67801ea63141955317899ddf99dc06e7d105086d Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Wed, 17 Feb 2021 20:49:17 -0500 Subject: [PATCH 30/44] added nodeinfo partial view --- .../Component/NodeInfoViewComponent.cs | 16 ++++++++++++++++ .../Shared/Components/NodeInfo/Default.cshtml | 7 +++++++ src/BirdsiteLive/Views/Shared/_Layout.cshtml | 6 ++++++ src/BirdsiteLive/wwwroot/css/pattern.css | 8 ++++++++ 4 files changed, 37 insertions(+) create mode 100644 src/BirdsiteLive/Component/NodeInfoViewComponent.cs create mode 100644 src/BirdsiteLive/Views/Shared/Components/NodeInfo/Default.cshtml create mode 100644 src/BirdsiteLive/wwwroot/css/pattern.css diff --git a/src/BirdsiteLive/Component/NodeInfoViewComponent.cs b/src/BirdsiteLive/Component/NodeInfoViewComponent.cs new file mode 100644 index 0000000..b51c319 --- /dev/null +++ b/src/BirdsiteLive/Component/NodeInfoViewComponent.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace BirdsiteLive.Component +{ + public class NodeInfoViewComponent : ViewComponent + { + public IViewComponentResult Invoke() + { + return View(null); + } + } +} diff --git a/src/BirdsiteLive/Views/Shared/Components/NodeInfo/Default.cshtml b/src/BirdsiteLive/Views/Shared/Components/NodeInfo/Default.cshtml new file mode 100644 index 0000000..3c30c68 --- /dev/null +++ b/src/BirdsiteLive/Views/Shared/Components/NodeInfo/Default.cshtml @@ -0,0 +1,7 @@ +@* + For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 +*@ + +
+ TEST +
diff --git a/src/BirdsiteLive/Views/Shared/_Layout.cshtml b/src/BirdsiteLive/Views/Shared/_Layout.cshtml index f9f571a..7f089ad 100644 --- a/src/BirdsiteLive/Views/Shared/_Layout.cshtml +++ b/src/BirdsiteLive/Views/Shared/_Layout.cshtml @@ -9,6 +9,7 @@ +
@@ -39,6 +40,11 @@