From 490c68ccc5efc468303dc84f98aef2090b1f5f8d Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Mon, 6 Jul 2020 00:56:26 -0400 Subject: [PATCH] implementation followers DAL + Tests --- .../DataAccessLayers/FollowersPostgresDal.cs | 139 ++++++++++- .../Contracts/IFollowersDal.cs | 13 +- .../BirdsiteLive.DAL/Models/Follower.cs | 10 +- .../FollowersPostgresDalTests.cs | 216 ++++++++++++++++++ 4 files changed, 369 insertions(+), 9 deletions(-) create mode 100644 src/Tests/BirdsiteLive.DAL.Postgres.Tests/DataAccessLayers/FollowersPostgresDalTests.cs 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/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/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/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