diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 645b026..9e68110 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -28,5 +28,5 @@ jobs: run: dotnet build --configuration Release --no-restore working-directory: ${{env.working-directory}} - name: Test - run: dotnet test --no-restore --verbosity normal + run: dotnet test --no-restore --verbosity quiet working-directory: ${{env.working-directory}} diff --git a/src/BirdsiteLive/Controllers/WellKnownController.cs b/src/BirdsiteLive/Controllers/WellKnownController.cs index 89c7f7c..613b948 100644 --- a/src/BirdsiteLive/Controllers/WellKnownController.cs +++ b/src/BirdsiteLive/Controllers/WellKnownController.cs @@ -24,6 +24,52 @@ namespace BirdsiteLive.Controllers } #endregion + [Route("/.well-known/nodeinfo")] + public IActionResult WellKnownNodeInfo() + { + var nodeInfo = new WellKnownNodeInfo + { + links = new Link[] + { + new Link() + { + rel = "http://nodeinfo.diaspora.software/ns/schema/2.0", + href = $"https://{_settings.Domain}/nodeinfo/2.0.json" + } + } + }; + return new JsonResult(nodeInfo); + } + + [Route("/nodeinfo/2.0.json")] + public IActionResult NodeInfo() + { + var nodeInfo = new NodeInfo + { + version = "2.0", + usage = new Usage() + { + localPosts = 0, + users = new Users() + { + total = 0 + } + }, + software = new Software() + { + name = "BirdsiteLive", + version = "0.1.0" + }, + protocols = new [] + { + "activitypub" + }, + openRegistrations = false + }; + + return new JsonResult(nodeInfo); + } + [Route("/.well-known/webfinger")] public IActionResult Webfinger(string resource = null) { @@ -51,7 +97,7 @@ namespace BirdsiteLive.Controllers if (!string.IsNullOrWhiteSpace(domain) && domain != _settings.Domain) return NotFound(); - + var user = _twitterService.GetUser(name); if (user == null) return NotFound(); @@ -59,7 +105,7 @@ namespace BirdsiteLive.Controllers var result = new WebFingerResult() { subject = $"acct:{name}@{_settings.Domain}", - aliases = new [] + aliases = new[] { $"https://{_settings.Domain}/@{name}", $"https://{_settings.Domain}/users/{name}" @@ -83,19 +129,64 @@ namespace BirdsiteLive.Controllers return new JsonResult(result); } + } - public class WebFingerResult - { - public string subject { get; set; } - public string[] aliases { get; set; } - public List links { get; set; } = new List(); - } + public class WebFingerResult + { + public string subject { get; set; } + public string[] aliases { get; set; } + public List links { get; set; } = new List(); + } - public class WebFingerLink - { - public string rel { get; set; } - public string type { get; set; } - public string href { get; set; } - } + public class WebFingerLink + { + public string rel { get; set; } + public string type { get; set; } + public string href { get; set; } + } + + public class WellKnownNodeInfo + { + public Link[] links { get; set; } + } + + public class Link + { + public string href { get; set; } + public string rel { get; set; } + } + + public class NodeInfo + { + public string version { get; set; } + public string[] protocols { get; set; } + public Software software { get; set; } + public Usage usage { get; set; } + public bool openRegistrations { get; set; } + public Services services { get; set; } + public object metadata { get; set; } + } + + public class Services + { + public object[] inbound { get; set; } + public object[] outbound { get; set; } + } + + public class Software + { + public string name { get; set; } + public string version { get; set; } + } + + public class Usage + { + public int localPosts { get; set; } + public Users users { get; set; } + } + + public class Users + { + public int total { get; set; } } } \ No newline at end of file diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/CachedTweetsPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/CachedTweetsPostgresDal.cs new file mode 100644 index 0000000..b95d5d1 --- /dev/null +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/CachedTweetsPostgresDal.cs @@ -0,0 +1,83 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +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 Newtonsoft.Json; +using Tweetinvi.Models; + +namespace BirdsiteLive.DAL.Postgres.DataAccessLayers +{ + public class CachedTweetsPostgresDal : PostgresBase, ICachedTweetsDal + { + #region Ctor + public CachedTweetsPostgresDal(PostgresSettings settings) : base(settings) + { + + } + #endregion + + public async Task CreateTweetAsync(long tweetId, int userId, CachedTweet tweet) + { + if(tweetId == default) throw new ArgumentException("tweetId"); + if(userId == default) throw new ArgumentException("userId"); + + var serializedData = JsonConvert.SerializeObject(tweet); + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + await dbConnection.ExecuteAsync( + $"INSERT INTO {_settings.CachedTweetsTableName} (id,twitterUserId,data) VALUES(@id,@twitterUserId,CAST(@data as json))", + new { id = tweetId, twitterUserId = userId, data = serializedData }); + } + } + + public async Task GetTweetAsync(long tweetId) + { + if (tweetId == default) throw new ArgumentException("tweetId"); + + var query = $"SELECT * FROM {_settings.CachedTweetsTableName} WHERE id = @id"; + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + var result = (await dbConnection.QueryAsync(query, new { id = tweetId })).FirstOrDefault(); + return Convert(result); + } + } + + public async Task DeleteTweetAsync(long tweetId) + { + if (tweetId == default) throw new ArgumentException("tweetId"); + + var query = $"DELETE FROM {_settings.CachedTweetsTableName} WHERE id = @id"; + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + await dbConnection.QueryAsync(query, new { id = tweetId }); + } + } + + private CachedTweet Convert(SerializedTweet result) + { + if (result == null || string.IsNullOrWhiteSpace(result.Data)) return null; + return JsonConvert.DeserializeObject(result.Data); + } + } + + internal class SerializedTweet + { + public long Id { get; set; } + public int TwitterUserId { get; set; } + public string Data { get; set; } + } +} \ 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 cbe37d5..7b36644 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/DbInitializerPostgresDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/DbInitializerPostgresDal.cs @@ -106,8 +106,9 @@ namespace BirdsiteLive.DAL.Postgres.DataAccessLayers followings INTEGER[], followingsSyncStatus JSONB, - acct VARCHAR(20) UNIQUE, - host VARCHAR(20) + acct VARCHAR(50), + host VARCHAR(253), + UNIQUE (acct, host) );"; await _tools.ExecuteRequestAsync(createFollowers); diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs index ace1997..7e6c2ac 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/FollowersPostgresDal.cs @@ -1,9 +1,142 @@ -using BirdsiteLive.DAL.Contracts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; +using BirdsiteLive.DAL.Models; +using BirdsiteLive.DAL.Postgres.DataAccessLayers.Base; +using BirdsiteLive.DAL.Postgres.Settings; +using Dapper; +using Newtonsoft.Json; namespace BirdsiteLive.DAL.Postgres.DataAccessLayers { - public class FollowersPostgresDal : IFollowersDal + public class FollowersPostgresDal : PostgresBase, IFollowersDal { - + #region Ctor + public FollowersPostgresDal(PostgresSettings settings) : base(settings) + { + + } + #endregion + + public async Task CreateFollowerAsync(string acct, string host, int[] followings, Dictionary followingSyncStatus) + { + var serializedDic = JsonConvert.SerializeObject(followingSyncStatus); + + acct = acct.ToLowerInvariant(); + host = host.ToLowerInvariant(); + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + await dbConnection.ExecuteAsync( + $"INSERT INTO {_settings.FollowersTableName} (acct,host,followings,followingsSyncStatus) VALUES(@acct,@host,@followings, CAST(@followingsSyncStatus as json))", + new { acct, host, followings, followingsSyncStatus = serializedDic }); + } + } + + public async Task GetFollowerAsync(string acct, string host) + { + var query = $"SELECT * FROM {_settings.FollowersTableName} WHERE acct = @acct AND host = @host"; + + acct = acct.ToLowerInvariant(); + host = host.ToLowerInvariant(); + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + var result = (await dbConnection.QueryAsync(query, new { acct, host })).FirstOrDefault(); + return Convert(result); + } + } + + public async Task GetFollowersAsync(int followedUserId) + { + if (followedUserId == default) throw new ArgumentException("followedUserId"); + + var query = $"SELECT * FROM {_settings.FollowersTableName} WHERE @id=ANY(followings)"; + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + var result = await dbConnection.QueryAsync(query, new { id = followedUserId}); + return result.Select(Convert).ToArray(); + } + } + + public async Task UpdateFollowerAsync(int id, int[] followings, Dictionary followingsSyncStatus) + { + if (id == default) throw new ArgumentException("id"); + + var serializedDic = JsonConvert.SerializeObject(followingsSyncStatus); + var query = $"UPDATE {_settings.FollowersTableName} SET followings = @followings, followingsSyncStatus = CAST(@followingsSyncStatus as json) WHERE id = @id"; + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + await dbConnection.QueryAsync(query, new { id, followings, followingsSyncStatus = serializedDic }); + } + } + + public async Task DeleteFollowerAsync(int id) + { + if (id == default) throw new ArgumentException("id"); + + var query = $"DELETE FROM {_settings.FollowersTableName} WHERE id = @id"; + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + await dbConnection.QueryAsync(query, new { id }); + } + } + + public async Task DeleteFollowerAsync(string acct, string host) + { + if (acct == default) throw new ArgumentException("acct"); + if (host == default) throw new ArgumentException("host"); + + acct = acct.ToLowerInvariant(); + host = host.ToLowerInvariant(); + + var query = $"DELETE FROM {_settings.FollowersTableName} WHERE acct = @acct AND host = @host"; + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + await dbConnection.QueryAsync(query, new { acct, host }); + } + } + + private Follower Convert(SerializedFollower follower) + { + if (follower == null) return null; + + return new Follower() + { + Id = follower.Id, + Acct = follower.Acct, + Host = follower.Host, + Followings = follower.Followings, + FollowingsSyncStatus = JsonConvert.DeserializeObject>(follower.FollowingsSyncStatus) + }; + } + } + + internal class SerializedFollower { + public int Id { get; set; } + + public int[] Followings { get; set; } + public string FollowingsSyncStatus { get; set; } + + public string Acct { get; set; } + public string Host { get; set; } } } \ No newline at end of file diff --git a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs index 2058bf3..eaa610b 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL.Postgres/DataAccessLayers/TwitterUserPostgresDal.cs @@ -1,9 +1,97 @@ -using BirdsiteLive.DAL.Contracts; +using System; +using System.Linq; +using System.Threading.Tasks; +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 { - public class TwitterUserPostgresDal : ITwitterUserDal + public class TwitterUserPostgresDal : PostgresBase, ITwitterUserDal { - + #region Ctor + public TwitterUserPostgresDal(PostgresSettings settings) : base(settings) + { + + } + #endregion + + public async Task CreateTwitterUserAsync(string acct, long lastTweetPostedId) + { + acct = acct.ToLowerInvariant(); + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + await dbConnection.ExecuteAsync( + $"INSERT INTO {_settings.TwitterUserTableName} (acct,lastTweetPostedId,lastTweetSynchronizedForAllFollowersId) VALUES(@acct,@lastTweetPostedId,@lastTweetSynchronizedForAllFollowersId)", + new { acct, lastTweetPostedId, lastTweetSynchronizedForAllFollowersId = lastTweetPostedId }); + } + } + + public async Task GetTwitterUserAsync(string acct) + { + var query = $"SELECT * FROM {_settings.TwitterUserTableName} WHERE acct = @acct"; + + acct = acct.ToLowerInvariant(); + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + var result = (await dbConnection.QueryAsync(query, new { acct = acct })).FirstOrDefault(); + return result; + } + } + + public async Task GetAllTwitterUsersAsync() + { + 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) + { + if(id == default) throw new ArgumentException("id"); + if(lastTweetPostedId == default) throw new ArgumentException("lastTweetPostedId"); + if(lastTweetSynchronizedForAllFollowersId == default) throw new ArgumentException("lastTweetSynchronizedForAllFollowersId"); + + var query = $"UPDATE {_settings.TwitterUserTableName} SET lastTweetPostedId = @lastTweetPostedId, lastTweetSynchronizedForAllFollowersId = @lastTweetSynchronizedForAllFollowersId WHERE id = @id"; + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + await dbConnection.QueryAsync(query, new { id, lastTweetPostedId, lastTweetSynchronizedForAllFollowersId }); + } + } + + public async Task DeleteTwitterUserAsync(string acct) + { + if (acct == default) throw new ArgumentException("acct"); + + acct = acct.ToLowerInvariant(); + + var query = $"DELETE FROM {_settings.TwitterUserTableName} WHERE acct = @acct"; + + using (var dbConnection = Connection) + { + dbConnection.Open(); + + await dbConnection.QueryAsync(query, new { acct }); + } + } } } \ No newline at end of file diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/BirdsiteLive.DAL.csproj b/src/DataAccessLayers/BirdsiteLive.DAL/BirdsiteLive.DAL.csproj index 9f5c4f4..84e0bf0 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/BirdsiteLive.DAL.csproj +++ b/src/DataAccessLayers/BirdsiteLive.DAL/BirdsiteLive.DAL.csproj @@ -4,4 +4,8 @@ netstandard2.0 + + + + diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ICachedTweetsDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ICachedTweetsDal.cs new file mode 100644 index 0000000..64ffe41 --- /dev/null +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ICachedTweetsDal.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; +using BirdsiteLive.DAL.Models; +using Tweetinvi.Models; + +namespace BirdsiteLive.DAL.Contracts +{ + public interface ICachedTweetsDal + { + Task CreateTweetAsync(long tweetId, int userId, CachedTweet tweet); + Task GetTweetAsync(long tweetId); + Task DeleteTweetAsync(long tweetId); + } +} \ No newline at end of file diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IFollowersDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IFollowersDal.cs index ab8392d..92e0cb3 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IFollowersDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IFollowersDal.cs @@ -1,7 +1,16 @@ -namespace BirdsiteLive.DAL.Contracts +using System.Collections.Generic; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Models; + +namespace BirdsiteLive.DAL.Contracts { public interface IFollowersDal { - + Task GetFollowerAsync(string acct, string host); + Task CreateFollowerAsync(string acct, string host, int[] followings, Dictionary followingSyncStatus); + Task GetFollowersAsync(int followedUserId); + Task UpdateFollowerAsync(int id, int[] followings, Dictionary followingSyncStatus); + Task DeleteFollowerAsync(int id); + Task DeleteFollowerAsync(string acct, string host); } } \ 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 195b49b..e0050fd 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/ITwitterUserDal.cs @@ -1,7 +1,14 @@ -namespace BirdsiteLive.DAL.Contracts +using System.Threading.Tasks; +using BirdsiteLive.DAL.Models; + +namespace BirdsiteLive.DAL.Contracts { public interface ITwitterUserDal { - + Task CreateTwitterUserAsync(string acct, long lastTweetPostedId); + Task GetTwitterUserAsync(string acct); + Task GetAllTwitterUsersAsync(); + Task UpdateTwitterUserAsync(int id, long lastTweetPostedId, long lastTweetSynchronizedForAllFollowersId); + Task DeleteTwitterUserAsync(string acct); } } \ No newline at end of file diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Models/CachedTweet.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Models/CachedTweet.cs index d1472b6..25f5120 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/Models/CachedTweet.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Models/CachedTweet.cs @@ -1,10 +1,31 @@ -namespace BirdsiteLive.DAL.Models +using System; + +namespace BirdsiteLive.DAL.Models { public class CachedTweet { + public int UserId { get; set; } + public long Id { get; set; } - public long TwitterUserId { get; set; } - - public string TweetData { get; set; } + public DateTime CreatedAt { get; set; } + + public string Text { get; set; } + public string FullText { get; set; } + + public long? InReplyToStatusId { get; set; } + public string InReplyToStatusIdStr { get; set; } + public long? InReplyToUserId { get; set; } + public string InReplyToUserIdStr { get; set; } + public string InReplyToScreenName { get; set; } + + // List Hashtags { get; } + //List Urls { get; } + //List Media { get; } + //List UserMentions { get; } + //List Retweets { get; set; } + public bool IsRetweet { get; } + public CachedTweet RetweetedTweet { get; } + public string Url { get; } } + } \ No newline at end of file diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Models/Follower.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Models/Follower.cs index a6c0903..5eedafb 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/Models/Follower.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Models/Follower.cs @@ -1,13 +1,15 @@ -namespace BirdsiteLive.DAL.Models +using System.Collections.Generic; + +namespace BirdsiteLive.DAL.Models { public class Follower { public int Id { get; set; } - public int FollowingAccountId { get; set; } + public int[] Followings { get; set; } + public Dictionary FollowingsSyncStatus { get; set; } + public string Acct { get; set; } public string Host { get; set; } - - public long LastTweetSynchronizedId { get; set; } } } \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/Base/PostgresTestingBase.cs b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/Base/PostgresTestingBase.cs new file mode 100644 index 0000000..72bf352 --- /dev/null +++ b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/Base/PostgresTestingBase.cs @@ -0,0 +1,27 @@ +using BirdsiteLive.DAL.Postgres.Settings; +using BirdsiteLive.DAL.Postgres.Tools; +using BirdsiteLive.DAL.Tools; + +namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers.Base +{ + public class PostgresTestingBase + { + protected readonly PostgresSettings _settings; + protected readonly PostgresTools _tools; + + #region Ctor + public PostgresTestingBase() + { + _settings = new PostgresSettings + { + ConnString = "Host=127.0.0.1;Username=postgres;Password=mysecretpassword;Database=mytestdb", + DbVersionTableName = "DbVersionTableName" + RandomGenerator.GetString(4), + CachedTweetsTableName = "CachedTweetsTableName" + RandomGenerator.GetString(4), + FollowersTableName = "FollowersTableName" + RandomGenerator.GetString(4), + TwitterUserTableName = "TwitterUserTableName" + RandomGenerator.GetString(4), + }; + _tools = new PostgresTools(_settings); + } + #endregion + } +} \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/CachedTweetsPostgresDalTests.cs b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/CachedTweetsPostgresDalTests.cs new file mode 100644 index 0000000..021784e --- /dev/null +++ b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/CachedTweetsPostgresDalTests.cs @@ -0,0 +1,87 @@ +using System; +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; + +namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers +{ + [TestClass] + public class CachedTweetsPostgresDalTests : PostgresTestingBase + { + [TestInitialize] + public async Task TestInit() + { + var dal = new DbInitializerPostgresDal(_settings, _tools); + await dal.InitDbAsync(); + } + + [TestCleanup] + public async Task CleanUp() + { + var dal = new DbInitializerPostgresDal(_settings, _tools); + try + { + await dal.DeleteAllAsync(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + [TestMethod] + public async Task CreateAndGet() + { + var id = 152L; + var userId = 15; + + var tweet = new CachedTweet + { + UserId = userId, + Id = id, + Text = "text data", + FullText = "full text data", + CreatedAt = DateTime.UtcNow + }; + + var dal = new CachedTweetsPostgresDal(_settings); + await dal.CreateTweetAsync(id, userId, tweet); + + var result = await dal.GetTweetAsync(id); + + Assert.IsNotNull(result); + Assert.AreEqual(id, result.Id); + Assert.AreEqual(tweet.Text, result.Text); + Assert.AreEqual(tweet.FullText, result.FullText); + Assert.AreEqual(tweet.CreatedAt, result.CreatedAt); + } + + [TestMethod] + public async Task CreateAndDelete() + { + var id = 152L; + var userId = 15; + + var tweet = new CachedTweet + { + UserId = userId, + Id = id, + Text = "text data", + FullText = "full text data", + CreatedAt = DateTime.UtcNow + }; + + var dal = new CachedTweetsPostgresDal(_settings); + await dal.CreateTweetAsync(id, userId, tweet); + + var result = await dal.GetTweetAsync(id); + Assert.IsNotNull(result); + + await dal.DeleteTweetAsync(id); + result = await dal.GetTweetAsync(id); + Assert.IsNull(result); + } + } +} \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/DbInitializerPostgresDalTests.cs b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/DbInitializerPostgresDalTests.cs index 5ca5968..7fc5383 100644 --- a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/DbInitializerPostgresDalTests.cs +++ b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/DbInitializerPostgresDalTests.cs @@ -1,34 +1,14 @@ using System; using System.Threading.Tasks; using BirdsiteLive.DAL.Postgres.DataAccessLayers; -using BirdsiteLive.DAL.Postgres.Settings; -using BirdsiteLive.DAL.Postgres.Tools; -using BirdsiteLive.DAL.Tools; +using BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers.Base; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers { [TestClass] - public class DbInitializerPostgresDalTests + public class DbInitializerPostgresDalTests : PostgresTestingBase { - private readonly PostgresSettings _settings; - private readonly PostgresTools _tools; - - #region Ctor - public DbInitializerPostgresDalTests() - { - _settings = new PostgresSettings - { - ConnString = "Host=127.0.0.1;Username=postgres;Password=mysecretpassword;Database=mytestdb", - DbVersionTableName = "DbVersionTableName" + RandomGenerator.GetString(4), - CachedTweetsTableName = "CachedTweetsTableName" + RandomGenerator.GetString(4), - FollowersTableName = "FollowersTableName" + RandomGenerator.GetString(4), - TwitterUserTableName = "TwitterUserTableName" + RandomGenerator.GetString(4), - }; - _tools = new PostgresTools(_settings); - } - #endregion - [TestCleanup] public async Task CleanUp() { diff --git a/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs new file mode 100644 index 0000000..f359f06 --- /dev/null +++ b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Postgres.DataAccessLayers; +using BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers.Base; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers +{ + [TestClass] + public class FollowersPostgresDalTests : PostgresTestingBase + { + [TestInitialize] + public async Task TestInit() + { + var dal = new DbInitializerPostgresDal(_settings, _tools); + await dal.InitDbAsync(); + } + + [TestCleanup] + public async Task CleanUp() + { + var dal = new DbInitializerPostgresDal(_settings, _tools); + try + { + await dal.DeleteAllAsync(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + [TestMethod] + public async Task CreateAndGetFollower() + { + var acct = "myhandle"; + var host = "domain.ext"; + var following = new[] {12, 19, 23}; + var followingSync = new Dictionary() + { + {12, 165L}, + {19, 166L}, + {23, 167L} + }; + + var dal = new FollowersPostgresDal(_settings); + await dal.CreateFollowerAsync(acct, host, following, followingSync); + + var result = await dal.GetFollowerAsync(acct, host); + + Assert.IsNotNull(result); + Assert.AreEqual(acct, result.Acct); + Assert.AreEqual(host, result.Host); + Assert.AreEqual(following.Length, result.Followings.Length); + Assert.AreEqual(following[0], result.Followings[0]); + Assert.AreEqual(followingSync.Count, result.FollowingsSyncStatus.Count); + Assert.AreEqual(followingSync.First().Key, result.FollowingsSyncStatus.First().Key); + Assert.AreEqual(followingSync.First().Value, result.FollowingsSyncStatus.First().Value); + } + + [TestMethod] + public async Task GetFollowersAsync() + { + var dal = new FollowersPostgresDal(_settings); + + //User 1 + var acct = "myhandle1"; + var host = "domain.ext"; + var following = new[] { 1,2,3 }; + var followingSync = new Dictionary(); + await dal.CreateFollowerAsync(acct, host, following, followingSync); + + //User 2 + acct = "myhandle2"; + host = "domain.ext"; + following = new[] { 2, 4, 5 }; + await dal.CreateFollowerAsync(acct, host, following, followingSync); + + //User 2 + acct = "myhandle3"; + host = "domain.ext"; + following = new[] { 1 }; + await dal.CreateFollowerAsync(acct, host, following, followingSync); + + var result = await dal.GetFollowersAsync(2); + Assert.AreEqual(2, result.Length); + + result = await dal.GetFollowersAsync(5); + Assert.AreEqual(1, result.Length); + + result = await dal.GetFollowersAsync(24); + Assert.AreEqual(0, result.Length); + } + + [TestMethod] + public async Task CreateUpdateAndGetFollower_Add() + { + var acct = "myhandle"; + var host = "domain.ext"; + var following = new[] { 12, 19, 23 }; + var followingSync = new Dictionary() + { + {12, 165L}, + {19, 166L}, + {23, 167L} + }; + + var dal = new FollowersPostgresDal(_settings); + await dal.CreateFollowerAsync(acct, host, following, followingSync); + var result = await dal.GetFollowerAsync(acct, host); + + var updatedFollowing = new[] { 12, 19, 23, 24 }; + var updatedFollowingSync = new Dictionary() + { + {12, 170L}, + {19, 171L}, + {23, 172L}, + {24, 173L} + }; + + await dal.UpdateFollowerAsync(result.Id, updatedFollowing, updatedFollowingSync); + result = await dal.GetFollowerAsync(acct, host); + + Assert.AreEqual(updatedFollowing.Length, result.Followings.Length); + Assert.AreEqual(updatedFollowing[0], result.Followings[0]); + Assert.AreEqual(updatedFollowingSync.Count, result.FollowingsSyncStatus.Count); + Assert.AreEqual(updatedFollowingSync.First().Key, result.FollowingsSyncStatus.First().Key); + Assert.AreEqual(updatedFollowingSync.First().Value, result.FollowingsSyncStatus.First().Value); + } + + [TestMethod] + public async Task CreateUpdateAndGetFollower_Remove() + { + var acct = "myhandle"; + var host = "domain.ext"; + var following = new[] { 12, 19, 23 }; + var followingSync = new Dictionary() + { + {12, 165L}, + {19, 166L}, + {23, 167L} + }; + + var dal = new FollowersPostgresDal(_settings); + await dal.CreateFollowerAsync(acct, host, following, followingSync); + var result = await dal.GetFollowerAsync(acct, host); + + var updatedFollowing = new[] { 12, 19 }; + var updatedFollowingSync = new Dictionary() + { + {12, 170L}, + {19, 171L} + }; + + await dal.UpdateFollowerAsync(result.Id, updatedFollowing, updatedFollowingSync); + result = await dal.GetFollowerAsync(acct, host); + + Assert.AreEqual(updatedFollowing.Length, result.Followings.Length); + Assert.AreEqual(updatedFollowing[0], result.Followings[0]); + Assert.AreEqual(updatedFollowingSync.Count, result.FollowingsSyncStatus.Count); + Assert.AreEqual(updatedFollowingSync.First().Key, result.FollowingsSyncStatus.First().Key); + Assert.AreEqual(updatedFollowingSync.First().Value, result.FollowingsSyncStatus.First().Value); + } + + [TestMethod] + public async Task CreateAndDeleteFollower_ById() + { + var acct = "myhandle"; + var host = "domain.ext"; + var following = new[] { 12, 19, 23 }; + var followingSync = new Dictionary() + { + {12, 165L}, + {19, 166L}, + {23, 167L} + }; + + var dal = new FollowersPostgresDal(_settings); + await dal.CreateFollowerAsync(acct, host, following, followingSync); + var result = await dal.GetFollowerAsync(acct, host); + Assert.IsNotNull(result); + + await dal.DeleteFollowerAsync(result.Id); + + result = await dal.GetFollowerAsync(acct, host); + Assert.IsNull(result); + } + + [TestMethod] + public async Task CreateAndDeleteFollower_ByHandle() + { + var acct = "myhandle"; + var host = "domain.ext"; + var following = new[] { 12, 19, 23 }; + var followingSync = new Dictionary() + { + {12, 165L}, + {19, 166L}, + {23, 167L} + }; + + var dal = new FollowersPostgresDal(_settings); + await dal.CreateFollowerAsync(acct, host, following, followingSync); + var result = await dal.GetFollowerAsync(acct, host); + Assert.IsNotNull(result); + + await dal.DeleteFollowerAsync(acct, host); + + result = await dal.GetFollowerAsync(acct, host); + Assert.IsNull(result); + } + } +} \ 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 new file mode 100644 index 0000000..7150409 --- /dev/null +++ b/src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/TwitterUserPostgresDalTests.cs @@ -0,0 +1,119 @@ +using System; +using System.Threading.Tasks; +using System.Xml; +using BirdsiteLive.DAL.Postgres.DataAccessLayers; +using BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers.Base; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace BirdsiteLive.DAL.Postgres.Tests.DataAccessLayers +{ + [TestClass] + public class TwitterUserPostgresDalTests : PostgresTestingBase + { + [TestInitialize] + public async Task TestInit() + { + var dal = new DbInitializerPostgresDal(_settings, _tools); + await dal.InitDbAsync(); + } + + [TestCleanup] + public async Task CleanUp() + { + var dal = new DbInitializerPostgresDal(_settings, _tools); + try + { + await dal.DeleteAllAsync(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + [TestMethod] + public async Task GetTwitterUserAsync_NoUser() + { + var dal = new TwitterUserPostgresDal(_settings); + var result = await dal.GetTwitterUserAsync("dontexist"); + Assert.IsNull(result); + } + + [TestMethod] + public async Task CreateAndGetUser() + { + var acct = "myid"; + var lastTweetId = 1548L; + + var dal = new TwitterUserPostgresDal(_settings); + + await dal.CreateTwitterUserAsync(acct, lastTweetId); + var result = await dal.GetTwitterUserAsync(acct); + + Assert.AreEqual(acct, result.Acct); + Assert.AreEqual(lastTweetId, result.LastTweetPostedId); + Assert.AreEqual(lastTweetId, result.LastTweetSynchronizedForAllFollowersId); + Assert.IsTrue(result.Id > 0); + } + + [TestMethod] + public async Task CreateUpdateAndGetUser() + { + var acct = "myid"; + var lastTweetId = 1548L; + + var dal = new TwitterUserPostgresDal(_settings); + + await dal.CreateTwitterUserAsync(acct, lastTweetId); + var result = await dal.GetTwitterUserAsync(acct); + + + var updatedLastTweetId = 1600L; + var updatedLastSyncId = 1550L; + await dal.UpdateTwitterUserAsync(result.Id, updatedLastTweetId, updatedLastSyncId); + + result = await dal.GetTwitterUserAsync(acct); + + Assert.AreEqual(acct, result.Acct); + Assert.AreEqual(updatedLastTweetId, result.LastTweetPostedId); + Assert.AreEqual(updatedLastSyncId, result.LastTweetSynchronizedForAllFollowersId); + } + + [TestMethod] + public async Task CreateAndDeleteUser() + { + var acct = "myid"; + 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(acct); + result = await dal.GetTwitterUserAsync(acct); + Assert.IsNull(result); + } + + [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); + } + } +} \ No newline at end of file