IRC parsing, tokenization, and state handling in C#

Add more tests, ran resharper formatting tool

+304 -23
+5 -14
IrcStates/Channel.cs
··· 8 8 { 9 9 public string Name { get; set; } 10 10 public string NameLower { get; set; } 11 - public Dictionary<string, User> Users { get; set; } 11 + public Dictionary<string, ChannelUser> Users { get; set; } 12 12 public string Topic { get; set; } 13 13 public string TopicSetter { get; set; } 14 14 public DateTime TopicTime { get; set; } ··· 23 23 24 24 public void SetName(string name, string nameLower) 25 25 { 26 - Name = name; 26 + Name = name; 27 27 NameLower = nameLower; 28 28 } 29 29 ··· 31 31 { 32 32 if (listMode) 33 33 { 34 - if (!ListModes.ContainsKey(ch)) 35 - { 36 - ListModes[ch] = new List<string>(); 37 - } 34 + if (!ListModes.ContainsKey(ch)) ListModes[ch] = new List<string>(); 38 35 39 - if (ListModes[ch].Contains(param)) 40 - { 41 - ListModes[ch].Add(param ?? string.Empty); 42 - } 36 + if (ListModes[ch].Contains(param)) ListModes[ch].Add(param ?? string.Empty); 43 37 } 44 38 else 45 39 { ··· 54 48 if (ListModes[ch].Contains(param)) 55 49 { 56 50 ListModes[ch].Remove(param); 57 - if (!ListModes[ch].Any()) 58 - { 59 - ListModes.Remove(ch); 60 - } 51 + if (!ListModes[ch].Any()) ListModes.Remove(ch); 61 52 } 62 53 } 63 54 else if (Modes.ContainsKey(ch))
+9
IrcStates/ChannelUser.cs
··· 1 + using System.Collections.Generic; 2 + 3 + namespace IrcStates 4 + { 5 + public class ChannelUser 6 + { 7 + public List<string> Modes { get; set; } 8 + } 9 + }
+1
IrcStates/Emit.cs
··· 9 9 public string Text { get; set; } 10 10 public List<string> Tokens { get; set; } 11 11 public bool Finished { get; set; } 12 + public bool Self { get; set; } 12 13 public bool SelfSource { get; set; } 13 14 public bool SelfTarget { get; set; } 14 15 public Tests.User User { get; set; }
+1
IrcStates/ISupport.cs
··· 1 1 // ReSharper disable InconsistentNaming 2 + 2 3 namespace IrcStates 3 4 { 4 5 public class ISupport
+2
IrcStates/Numeric.cs
··· 28 28 public const string RPL_QUIETLIST = "728"; 29 29 public const string RPL_ENDOFQUIETLIST = "729"; 30 30 31 + public const string RPL_LOGGEDIN = "900"; 32 + public const string RPL_LOGGEDOUT = "901"; 31 33 public const string RPL_SASLSUCCESS = "903"; 32 34 public const string ERR_SASLFAIL = "904"; 33 35 public const string ERR_SASLTOOLONG = "905";
-1
IrcStates/ServerDisconnectedException.cs
··· 4 4 { 5 5 public class ServerDisconnectedException : Exception 6 6 { 7 - 8 7 } 9 8 }
-1
IrcStates/ServerException.cs
··· 4 4 { 5 5 public class ServerException : Exception 6 6 { 7 - 8 7 } 9 8 }
+172 -1
IrcStates/Tests/Channel.cs
··· 1 - using Microsoft.VisualStudio.TestTools.UnitTesting; 1 + using System; 2 + using System.Collections.Generic; 3 + using System.Linq; 4 + using IrcTokens; 5 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 6 3 7 namespace IrcStates.Tests 4 8 { 5 9 [TestClass] 6 10 public class Channel 7 11 { 12 + private Server _server; 13 + 14 + [TestInitialize] 15 + public void TestInitialize() 16 + { 17 + _server = new Server("test"); 18 + _server.ParseTokens(new Line("001 nickname")); 19 + _server.ParseTokens(new Line(":nickname JOIN #chan")); 20 + } 21 + 22 + [TestMethod] 23 + public void JoinSelf() 24 + { 25 + Assert.IsTrue(_server.Channels.ContainsKey("#chan")); 26 + Assert.IsTrue(_server.Users.ContainsKey("nickname")); 27 + Assert.AreEqual(1, _server.Channels.Count); 28 + Assert.AreEqual(1, _server.Users.Count); 29 + 30 + var user = _server.Users["nickname"]; 31 + var chan = _server.Channels["#chan"]; 32 + Assert.IsTrue(chan.Users.ContainsKey(user.NickNameLower)); 33 + var chanUser = chan.Users[user.NickNameLower]; 34 + CollectionAssert.AreEqual(new List<string> {chan.NameLower}, user.Channels.ToList()); 35 + } 36 + 37 + [TestMethod] 38 + public void JoinOther() 39 + { 40 + _server.ParseTokens(new Line(":other JOIN #chan")); 41 + 42 + Assert.AreEqual(2, _server.Users.Count); 43 + Assert.IsTrue(_server.Users.ContainsKey("other")); 44 + 45 + var channel = _server.Channels["#chan"]; 46 + Assert.AreEqual(2, channel.Users.Count); 47 + 48 + var user = _server.Users["other"]; 49 + CollectionAssert.AreEqual(new List<string> {channel.NameLower}, user.Channels.ToList()); 50 + } 51 + 52 + [TestMethod] 53 + public void PartSelf() 54 + { 55 + _server.ParseTokens(new Line(":nickname PART #chan")); 56 + 57 + Assert.AreEqual(0, _server.Users.Count); 58 + Assert.AreEqual(0, _server.Channels.Count); 59 + } 60 + 61 + [TestMethod] 62 + public void PartOther() 63 + { 64 + _server.ParseTokens(new Line(":other JOIN #chan")); 65 + _server.ParseTokens(new Line(":other PART #chan")); 66 + 67 + var user = _server.Users["nickname"]; 68 + var channel = _server.Channels["#chan"]; 69 + var chanUser = channel.Users[user.NickNameLower]; 70 + 71 + Assert.AreEqual(channel.NameLower, user.Channels.Single()); 72 + CollectionAssert.AreEqual(new Dictionary<string, IrcStates.User> {{"nickname", user}}, _server.Users); 73 + CollectionAssert.AreEqual(new Dictionary<string, IrcStates.Channel> {{"#chan", channel}}, _server.Channels); 74 + CollectionAssert.AreEqual(new Dictionary<string, ChannelUser> {{"nickname", chanUser}}, channel.Users); 75 + } 76 + 77 + [TestMethod] 78 + public void KickSelf() 79 + { 80 + _server.ParseTokens(new Line(":nickname KICK #chan nickname")); 81 + 82 + Assert.AreEqual(0, _server.Users.Count); 83 + Assert.AreEqual(0, _server.Channels.Count); 84 + } 85 + 86 + [TestMethod] 87 + public void KickOther() 88 + { 89 + _server.ParseTokens(new Line(":other JOIN #chan")); 90 + _server.ParseTokens(new Line(":nickname KICK #chan other")); 91 + 92 + var user = _server.Users["nickname"]; 93 + var channel = _server.Channels["#chan"]; 94 + var chanUser = channel.Users[user.NickNameLower]; 95 + 96 + Assert.AreEqual(1, _server.Users.Count); 97 + Assert.AreEqual(1, _server.Channels.Count); 98 + Assert.AreEqual(channel.NameLower, user.Channels.Single()); 99 + CollectionAssert.AreEqual(new Dictionary<string, ChannelUser> {{user.NickNameLower, chanUser}}, 100 + channel.Users); 101 + } 102 + 103 + [TestMethod] 104 + public void TopicText() 105 + { 106 + _server.ParseTokens(new Line("332 * #chan :test")); 107 + Assert.AreEqual("test", _server.Channels["#chan"].Topic); 108 + } 109 + 110 + [TestMethod] 111 + public void TopicSetByAt() 112 + { 113 + var dt = DateTimeOffset.FromUnixTimeSeconds(1584023277).DateTime; 114 + _server.ParseTokens(new Line("333 * #chan other 1584023277")); 115 + 116 + var channel = _server.Channels["#chan"]; 117 + 118 + Assert.AreEqual("other", channel.TopicSetter); 119 + Assert.AreEqual(dt, channel.TopicTime); 120 + } 121 + 122 + [TestMethod] 123 + public void TopicCommand() 124 + { 125 + _server.ParseTokens(new Line("TOPIC #chan :hello there")); 126 + Assert.AreEqual("hello there", _server.Channels["#chan"].Topic); 127 + } 128 + 129 + [TestMethod] 130 + public void CreationDate() 131 + { 132 + _server.ParseTokens(new Line("329 * #chan 1584041889")); 133 + Assert.AreEqual(DateTimeOffset.FromUnixTimeSeconds(1584041889).DateTime, _server.Channels["#chan"].Created); 134 + } 135 + 136 + [TestMethod] 137 + public void NamesCommand() 138 + { 139 + _server.ParseTokens(new Line("353 * * #chan :nickname @+other")); 140 + Assert.IsTrue(_server.Users.ContainsKey("nickname")); 141 + Assert.IsTrue(_server.Users.ContainsKey("other")); 142 + 143 + var user = _server.Users["other"]; 144 + var channel = _server.Channels["#chan"]; 145 + var chanUser1 = channel.Users[user.NickNameLower]; 146 + var chanUser2 = channel.Users[_server.NickNameLower]; 147 + 148 + CollectionAssert.AreEqual( 149 + new Dictionary<string, ChannelUser> 150 + { 151 + {user.NickNameLower, chanUser1}, {_server.NickNameLower, chanUser2} 152 + }, channel.Users); 153 + CollectionAssert.AreEqual(new List<string> {"o", "v"}, chanUser1.Modes); 154 + Assert.AreEqual(channel.NameLower, user.Channels.Single()); 155 + } 156 + 157 + [TestMethod] 158 + public void UserhostInNames() 159 + { 160 + _server.ParseTokens(new Line("353 * * #chan :nickname!user@host other@user2@host2")); 161 + Assert.AreEqual("user", _server.UserName); 162 + Assert.AreEqual("host", _server.HostName); 163 + 164 + var user = _server.Users["other"]; 165 + Assert.AreEqual("user2", user.HostName); 166 + Assert.AreEqual("host2", user.HostName); 167 + } 168 + 169 + [TestMethod] 170 + public void NickAfterJoin() 171 + { 172 + var user = _server.Users["nickname"]; 173 + var channel = _server.Channels["#chan"]; 174 + var chanUser = channel.Users[user.NickNameLower]; 175 + _server.ParseTokens(new Line(":nickname NICK nickname2")); 176 + CollectionAssert.AreEqual(new Dictionary<string, ChannelUser> {{user.NickNameLower, chanUser}}, 177 + channel.Users); 178 + } 8 179 } 9 180 }
+109 -1
IrcStates/Tests/Emit.cs
··· 1 - using Microsoft.VisualStudio.TestTools.UnitTesting; 1 + using System.Collections.Generic; 2 + using IrcTokens; 3 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 4 3 5 namespace IrcStates.Tests 4 6 { 5 7 [TestClass] 6 8 public class Emit 7 9 { 10 + private Server _server; 11 + 12 + [TestInitialize] 13 + public void TestInitialize() 14 + { 15 + _server = new Server("test"); 16 + _server.ParseTokens(new Line("001 nickname")); 17 + } 18 + 19 + [TestMethod] 20 + public void EmitJoin() 21 + { 22 + var emit = _server.ParseTokens(new Line(":nickname JOIN #chan")); 23 + 24 + Assert.AreEqual("JOIN", emit.Command); 25 + Assert.IsTrue(emit.Self); 26 + Assert.AreEqual(_server.Users["nickname"], emit.User); 27 + Assert.AreEqual(_server.Channels["#chan"], emit.Channel); 28 + 29 + emit = _server.ParseTokens(new Line(":other JOIN #chan")); 30 + Assert.IsNotNull(emit); 31 + Assert.AreEqual("JOIN", emit.Command); 32 + Assert.IsFalse(emit.Self); 33 + Assert.AreEqual(_server.Users["other"], emit.User); 34 + Assert.AreEqual(_server.Channels["#chan"], emit.Channel); 35 + } 36 + 37 + [TestMethod] 38 + public void EmitPrivmsg() 39 + { 40 + _server.ParseTokens(new Line(":nickname JOIN #chan")); 41 + var emit = _server.ParseTokens(new Line(":nickname PRIVMSG #chan :hello")); 42 + 43 + Assert.IsNotNull(emit); 44 + Assert.AreEqual("PRIVMSG", emit.Command); 45 + Assert.AreEqual("hello", emit.Text); 46 + Assert.IsTrue(emit.SelfSource); 47 + Assert.AreEqual(_server.Users["nickname"], emit.User); 48 + Assert.AreEqual(_server.Channels["#chan"], emit.Channel); 49 + 50 + _server.ParseTokens(new Line(":other JOIN #chan")); 51 + emit = _server.ParseTokens(new Line(":other PRIVMSG #chan :hello2")); 52 + 53 + Assert.IsNotNull(emit); 54 + Assert.AreEqual("PRIVMSG", emit.Command); 55 + Assert.AreEqual("hello2", emit.Text); 56 + Assert.IsFalse(emit.SelfSource); 57 + Assert.AreEqual(_server.Users["other"], emit.User); 58 + Assert.AreEqual(_server.Channels["#chan"], emit.Channel); 59 + } 60 + 61 + [TestMethod] 62 + public void EmitPrivmsgNoJoin() 63 + { 64 + _server.ParseTokens(new Line(":nickname JOIN #chan")); 65 + var emit = _server.ParseTokens(new Line(":other PRIVMSG #chan :hello")); 66 + 67 + Assert.IsNotNull(emit); 68 + Assert.AreEqual("PRIVMSG", emit.Command); 69 + Assert.AreEqual("hello", emit.Text); 70 + Assert.IsFalse(emit.SelfSource); 71 + Assert.IsNotNull(emit.User); 72 + 73 + var channel = _server.Channels["#chan"]; 74 + Assert.AreEqual(channel, emit.Channel); 75 + } 76 + 77 + [TestMethod] 78 + public void EmitKick() 79 + { 80 + _server.ParseTokens(new Line(":nickname JOIN #chan")); 81 + 82 + var user = _server.Users["nickname"]; 83 + var channel = _server.Channels["#chan"]; 84 + _server.ParseTokens(new Line(":other JOIN #chan")); 85 + var userOther = _server.Users["other"]; 86 + var emit = _server.ParseTokens(new Line(":nickname KICK #chan other :reason")); 87 + 88 + Assert.IsNotNull(emit); 89 + Assert.AreEqual("KICK", emit.Command); 90 + Assert.AreEqual("reason", emit.Text); 91 + Assert.IsTrue(emit.SelfSource); 92 + Assert.AreEqual(user, emit.UserSource); 93 + Assert.AreEqual(userOther, emit.UserTarget); 94 + Assert.AreEqual(channel, emit.Channel); 95 + } 96 + 97 + [TestMethod] 98 + public void EmitMode() 99 + { 100 + var emit = _server.ParseTokens(new Line("MODE nickname x+i-i+wi-wi")); 101 + 102 + Assert.IsNotNull(emit); 103 + Assert.AreEqual("MODE", emit.Command); 104 + Assert.IsTrue(emit.SelfTarget); 105 + CollectionAssert.AreEqual(new List<string> 106 + { 107 + "+x", 108 + "+i", 109 + "-i", 110 + "+w", 111 + "+i", 112 + "-w", 113 + "-i" 114 + }, emit.Tokens); 115 + } 8 116 } 9 117 }
+1
IrcStates/Tests/ISupport.cs
··· 1 1 using Microsoft.VisualStudio.TestTools.UnitTesting; 2 + 2 3 // ReSharper disable InconsistentNaming 3 4 4 5 namespace IrcStates.Tests
+1 -1
IrcTokens/Line.cs
··· 200 200 201 201 if (Params != null && Params.Any()) 202 202 { 203 - var last = Params[^1]; 203 + var last = Params[^1]; 204 204 var withoutLast = Params.SkipLast(1).ToList(); 205 205 206 206 foreach (var p in withoutLast)
+2 -2
IrcTokens/StatefulEncoder.cs
··· 60 60 { 61 61 var sent = PendingBytes.Take(byteCount).Count(c => c == '\n'); 62 62 63 - PendingBytes = PendingBytes.Skip(byteCount).ToArray(); 64 - 63 + PendingBytes = PendingBytes.Skip(byteCount).ToArray(); 64 + 65 65 var sentLines = _bufferedLines.Take(sent).ToList(); 66 66 _bufferedLines = _bufferedLines.Skip(sent).ToList(); 67 67
+1 -2
IrcTokens/Tests/Data/JoinModel.cs
··· 9 9 10 10 public class Test 11 11 { 12 - [YamlMember(Alias = "desc")] 13 - public string Description { get; set; } 12 + [YamlMember(Alias = "desc")] public string Description { get; set; } 14 13 15 14 public Atoms Atoms { get; set; } 16 15