Merge pull request #134 from NicolasConstant/topic_add-proper-exceptions
Topic add proper exceptions handling
This commit is contained in:
commit
b0e7601333
8 changed files with 576 additions and 83 deletions
|
@ -9,6 +9,7 @@ using BirdsiteLive.Moderation.Actions;
|
||||||
using BirdsiteLive.Pipeline.Contracts;
|
using BirdsiteLive.Pipeline.Contracts;
|
||||||
using BirdsiteLive.Pipeline.Models;
|
using BirdsiteLive.Pipeline.Models;
|
||||||
using BirdsiteLive.Twitter;
|
using BirdsiteLive.Twitter;
|
||||||
|
using BirdsiteLive.Twitter.Models;
|
||||||
|
|
||||||
namespace BirdsiteLive.Pipeline.Processors
|
namespace BirdsiteLive.Pipeline.Processors
|
||||||
{
|
{
|
||||||
|
@ -35,26 +36,61 @@ namespace BirdsiteLive.Pipeline.Processors
|
||||||
|
|
||||||
foreach (var user in syncTwitterUsers)
|
foreach (var user in syncTwitterUsers)
|
||||||
{
|
{
|
||||||
var userView = _twitterUserService.GetUser(user.Acct);
|
TwitterUser userView = null;
|
||||||
if (userView == null)
|
|
||||||
{
|
|
||||||
await AnalyseFailingUserAsync(user);
|
|
||||||
}
|
|
||||||
else if (!userView.Protected)
|
|
||||||
{
|
|
||||||
user.FetchingErrorCount = 0;
|
|
||||||
var userWtData = new UserWithDataToSync
|
|
||||||
{
|
|
||||||
User = user
|
|
||||||
};
|
|
||||||
usersWtData.Add(userWtData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
userView = _twitterUserService.GetUser(user.Acct);
|
||||||
|
}
|
||||||
|
catch (UserNotFoundException)
|
||||||
|
{
|
||||||
|
await ProcessNotFoundUserAsync(user);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
catch (UserHasBeenSuspendedException)
|
||||||
|
{
|
||||||
|
await ProcessNotFoundUserAsync(user);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
catch (RateLimitExceededException)
|
||||||
|
{
|
||||||
|
await ProcessRateLimitExceededAsync(user);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userView == null || userView.Protected)
|
||||||
|
{
|
||||||
|
await ProcessFailingUserAsync(user);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.FetchingErrorCount = 0;
|
||||||
|
var userWtData = new UserWithDataToSync
|
||||||
|
{
|
||||||
|
User = user
|
||||||
|
};
|
||||||
|
usersWtData.Add(userWtData);
|
||||||
|
}
|
||||||
return usersWtData.ToArray();
|
return usersWtData.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AnalyseFailingUserAsync(SyncTwitterUser user)
|
private async Task ProcessRateLimitExceededAsync(SyncTwitterUser user)
|
||||||
|
{
|
||||||
|
var dbUser = await _twitterUserDal.GetTwitterUserAsync(user.Acct);
|
||||||
|
dbUser.LastSync = DateTime.UtcNow;
|
||||||
|
await _twitterUserDal.UpdateTwitterUserAsync(dbUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessNotFoundUserAsync(SyncTwitterUser user)
|
||||||
|
{
|
||||||
|
await _removeTwitterAccountAction.ProcessAsync(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessFailingUserAsync(SyncTwitterUser user)
|
||||||
{
|
{
|
||||||
var dbUser = await _twitterUserDal.GetTwitterUserAsync(user.Acct);
|
var dbUser = await _twitterUserDal.GetTwitterUserAsync(user.Acct);
|
||||||
dbUser.FetchingErrorCount++;
|
dbUser.FetchingErrorCount++;
|
||||||
|
@ -68,9 +104,6 @@ namespace BirdsiteLive.Pipeline.Processors
|
||||||
{
|
{
|
||||||
await _twitterUserDal.UpdateTwitterUserAsync(dbUser);
|
await _twitterUserDal.UpdateTwitterUserAsync(dbUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purge
|
|
||||||
_twitterUserService.PurgeUser(user.Acct);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BirdsiteLive.Twitter
|
||||||
|
{
|
||||||
|
public class RateLimitExceededException : Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BirdsiteLive.Twitter
|
||||||
|
{
|
||||||
|
public class UserHasBeenSuspendedException : Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BirdsiteLive.Twitter
|
||||||
|
{
|
||||||
|
public class UserNotFoundException : Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ using BirdsiteLive.Twitter.Models;
|
||||||
using BirdsiteLive.Twitter.Tools;
|
using BirdsiteLive.Twitter.Tools;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Tweetinvi;
|
using Tweetinvi;
|
||||||
|
using Tweetinvi.Exceptions;
|
||||||
using Tweetinvi.Models;
|
using Tweetinvi.Models;
|
||||||
|
|
||||||
namespace BirdsiteLive.Twitter
|
namespace BirdsiteLive.Twitter
|
||||||
|
@ -45,17 +46,34 @@ namespace BirdsiteLive.Twitter
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
user = User.GetUserFromScreenName(username);
|
user = User.GetUserFromScreenName(username);
|
||||||
_statisticsHandler.CalledUserApi();
|
}
|
||||||
if (user == null)
|
catch (TwitterException e)
|
||||||
|
{
|
||||||
|
if (e.TwitterExceptionInfos.Any(x => x.Message.ToLowerInvariant().Contains("User has been suspended".ToLowerInvariant())))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("User {username} not found", username);
|
throw new UserHasBeenSuspendedException();
|
||||||
return null;
|
}
|
||||||
|
else if (e.TwitterExceptionInfos.Any(x => x.Message.ToLowerInvariant().Contains("User not found".ToLowerInvariant())))
|
||||||
|
{
|
||||||
|
throw new UserNotFoundException();
|
||||||
|
}
|
||||||
|
else if (e.TwitterExceptionInfos.Any(x => x.Message.ToLowerInvariant().Contains("Rate limit exceeded".ToLowerInvariant())))
|
||||||
|
{
|
||||||
|
throw new RateLimitExceededException();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError(e, "Error retrieving user {Username}", username);
|
_logger.LogError(e, "Error retrieving user {Username}", username);
|
||||||
return null;
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_statisticsHandler.CalledUserApi();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand URLs
|
// Expand URLs
|
||||||
|
|
|
@ -66,13 +66,42 @@ namespace BirdsiteLive.Controllers
|
||||||
|
|
||||||
id = id.Trim(new[] { ' ', '@' }).ToLowerInvariant();
|
id = id.Trim(new[] { ' ', '@' }).ToLowerInvariant();
|
||||||
|
|
||||||
|
TwitterUser user = null;
|
||||||
|
var isSaturated = false;
|
||||||
|
var notFound = false;
|
||||||
|
|
||||||
// Ensure valid username
|
// Ensure valid username
|
||||||
// https://help.twitter.com/en/managing-your-account/twitter-username-rules
|
// https://help.twitter.com/en/managing-your-account/twitter-username-rules
|
||||||
TwitterUser user = null;
|
|
||||||
if (!string.IsNullOrWhiteSpace(id) && UserRegexes.TwitterAccount.IsMatch(id) && id.Length <= 15)
|
if (!string.IsNullOrWhiteSpace(id) && UserRegexes.TwitterAccount.IsMatch(id) && id.Length <= 15)
|
||||||
user = _twitterUserService.GetUser(id);
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
user = _twitterUserService.GetUser(id);
|
||||||
|
}
|
||||||
|
catch (UserNotFoundException)
|
||||||
|
{
|
||||||
|
notFound = true;
|
||||||
|
}
|
||||||
|
catch (UserHasBeenSuspendedException)
|
||||||
|
{
|
||||||
|
notFound = true;
|
||||||
|
}
|
||||||
|
catch (RateLimitExceededException)
|
||||||
|
{
|
||||||
|
isSaturated = true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Exception getting {Id}", id);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
notFound = true;
|
||||||
|
}
|
||||||
|
|
||||||
var isSaturated = _twitterUserService.IsUserApiRateLimited();
|
//var isSaturated = _twitterUserService.IsUserApiRateLimited();
|
||||||
|
|
||||||
var acceptHeaders = Request.Headers["Accept"];
|
var acceptHeaders = Request.Headers["Accept"];
|
||||||
if (acceptHeaders.Any())
|
if (acceptHeaders.Any())
|
||||||
|
@ -80,17 +109,17 @@ namespace BirdsiteLive.Controllers
|
||||||
var r = acceptHeaders.First();
|
var r = acceptHeaders.First();
|
||||||
if (r.Contains("application/activity+json"))
|
if (r.Contains("application/activity+json"))
|
||||||
{
|
{
|
||||||
if (user == null && isSaturated) return new ObjectResult("Too Many Requests") { StatusCode = 429 };
|
if (isSaturated) return new ObjectResult("Too Many Requests") { StatusCode = 429 };
|
||||||
if (user == null) return NotFound();
|
if (notFound) return NotFound();
|
||||||
var apUser = _userService.GetUser(user);
|
var apUser = _userService.GetUser(user);
|
||||||
var jsonApUser = JsonConvert.SerializeObject(apUser);
|
var jsonApUser = JsonConvert.SerializeObject(apUser);
|
||||||
return Content(jsonApUser, "application/activity+json; charset=utf-8");
|
return Content(jsonApUser, "application/activity+json; charset=utf-8");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user == null && isSaturated) return View("ApiSaturated");
|
if (isSaturated) return View("ApiSaturated");
|
||||||
if (user == null) return View("UserNotFound");
|
if (notFound) return View("UserNotFound");
|
||||||
|
|
||||||
var displayableUser = new DisplayTwitterUser
|
var displayableUser = new DisplayTwitterUser
|
||||||
{
|
{
|
||||||
Name = user.Name,
|
Name = user.Name,
|
||||||
|
@ -138,46 +167,61 @@ namespace BirdsiteLive.Controllers
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Inbox()
|
public async Task<IActionResult> Inbox()
|
||||||
{
|
{
|
||||||
var r = Request;
|
try
|
||||||
using (var reader = new StreamReader(Request.Body))
|
|
||||||
{
|
{
|
||||||
var body = await reader.ReadToEndAsync();
|
var r = Request;
|
||||||
|
using (var reader = new StreamReader(Request.Body))
|
||||||
_logger.LogTrace("User Inbox: {Body}", body);
|
|
||||||
//System.IO.File.WriteAllText($@"C:\apdebug\{Guid.NewGuid()}.json", body);
|
|
||||||
|
|
||||||
var activity = ApDeserializer.ProcessActivity(body);
|
|
||||||
var signature = r.Headers["Signature"].First();
|
|
||||||
|
|
||||||
switch (activity?.type)
|
|
||||||
{
|
{
|
||||||
case "Follow":
|
var body = await reader.ReadToEndAsync();
|
||||||
{
|
|
||||||
var succeeded = await _userService.FollowRequestedAsync(signature, r.Method, r.Path,
|
_logger.LogTrace("User Inbox: {Body}", body);
|
||||||
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityFollow, body);
|
//System.IO.File.WriteAllText($@"C:\apdebug\{Guid.NewGuid()}.json", body);
|
||||||
if (succeeded) return Accepted();
|
|
||||||
else return Unauthorized();
|
var activity = ApDeserializer.ProcessActivity(body);
|
||||||
}
|
var signature = r.Headers["Signature"].First();
|
||||||
case "Undo":
|
|
||||||
if (activity is ActivityUndoFollow)
|
switch (activity?.type)
|
||||||
{
|
{
|
||||||
var succeeded = await _userService.UndoFollowRequestedAsync(signature, r.Method, r.Path,
|
case "Follow":
|
||||||
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityUndoFollow, body);
|
{
|
||||||
if (succeeded) return Accepted();
|
var succeeded = await _userService.FollowRequestedAsync(signature, r.Method, r.Path,
|
||||||
else return Unauthorized();
|
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityFollow, body);
|
||||||
}
|
if (succeeded) return Accepted();
|
||||||
return Accepted();
|
else return Unauthorized();
|
||||||
case "Delete":
|
}
|
||||||
{
|
case "Undo":
|
||||||
var succeeded = await _userService.DeleteRequestedAsync(signature, r.Method, r.Path,
|
if (activity is ActivityUndoFollow)
|
||||||
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityDelete, body);
|
{
|
||||||
if (succeeded) return Accepted();
|
var succeeded = await _userService.UndoFollowRequestedAsync(signature, r.Method, r.Path,
|
||||||
else return Unauthorized();
|
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityUndoFollow, body);
|
||||||
}
|
if (succeeded) return Accepted();
|
||||||
default:
|
else return Unauthorized();
|
||||||
return Accepted();
|
}
|
||||||
|
return Accepted();
|
||||||
|
case "Delete":
|
||||||
|
{
|
||||||
|
var succeeded = await _userService.DeleteRequestedAsync(signature, r.Method, r.Path,
|
||||||
|
r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityDelete, body);
|
||||||
|
if (succeeded) return Accepted();
|
||||||
|
else return Unauthorized();
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return Accepted();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (UserNotFoundException)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
catch (UserHasBeenSuspendedException)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
catch (RateLimitExceededException)
|
||||||
|
{
|
||||||
|
return new ObjectResult("Too Many Requests") { StatusCode = 429 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/users/{id}/followers")]
|
[Route("/users/{id}/followers")]
|
||||||
|
|
|
@ -12,6 +12,7 @@ using BirdsiteLive.Models;
|
||||||
using BirdsiteLive.Models.WellKnownModels;
|
using BirdsiteLive.Models.WellKnownModels;
|
||||||
using BirdsiteLive.Twitter;
|
using BirdsiteLive.Twitter;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace BirdsiteLive.Controllers
|
namespace BirdsiteLive.Controllers
|
||||||
|
@ -23,13 +24,15 @@ namespace BirdsiteLive.Controllers
|
||||||
private readonly ITwitterUserService _twitterUserService;
|
private readonly ITwitterUserService _twitterUserService;
|
||||||
private readonly ITwitterUserDal _twitterUserDal;
|
private readonly ITwitterUserDal _twitterUserDal;
|
||||||
private readonly InstanceSettings _settings;
|
private readonly InstanceSettings _settings;
|
||||||
|
private readonly ILogger<WellKnownController> _logger;
|
||||||
|
|
||||||
#region Ctor
|
#region Ctor
|
||||||
public WellKnownController(InstanceSettings settings, ITwitterUserService twitterUserService, ITwitterUserDal twitterUserDal, IModerationRepository moderationRepository)
|
public WellKnownController(InstanceSettings settings, ITwitterUserService twitterUserService, ITwitterUserDal twitterUserDal, IModerationRepository moderationRepository, ILogger<WellKnownController> logger)
|
||||||
{
|
{
|
||||||
_twitterUserService = twitterUserService;
|
_twitterUserService = twitterUserService;
|
||||||
_twitterUserDal = twitterUserDal;
|
_twitterUserDal = twitterUserDal;
|
||||||
_moderationRepository = moderationRepository;
|
_moderationRepository = moderationRepository;
|
||||||
|
_logger = logger;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -174,9 +177,27 @@ namespace BirdsiteLive.Controllers
|
||||||
if (!string.IsNullOrWhiteSpace(domain) && domain != _settings.Domain)
|
if (!string.IsNullOrWhiteSpace(domain) && domain != _settings.Domain)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
var user = _twitterUserService.GetUser(name);
|
try
|
||||||
if (user == null)
|
{
|
||||||
|
_twitterUserService.GetUser(name);
|
||||||
|
}
|
||||||
|
catch (UserNotFoundException)
|
||||||
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
}
|
||||||
|
catch (UserHasBeenSuspendedException)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
catch (RateLimitExceededException)
|
||||||
|
{
|
||||||
|
return new ObjectResult("Too Many Requests") { StatusCode = 429 };
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Exception getting {Name}", name);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
var actorUrl = UrlFactory.GetActorUrl(_settings.Domain, name);
|
var actorUrl = UrlFactory.GetActorUrl(_settings.Domain, name);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -159,11 +160,136 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
||||||
|
|
||||||
twitterUserServiceMock
|
twitterUserServiceMock
|
||||||
.Setup(x => x.GetUser(It.Is<string>(y => y == acct2)))
|
.Setup(x => x.GetUser(It.Is<string>(y => y == acct2)))
|
||||||
.Returns((TwitterUser) null);
|
.Throws(new UserNotFoundException());
|
||||||
|
|
||||||
|
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||||
|
|
||||||
|
var removeTwitterAccountActionMock = new Mock<IRemoveTwitterAccountAction>(MockBehavior.Strict);
|
||||||
|
removeTwitterAccountActionMock
|
||||||
|
.Setup(x => x.ProcessAsync(It.Is<SyncTwitterUser>(y => y.Acct == acct2)))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
var processor = new RefreshTwitterUserStatusProcessor(twitterUserServiceMock.Object, twitterUserDalMock.Object, removeTwitterAccountActionMock.Object, settings);
|
||||||
|
var result = await processor.ProcessAsync(users.ToArray(), CancellationToken.None);
|
||||||
|
|
||||||
|
#region Validations
|
||||||
|
Assert.AreEqual(1, result.Length);
|
||||||
|
Assert.IsTrue(result.Any(x => x.User.Id == userId1));
|
||||||
|
|
||||||
|
twitterUserServiceMock.VerifyAll();
|
||||||
|
twitterUserDalMock.VerifyAll();
|
||||||
|
removeTwitterAccountActionMock.VerifyAll();
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ProcessAsync_Suspended_Test()
|
||||||
|
{
|
||||||
|
#region Stubs
|
||||||
|
var userId1 = 1;
|
||||||
|
var acct1 = "user1";
|
||||||
|
|
||||||
|
var userId2 = 2;
|
||||||
|
var acct2 = "user2";
|
||||||
|
|
||||||
|
var users = new List<SyncTwitterUser>
|
||||||
|
{
|
||||||
|
new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId1,
|
||||||
|
Acct = acct1
|
||||||
|
},
|
||||||
|
new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId2,
|
||||||
|
Acct = acct2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var settings = new InstanceSettings
|
||||||
|
{
|
||||||
|
FailingTwitterUserCleanUpThreshold = 300
|
||||||
|
};
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Mocks
|
||||||
|
var twitterUserServiceMock = new Mock<ICachedTwitterUserService>(MockBehavior.Strict);
|
||||||
|
twitterUserServiceMock
|
||||||
|
.Setup(x => x.GetUser(It.Is<string>(y => y == acct1)))
|
||||||
|
.Returns(new TwitterUser
|
||||||
|
{
|
||||||
|
Protected = false
|
||||||
|
});
|
||||||
|
|
||||||
twitterUserServiceMock
|
twitterUserServiceMock
|
||||||
.Setup(x => x.PurgeUser(It.Is<string>(y => y == acct2)));
|
.Setup(x => x.GetUser(It.Is<string>(y => y == acct2)))
|
||||||
|
.Throws(new UserHasBeenSuspendedException());
|
||||||
|
|
||||||
|
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||||
|
|
||||||
|
var removeTwitterAccountActionMock = new Mock<IRemoveTwitterAccountAction>(MockBehavior.Strict);
|
||||||
|
removeTwitterAccountActionMock
|
||||||
|
.Setup(x => x.ProcessAsync(It.Is<SyncTwitterUser>(y => y.Acct == acct2)))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
var processor = new RefreshTwitterUserStatusProcessor(twitterUserServiceMock.Object, twitterUserDalMock.Object, removeTwitterAccountActionMock.Object, settings);
|
||||||
|
var result = await processor.ProcessAsync(users.ToArray(), CancellationToken.None);
|
||||||
|
|
||||||
|
#region Validations
|
||||||
|
Assert.AreEqual(1, result.Length);
|
||||||
|
Assert.IsTrue(result.Any(x => x.User.Id == userId1));
|
||||||
|
|
||||||
|
twitterUserServiceMock.VerifyAll();
|
||||||
|
twitterUserDalMock.VerifyAll();
|
||||||
|
removeTwitterAccountActionMock.VerifyAll();
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ProcessAsync_Exception_Test()
|
||||||
|
{
|
||||||
|
#region Stubs
|
||||||
|
var userId1 = 1;
|
||||||
|
var acct1 = "user1";
|
||||||
|
|
||||||
|
var userId2 = 2;
|
||||||
|
var acct2 = "user2";
|
||||||
|
|
||||||
|
var users = new List<SyncTwitterUser>
|
||||||
|
{
|
||||||
|
new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId1,
|
||||||
|
Acct = acct1
|
||||||
|
},
|
||||||
|
new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId2,
|
||||||
|
Acct = acct2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var settings = new InstanceSettings
|
||||||
|
{
|
||||||
|
FailingTwitterUserCleanUpThreshold = 300
|
||||||
|
};
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Mocks
|
||||||
|
var twitterUserServiceMock = new Mock<ICachedTwitterUserService>(MockBehavior.Strict);
|
||||||
|
twitterUserServiceMock
|
||||||
|
.Setup(x => x.GetUser(It.Is<string>(y => y == acct1)))
|
||||||
|
.Returns(new TwitterUser
|
||||||
|
{
|
||||||
|
Protected = false
|
||||||
|
});
|
||||||
|
|
||||||
|
twitterUserServiceMock
|
||||||
|
.Setup(x => x.GetUser(It.Is<string>(y => y == acct2)))
|
||||||
|
.Throws(new Exception());
|
||||||
|
|
||||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||||
twitterUserDalMock
|
twitterUserDalMock
|
||||||
.Setup(x => x.GetTwitterUserAsync(It.Is<string>(y => y == acct2)))
|
.Setup(x => x.GetTwitterUserAsync(It.Is<string>(y => y == acct2)))
|
||||||
|
@ -194,7 +320,7 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task ProcessAsync_Unfound_OverThreshold_Test()
|
public async Task ProcessAsync_Error_Test()
|
||||||
{
|
{
|
||||||
#region Stubs
|
#region Stubs
|
||||||
var userId1 = 1;
|
var userId1 = 1;
|
||||||
|
@ -235,10 +361,79 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
||||||
twitterUserServiceMock
|
twitterUserServiceMock
|
||||||
.Setup(x => x.GetUser(It.Is<string>(y => y == acct2)))
|
.Setup(x => x.GetUser(It.Is<string>(y => y == acct2)))
|
||||||
.Returns((TwitterUser)null);
|
.Returns((TwitterUser)null);
|
||||||
|
|
||||||
|
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||||
|
twitterUserDalMock
|
||||||
|
.Setup(x => x.GetTwitterUserAsync(It.Is<string>(y => y == acct2)))
|
||||||
|
.ReturnsAsync(new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId2,
|
||||||
|
FetchingErrorCount = 0
|
||||||
|
});
|
||||||
|
|
||||||
|
twitterUserDalMock
|
||||||
|
.Setup(x => x.UpdateTwitterUserAsync(It.Is<SyncTwitterUser>(y => y.Id == userId2 && y.FetchingErrorCount == 1)))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
|
||||||
|
var removeTwitterAccountActionMock = new Mock<IRemoveTwitterAccountAction>(MockBehavior.Strict);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
var processor = new RefreshTwitterUserStatusProcessor(twitterUserServiceMock.Object, twitterUserDalMock.Object, removeTwitterAccountActionMock.Object, settings);
|
||||||
|
var result = await processor.ProcessAsync(users.ToArray(), CancellationToken.None);
|
||||||
|
|
||||||
|
#region Validations
|
||||||
|
Assert.AreEqual(1, result.Length);
|
||||||
|
Assert.IsTrue(result.Any(x => x.User.Id == userId1));
|
||||||
|
|
||||||
|
twitterUserServiceMock.VerifyAll();
|
||||||
|
twitterUserDalMock.VerifyAll();
|
||||||
|
removeTwitterAccountActionMock.VerifyAll();
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ProcessAsync_Error_OverThreshold_Test()
|
||||||
|
{
|
||||||
|
#region Stubs
|
||||||
|
var userId1 = 1;
|
||||||
|
var acct1 = "user1";
|
||||||
|
|
||||||
|
var userId2 = 2;
|
||||||
|
var acct2 = "user2";
|
||||||
|
|
||||||
|
var users = new List<SyncTwitterUser>
|
||||||
|
{
|
||||||
|
new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId1,
|
||||||
|
Acct = acct1
|
||||||
|
},
|
||||||
|
new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId2,
|
||||||
|
Acct = acct2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var settings = new InstanceSettings
|
||||||
|
{
|
||||||
|
FailingTwitterUserCleanUpThreshold = 300
|
||||||
|
};
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Mocks
|
||||||
|
var twitterUserServiceMock = new Mock<ICachedTwitterUserService>(MockBehavior.Strict);
|
||||||
|
twitterUserServiceMock
|
||||||
|
.Setup(x => x.GetUser(It.Is<string>(y => y == acct1)))
|
||||||
|
.Returns(new TwitterUser
|
||||||
|
{
|
||||||
|
Protected = false
|
||||||
|
});
|
||||||
|
|
||||||
twitterUserServiceMock
|
twitterUserServiceMock
|
||||||
.Setup(x => x.PurgeUser(It.Is<string>(y => y == acct2)));
|
.Setup(x => x.GetUser(It.Is<string>(y => y == acct2)))
|
||||||
|
.Returns((TwitterUser)null);
|
||||||
|
|
||||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||||
twitterUserDalMock
|
twitterUserDalMock
|
||||||
.Setup(x => x.GetTwitterUserAsync(It.Is<string>(y => y == acct2)))
|
.Setup(x => x.GetTwitterUserAsync(It.Is<string>(y => y == acct2)))
|
||||||
|
@ -312,8 +507,20 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
||||||
{
|
{
|
||||||
Protected = true
|
Protected = true
|
||||||
});
|
});
|
||||||
|
|
||||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||||
|
twitterUserDalMock
|
||||||
|
.Setup(x => x.GetTwitterUserAsync(It.Is<string>(y => y == acct2)))
|
||||||
|
.ReturnsAsync(new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId2,
|
||||||
|
FetchingErrorCount = 0
|
||||||
|
});
|
||||||
|
|
||||||
|
twitterUserDalMock
|
||||||
|
.Setup(x => x.UpdateTwitterUserAsync(It.Is<SyncTwitterUser>(y => y.Id == userId2 && y.FetchingErrorCount == 1)))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
|
||||||
var removeTwitterAccountActionMock = new Mock<IRemoveTwitterAccountAction>(MockBehavior.Strict);
|
var removeTwitterAccountActionMock = new Mock<IRemoveTwitterAccountAction>(MockBehavior.Strict);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -331,7 +538,81 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task ProcessAsync_Unfound_NotInit_Test()
|
public async Task ProcessAsync_Protected_OverThreshold_Test()
|
||||||
|
{
|
||||||
|
#region Stubs
|
||||||
|
var userId1 = 1;
|
||||||
|
var acct1 = "user1";
|
||||||
|
|
||||||
|
var userId2 = 2;
|
||||||
|
var acct2 = "user2";
|
||||||
|
|
||||||
|
var users = new List<SyncTwitterUser>
|
||||||
|
{
|
||||||
|
new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId1,
|
||||||
|
Acct = acct1
|
||||||
|
},
|
||||||
|
new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId2,
|
||||||
|
Acct = acct2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var settings = new InstanceSettings
|
||||||
|
{
|
||||||
|
FailingTwitterUserCleanUpThreshold = 300
|
||||||
|
};
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Mocks
|
||||||
|
var twitterUserServiceMock = new Mock<ICachedTwitterUserService>(MockBehavior.Strict);
|
||||||
|
twitterUserServiceMock
|
||||||
|
.Setup(x => x.GetUser(It.Is<string>(y => y == acct1)))
|
||||||
|
.Returns(new TwitterUser
|
||||||
|
{
|
||||||
|
Protected = false
|
||||||
|
});
|
||||||
|
|
||||||
|
twitterUserServiceMock
|
||||||
|
.Setup(x => x.GetUser(It.Is<string>(y => y == acct2)))
|
||||||
|
.Returns(new TwitterUser
|
||||||
|
{
|
||||||
|
Protected = true
|
||||||
|
});
|
||||||
|
|
||||||
|
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||||
|
twitterUserDalMock
|
||||||
|
.Setup(x => x.GetTwitterUserAsync(It.Is<string>(y => y == acct2)))
|
||||||
|
.ReturnsAsync(new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId2,
|
||||||
|
FetchingErrorCount = 500
|
||||||
|
});
|
||||||
|
|
||||||
|
var removeTwitterAccountActionMock = new Mock<IRemoveTwitterAccountAction>(MockBehavior.Strict);
|
||||||
|
removeTwitterAccountActionMock
|
||||||
|
.Setup(x => x.ProcessAsync(It.Is<SyncTwitterUser>(y => y.Id == userId2)))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
var processor = new RefreshTwitterUserStatusProcessor(twitterUserServiceMock.Object, twitterUserDalMock.Object, removeTwitterAccountActionMock.Object, settings);
|
||||||
|
var result = await processor.ProcessAsync(users.ToArray(), CancellationToken.None);
|
||||||
|
|
||||||
|
#region Validations
|
||||||
|
Assert.AreEqual(1, result.Length);
|
||||||
|
Assert.IsTrue(result.Any(x => x.User.Id == userId1));
|
||||||
|
|
||||||
|
twitterUserServiceMock.VerifyAll();
|
||||||
|
twitterUserDalMock.VerifyAll();
|
||||||
|
removeTwitterAccountActionMock.VerifyAll();
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ProcessAsync_Error_NotInit_Test()
|
||||||
{
|
{
|
||||||
#region Stubs
|
#region Stubs
|
||||||
var userId1 = 1;
|
var userId1 = 1;
|
||||||
|
@ -361,9 +642,6 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
||||||
.Setup(x => x.GetUser(It.Is<string>(y => y == acct1)))
|
.Setup(x => x.GetUser(It.Is<string>(y => y == acct1)))
|
||||||
.Returns((TwitterUser)null);
|
.Returns((TwitterUser)null);
|
||||||
|
|
||||||
twitterUserServiceMock
|
|
||||||
.Setup(x => x.PurgeUser(It.Is<string>(y => y == acct1)));
|
|
||||||
|
|
||||||
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||||
twitterUserDalMock
|
twitterUserDalMock
|
||||||
.Setup(x => x.GetTwitterUserAsync(It.Is<string>(y => y == acct1)))
|
.Setup(x => x.GetTwitterUserAsync(It.Is<string>(y => y == acct1)))
|
||||||
|
@ -388,5 +666,77 @@ namespace BirdsiteLive.Pipeline.Tests.Processors
|
||||||
removeTwitterAccountActionMock.VerifyAll();
|
removeTwitterAccountActionMock.VerifyAll();
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ProcessAsync_RateLimited_Test()
|
||||||
|
{
|
||||||
|
#region Stubs
|
||||||
|
var userId1 = 1;
|
||||||
|
var acct1 = "user1";
|
||||||
|
|
||||||
|
var userId2 = 2;
|
||||||
|
var acct2 = "user2";
|
||||||
|
|
||||||
|
var users = new List<SyncTwitterUser>
|
||||||
|
{
|
||||||
|
new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId1,
|
||||||
|
Acct = acct1
|
||||||
|
},
|
||||||
|
new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId2,
|
||||||
|
Acct = acct2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var settings = new InstanceSettings
|
||||||
|
{
|
||||||
|
FailingTwitterUserCleanUpThreshold = 300
|
||||||
|
};
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Mocks
|
||||||
|
var twitterUserServiceMock = new Mock<ICachedTwitterUserService>(MockBehavior.Strict);
|
||||||
|
twitterUserServiceMock
|
||||||
|
.Setup(x => x.GetUser(It.Is<string>(y => y == acct1)))
|
||||||
|
.Returns(new TwitterUser
|
||||||
|
{
|
||||||
|
Protected = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
twitterUserServiceMock
|
||||||
|
.Setup(x => x.GetUser(It.Is<string>(y => y == acct2)))
|
||||||
|
.Throws(new RateLimitExceededException());
|
||||||
|
|
||||||
|
var twitterUserDalMock = new Mock<ITwitterUserDal>(MockBehavior.Strict);
|
||||||
|
twitterUserDalMock
|
||||||
|
.Setup(x => x.GetTwitterUserAsync(It.Is<string>(y => y == acct2)))
|
||||||
|
.ReturnsAsync(new SyncTwitterUser
|
||||||
|
{
|
||||||
|
Id = userId2,
|
||||||
|
FetchingErrorCount = 20
|
||||||
|
});
|
||||||
|
|
||||||
|
twitterUserDalMock
|
||||||
|
.Setup(x => x.UpdateTwitterUserAsync(It.Is<SyncTwitterUser>(y => y.Id == userId2 && y.FetchingErrorCount == 20)))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
|
||||||
|
var removeTwitterAccountActionMock = new Mock<IRemoveTwitterAccountAction>(MockBehavior.Strict);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
var processor = new RefreshTwitterUserStatusProcessor(twitterUserServiceMock.Object, twitterUserDalMock.Object, removeTwitterAccountActionMock.Object, settings);
|
||||||
|
var result = await processor.ProcessAsync(users.ToArray(), CancellationToken.None);
|
||||||
|
|
||||||
|
#region Validations
|
||||||
|
Assert.AreEqual(1, result.Length);
|
||||||
|
Assert.IsTrue(result.Any(x => x.User.Id == userId1));
|
||||||
|
|
||||||
|
twitterUserServiceMock.VerifyAll();
|
||||||
|
twitterUserDalMock.VerifyAll();
|
||||||
|
removeTwitterAccountActionMock.VerifyAll();
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue