about summary refs log tree commit diff
path: root/IRCStates/Tests
diff options
context:
space:
mode:
authorBen Harris <ben@tilde.team>2020-05-14 23:06:10 -0400
committerBen Harris <ben@tilde.team>2020-05-14 23:17:47 -0400
commit21f1e95fb8e935134a969bc3d729964d8d2aadfa (patch)
treedb2be27e9b5ac48e19f92b56cbad68ab59f7099e /IRCStates/Tests
parent304df7805b9925c2edd992fd4177eef80197f807 (diff)
rename Irc to IRC
Diffstat (limited to 'IRCStates/Tests')
-rw-r--r--IRCStates/Tests/Cap.cs131
-rw-r--r--IRCStates/Tests/Casemap.cs58
-rw-r--r--IRCStates/Tests/Channel.cs202
-rw-r--r--IRCStates/Tests/Emit.cs117
-rw-r--r--IRCStates/Tests/ISupport.cs210
-rw-r--r--IRCStates/Tests/Mode.cs179
-rw-r--r--IRCStates/Tests/Motd.cs23
-rw-r--r--IRCStates/Tests/Sasl.cs38
-rw-r--r--IRCStates/Tests/User.cs298
-rw-r--r--IRCStates/Tests/Who.cs61
10 files changed, 1317 insertions, 0 deletions
diff --git a/IRCStates/Tests/Cap.cs b/IRCStates/Tests/Cap.cs
new file mode 100644
index 0000000..3c0faba
--- /dev/null
+++ b/IRCStates/Tests/Cap.cs
@@ -0,0 +1,131 @@
+using System.Collections.Generic;
+using IRCTokens;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IRCStates.Tests
+{
+    [TestClass]
+    public class Cap
+    {
+        private Server _server;
+
+        [TestInitialize]
+        public void TestInitialize()
+        {
+            _server = new Server("test");
+        }
+
+        [TestMethod]
+        public void LSOneLine()
+        {
+            Assert.IsFalse(_server.HasCap);
+            CollectionAssert.AreEqual(new Dictionary<string, string>(), _server.AvailableCaps);
+            _server.Parse(new Line("CAP * LS :a b"));
+            CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}, {"b", ""}}, _server.AvailableCaps);
+        }
+
+        [TestMethod]
+        public void LSTwoLines()
+        {
+            _server.Parse(new Line("CAP * LS * :a b"));
+            CollectionAssert.AreEqual(new Dictionary<string, string>(), _server.AvailableCaps);
+            _server.Parse(new Line("CAP * LS :c"));
+            Assert.IsTrue(_server.AvailableCaps.ContainsKey("a"));
+            Assert.IsTrue(_server.AvailableCaps.ContainsKey("b"));
+            Assert.IsTrue(_server.AvailableCaps.ContainsKey("c"));
+        }
+
+        [TestMethod]
+        public void LSValues()
+        {
+            _server.Parse(new Line("CAP * LS :a b= c=1"));
+            CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}, {"b", ""}, {"c", "1"}},
+                _server.AvailableCaps);
+        }
+
+        [TestMethod]
+        public void ACKOneLine()
+        {
+            _server.Parse(new Line("CAP * LS :a b"));
+            _server.Parse(new Line("CAP * ACK :a b"));
+            CollectionAssert.AreEqual(new List<string> {"a", "b"}, _server.AgreedCaps);
+        }
+
+        [TestMethod]
+        public void ACKTwoLines()
+        {
+            _server.Parse(new Line("CAP * LS :a b c"));
+            _server.Parse(new Line("CAP * ACK * :a b"));
+            _server.Parse(new Line("CAP * ACK :c"));
+            CollectionAssert.AreEqual(new List<string> {"a", "b", "c"}, _server.AgreedCaps);
+        }
+
+        [TestMethod]
+        public void ACKNotLS()
+        {
+            _server.Parse(new Line("CAP * LS a"));
+            _server.Parse(new Line("CAP * ACK b"));
+            CollectionAssert.AreEqual(new List<string>(), _server.AgreedCaps);
+        }
+
+        [TestMethod]
+        public void NEWNoLS()
+        {
+            _server.Parse(new Line("CAP * NEW :a"));
+            CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}}, _server.AvailableCaps);
+        }
+
+        [TestMethod]
+        public void NEWOneLine()
+        {
+            _server.Parse(new Line("CAP * LS :a"));
+            _server.Parse(new Line("CAP * NEW :b"));
+            CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}, {"b", ""}}, _server.AvailableCaps);
+        }
+
+        [TestMethod]
+        public void NEWTwoLines()
+        {
+            _server.Parse(new Line("CAP * LS :a"));
+            _server.Parse(new Line("CAP * NEW :b c"));
+            CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}, {"b", ""}, {"c", ""}},
+                _server.AvailableCaps);
+        }
+
+        [TestMethod]
+        public void DELNotAcked()
+        {
+            _server.Parse(new Line("CAP * DEL a"));
+        }
+
+        [TestMethod]
+        public void DELOneLS()
+        {
+            _server.Parse(new Line("CAP * LS :a"));
+            _server.Parse(new Line("CAP * ACK :a"));
+            _server.Parse(new Line("CAP * DEL :a"));
+            CollectionAssert.AreEqual(new Dictionary<string, string>(), _server.AvailableCaps);
+            CollectionAssert.AreEqual(new List<string>(), _server.AgreedCaps);
+        }
+
+        [TestMethod]
+        public void DELTwoLS()
+        {
+            _server.Parse(new Line("CAP * LS :a b"));
+            _server.Parse(new Line("CAP * ACK :a b"));
+            _server.Parse(new Line("CAP * DEL :a"));
+            CollectionAssert.AreEqual(new Dictionary<string, string> {{"b", ""}}, _server.AvailableCaps);
+            CollectionAssert.AreEqual(new List<string> {"b"}, _server.AgreedCaps);
+        }
+
+        [TestMethod]
+        public void DELTwoDEL()
+        {
+            _server.Parse(new Line("CAP * LS :a b"));
+            _server.Parse(new Line("CAP * ACK :a b"));
+            _server.Parse(new Line("CAP * DEL :a b"));
+            CollectionAssert.AreEqual(new Dictionary<string, string>(), _server.AvailableCaps);
+            CollectionAssert.AreEqual(new List<string>(), _server.AgreedCaps);
+        }
+    }
+}
diff --git a/IRCStates/Tests/Casemap.cs b/IRCStates/Tests/Casemap.cs
new file mode 100644
index 0000000..4a02444
--- /dev/null
+++ b/IRCStates/Tests/Casemap.cs
@@ -0,0 +1,58 @@
+using IRCTokens;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IRCStates.Tests
+{
+    [TestClass]
+    public class Casemap
+    {
+        [TestMethod]
+        public void Rfc1459()
+        {
+            var lower = IRCStates.Casemap.CaseFold(IRCStates.Casemap.CaseMapping.Rfc1459, @"ÀTEST[]~\");
+            Assert.AreEqual("Àtest{}^|", lower);
+        }
+
+        [TestMethod]
+        public void Ascii()
+        {
+            var lower = IRCStates.Casemap.CaseFold(IRCStates.Casemap.CaseMapping.Ascii, @"ÀTEST[]~\");
+            Assert.AreEqual(@"Àtest[]~\", lower);
+        }
+
+        [TestMethod]
+        public void CommandJoin()
+        {
+            var server = new Server("test");
+            server.Parse(new Line("001 nickname"));
+            server.Parse(new Line(":Nickname JOIN #Chan"));
+            server.Parse(new Line(":Other JOIN #Chan"));
+
+            Assert.IsTrue(server.Users.ContainsKey("nickname"));
+            Assert.IsFalse(server.Users.ContainsKey("Nickname"));
+            Assert.IsTrue(server.Users.ContainsKey("other"));
+            Assert.IsFalse(server.Users.ContainsKey("Other"));
+            Assert.IsTrue(server.Channels.ContainsKey("#chan"));
+            Assert.IsFalse(server.Channels.ContainsKey("#Chan"));
+
+            var channel = server.Channels["#chan"];
+            Assert.AreEqual("#Chan", channel.Name);
+        }
+
+        [TestMethod]
+        public void CommandNick()
+        {
+            var server = new Server("test");
+            server.Parse(new Line("001 nickname"));
+            server.Parse(new Line(":nickname JOIN #chan"));
+            var user = server.Users["nickname"];
+            server.Parse(new Line(":nickname NICK NewNickname"));
+            Assert.AreEqual(1, server.Users.Count);
+            Assert.IsTrue(server.Users.ContainsKey("newnickname"));
+            Assert.AreEqual("NewNickname", user.NickName);
+            Assert.AreEqual("newnickname", user.NickNameLower);
+            Assert.AreEqual("NewNickname", server.NickName);
+            Assert.AreEqual("newnickname", server.NickNameLower);
+        }
+    }
+}
diff --git a/IRCStates/Tests/Channel.cs b/IRCStates/Tests/Channel.cs
new file mode 100644
index 0000000..6868e0f
--- /dev/null
+++ b/IRCStates/Tests/Channel.cs
@@ -0,0 +1,202 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using IRCTokens;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IRCStates.Tests
+{
+    [TestClass]
+    public class Channel
+    {
+        private Server _server;
+
+        [TestInitialize]
+        public void TestInitialize()
+        {
+            _server = new Server("test");
+            _server.Parse(new Line("001 nickname"));
+            _server.Parse(new Line(":nickname JOIN #chan"));
+        }
+
+        [TestMethod]
+        public void JoinSelf()
+        {
+            Assert.IsTrue(_server.Channels.ContainsKey("#chan"));
+            Assert.IsTrue(_server.Users.ContainsKey("nickname"));
+            Assert.AreEqual(1, _server.Channels.Count);
+            Assert.AreEqual(1, _server.Users.Count);
+
+            var user = _server.Users["nickname"];
+            var chan = _server.Channels["#chan"];
+            Assert.IsTrue(chan.Users.ContainsKey(user.NickNameLower));
+            var chanUser = chan.Users[user.NickNameLower];
+            CollectionAssert.AreEqual(new List<string> {chan.NameLower}, user.Channels.ToList());
+        }
+
+        [TestMethod]
+        public void JoinOther()
+        {
+            _server.Parse(new Line(":other JOIN #chan"));
+
+            Assert.AreEqual(2, _server.Users.Count);
+            Assert.IsTrue(_server.Users.ContainsKey("other"));
+
+            var channel = _server.Channels["#chan"];
+            Assert.AreEqual(2, channel.Users.Count);
+
+            var user = _server.Users["other"];
+            CollectionAssert.AreEqual(new List<string> {channel.NameLower}, user.Channels.ToList());
+        }
+
+        [TestMethod]
+        public void PartSelf()
+        {
+            _server.Parse(new Line(":nickname PART #chan"));
+
+            Assert.AreEqual(0, _server.Users.Count);
+            Assert.AreEqual(0, _server.Channels.Count);
+        }
+
+        [TestMethod]
+        public void PartOther()
+        {
+            _server.Parse(new Line(":other JOIN #chan"));
+            _server.Parse(new Line(":other PART #chan"));
+
+            var user     = _server.Users["nickname"];
+            var channel  = _server.Channels["#chan"];
+            var chanUser = channel.Users[user.NickNameLower];
+
+            Assert.AreEqual(channel.NameLower, user.Channels.Single());
+            CollectionAssert.AreEqual(new Dictionary<string, IRCStates.User> {{"nickname", user}}, _server.Users);
+            CollectionAssert.AreEqual(new Dictionary<string, IRCStates.Channel> {{"#chan", channel}}, _server.Channels);
+            CollectionAssert.AreEqual(new Dictionary<string, ChannelUser> {{"nickname", chanUser}}, channel.Users);
+        }
+
+        [TestMethod]
+        public void KickSelf()
+        {
+            _server.Parse(new Line(":nickname KICK #chan nickname"));
+
+            Assert.AreEqual(0, _server.Users.Count);
+            Assert.AreEqual(0, _server.Channels.Count);
+        }
+
+        [TestMethod]
+        public void KickOther()
+        {
+            _server.Parse(new Line(":other JOIN #chan"));
+            _server.Parse(new Line(":nickname KICK #chan other"));
+
+            var user     = _server.Users["nickname"];
+            var channel  = _server.Channels["#chan"];
+            var chanUser = channel.Users[user.NickNameLower];
+
+            Assert.AreEqual(1, _server.Users.Count);
+            Assert.AreEqual(1, _server.Channels.Count);
+            Assert.AreEqual(channel.NameLower, user.Channels.Single());
+            CollectionAssert.AreEqual(new Dictionary<string, ChannelUser> {{user.NickNameLower, chanUser}},
+                channel.Users);
+        }
+
+        [TestMethod]
+        public void QuitSelf()
+        {
+            _server.Parse(new Line("QUIT :i'm outta here"));
+            Assert.IsFalse(_server.Users.Any());
+            Assert.IsFalse(_server.Channels.Any());
+        }
+
+        [TestMethod]
+        public void QuitSelfWithSource()
+        {
+            _server.Parse(new Line(":nickname QUIT :i'm outta here"));
+            Assert.IsFalse(_server.Users.Any());
+            Assert.IsFalse(_server.Channels.Any());
+        }
+
+        [TestMethod]
+        public void QuitOther()
+        {
+            _server.Parse(new Line(":other JOIN #chan"));
+            _server.Parse(new Line(":other QUIT :see ya"));
+            Assert.IsFalse(_server.Users.ContainsKey("other"));
+        }
+
+        [TestMethod]
+        public void TopicText()
+        {
+            _server.Parse(new Line("332 * #chan :test"));
+            Assert.AreEqual("test", _server.Channels["#chan"].Topic);
+        }
+
+        [TestMethod]
+        public void TopicSetByAt()
+        {
+            var dt = DateTimeOffset.FromUnixTimeSeconds(1584023277).DateTime;
+            _server.Parse(new Line("333 * #chan other 1584023277"));
+
+            var channel = _server.Channels["#chan"];
+
+            Assert.AreEqual("other", channel.TopicSetter);
+            Assert.AreEqual(dt, channel.TopicTime);
+        }
+
+        [TestMethod]
+        public void TopicCommand()
+        {
+            _server.Parse(new Line("TOPIC #chan :hello there"));
+            Assert.AreEqual("hello there", _server.Channels["#chan"].Topic);
+        }
+
+        [TestMethod]
+        public void CreationDate()
+        {
+            _server.Parse(new Line("329 * #chan 1584041889"));
+            Assert.AreEqual(DateTimeOffset.FromUnixTimeSeconds(1584041889).DateTime, _server.Channels["#chan"].Created);
+        }
+
+        [TestMethod]
+        public void NamesCommand()
+        {
+            _server.Parse(new Line("353 * * #chan :nickname @+other"));
+            Assert.IsTrue(_server.Users.ContainsKey("nickname"));
+            Assert.IsTrue(_server.Users.ContainsKey("other"));
+
+            var user      = _server.Users["other"];
+            var channel   = _server.Channels["#chan"];
+            var chanUser1 = channel.Users[user.NickNameLower];
+            var chanUser2 = channel.Users[_server.NickNameLower];
+
+            Assert.AreEqual(2, channel.Users.Count);
+            CollectionAssert.AreEqual(chanUser1.Modes, channel.Users[user.NickNameLower].Modes);
+            CollectionAssert.AreEqual(chanUser2.Modes, channel.Users[_server.NickNameLower].Modes);
+            CollectionAssert.AreEqual(new List<string> {"o", "v"}, chanUser1.Modes);
+            Assert.AreEqual(channel.NameLower, user.Channels.Single());
+        }
+
+        [TestMethod]
+        public void UserhostInNames()
+        {
+            _server.Parse(new Line("353 * * #chan :nickname!user@host other!user2@host2"));
+            Assert.AreEqual("user", _server.UserName);
+            Assert.AreEqual("host", _server.HostName);
+
+            var user = _server.Users["other"];
+            Assert.AreEqual("user2", user.UserName);
+            Assert.AreEqual("host2", user.HostName);
+        }
+
+        [TestMethod]
+        public void NickAfterJoin()
+        {
+            var user     = _server.Users["nickname"];
+            var channel  = _server.Channels["#chan"];
+            var chanUser = channel.Users[user.NickNameLower];
+            _server.Parse(new Line(":nickname NICK nickname2"));
+            CollectionAssert.AreEqual(new Dictionary<string, ChannelUser> {{user.NickNameLower, chanUser}},
+                channel.Users);
+        }
+    }
+}
diff --git a/IRCStates/Tests/Emit.cs b/IRCStates/Tests/Emit.cs
new file mode 100644
index 0000000..07fea8c
--- /dev/null
+++ b/IRCStates/Tests/Emit.cs
@@ -0,0 +1,117 @@
+using System.Collections.Generic;
+using IRCTokens;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IRCStates.Tests
+{
+    [TestClass]
+    public class Emit
+    {
+        private Server _server;
+
+        [TestInitialize]
+        public void TestInitialize()
+        {
+            _server = new Server("test");
+            _server.Parse(new Line("001 nickname"));
+        }
+
+        [TestMethod]
+        public void EmitJoin()
+        {
+            var emit = _server.Parse(new Line(":nickname JOIN #chan"));
+
+            Assert.AreEqual("JOIN", emit.Command);
+            Assert.IsTrue(emit.Self);
+            Assert.AreEqual(_server.Users["nickname"], emit.User);
+            Assert.AreEqual(_server.Channels["#chan"], emit.Channel);
+
+            emit = _server.Parse(new Line(":other JOIN #chan"));
+            Assert.IsNotNull(emit);
+            Assert.AreEqual("JOIN", emit.Command);
+            Assert.IsFalse(emit.Self);
+            Assert.AreEqual(_server.Users["other"], emit.User);
+            Assert.AreEqual(_server.Channels["#chan"], emit.Channel);
+        }
+
+        [TestMethod]
+        public void EmitPrivmsg()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            var emit = _server.Parse(new Line(":nickname PRIVMSG #chan :hello"));
+
+            Assert.IsNotNull(emit);
+            Assert.AreEqual("PRIVMSG", emit.Command);
+            Assert.AreEqual("hello", emit.Text);
+            Assert.IsTrue(emit.SelfSource);
+            Assert.AreEqual(_server.Users["nickname"], emit.User);
+            Assert.AreEqual(_server.Channels["#chan"], emit.Channel);
+
+            _server.Parse(new Line(":other JOIN #chan"));
+            emit = _server.Parse(new Line(":other PRIVMSG #chan :hello2"));
+
+            Assert.IsNotNull(emit);
+            Assert.AreEqual("PRIVMSG", emit.Command);
+            Assert.AreEqual("hello2", emit.Text);
+            Assert.IsFalse(emit.SelfSource);
+            Assert.AreEqual(_server.Users["other"], emit.User);
+            Assert.AreEqual(_server.Channels["#chan"], emit.Channel);
+        }
+
+        [TestMethod]
+        public void EmitPrivmsgNoJoin()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            var emit = _server.Parse(new Line(":other PRIVMSG #chan :hello"));
+
+            Assert.IsNotNull(emit);
+            Assert.AreEqual("PRIVMSG", emit.Command);
+            Assert.AreEqual("hello", emit.Text);
+            Assert.IsFalse(emit.SelfSource);
+            Assert.IsNotNull(emit.User);
+
+            var channel = _server.Channels["#chan"];
+            Assert.AreEqual(channel, emit.Channel);
+        }
+
+        [TestMethod]
+        public void EmitKick()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+
+            var user    = _server.Users["nickname"];
+            var channel = _server.Channels["#chan"];
+            _server.Parse(new Line(":other JOIN #chan"));
+            var userOther = _server.Users["other"];
+            var emit      = _server.Parse(new Line(":nickname KICK #chan other :reason"));
+
+            Assert.IsNotNull(emit);
+            Assert.AreEqual("KICK", emit.Command);
+            Assert.AreEqual("reason", emit.Text);
+            Assert.IsTrue(emit.SelfSource);
+            Assert.AreEqual(user, emit.UserSource);
+            Assert.AreEqual(userOther, emit.UserTarget);
+            Assert.AreEqual(channel, emit.Channel);
+        }
+
+        [TestMethod]
+        public void EmitMode()
+        {
+            var emit = _server.Parse(new Line("MODE nickname x+i-i+wi-wi"));
+
+            Assert.IsNotNull(emit);
+            Assert.AreEqual("MODE", emit.Command);
+            Assert.IsTrue(emit.SelfTarget);
+            CollectionAssert.AreEqual(new List<string>
+            {
+                "+x",
+                "+i",
+                "-i",
+                "+w",
+                "+i",
+                "-w",
+                "-i"
+            }, emit.Tokens);
+        }
+    }
+}
diff --git a/IRCStates/Tests/ISupport.cs b/IRCStates/Tests/ISupport.cs
new file mode 100644
index 0000000..5cdcc61
--- /dev/null
+++ b/IRCStates/Tests/ISupport.cs
@@ -0,0 +1,210 @@
+using System.Collections.Generic;
+using IRCTokens;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+// ReSharper disable InconsistentNaming
+
+namespace IRCStates.Tests
+{
+    [TestClass]
+    public class ISupport
+    {
+        private Server _server;
+
+        [TestInitialize]
+        public void TestInitialize()
+        {
+            _server = new Server("test");
+            _server.Parse(new Line("001 nickname"));
+        }
+
+        [TestMethod]
+        public void ChanModes()
+        {
+            CollectionAssert.AreEqual(new List<string> {"b"}, _server.ISupport.ChanModes.ListModes);
+            CollectionAssert.AreEqual(new List<string> {"k"}, _server.ISupport.ChanModes.SettingBModes);
+            CollectionAssert.AreEqual(new List<string> {"l"}, _server.ISupport.ChanModes.SettingCModes);
+            CollectionAssert.AreEqual(new List<string>
+            {
+                "i",
+                "m",
+                "n",
+                "p",
+                "s",
+                "t"
+            }, _server.ISupport.ChanModes.SettingDModes);
+
+            _server.Parse(new Line("005 * CHANMODES=a,b,c,d *"));
+
+            CollectionAssert.AreEqual(new List<string> {"a"}, _server.ISupport.ChanModes.ListModes);
+            CollectionAssert.AreEqual(new List<string> {"b"}, _server.ISupport.ChanModes.SettingBModes);
+            CollectionAssert.AreEqual(new List<string> {"c"}, _server.ISupport.ChanModes.SettingCModes);
+            CollectionAssert.AreEqual(new List<string> {"d"}, _server.ISupport.ChanModes.SettingDModes);
+        }
+
+        [TestMethod]
+        public void Prefix()
+        {
+            CollectionAssert.AreEqual(new List<string> {"o", "v"}, _server.ISupport.Prefix.Modes);
+            CollectionAssert.AreEqual(new List<string> {"@", "+"}, _server.ISupport.Prefix.Prefixes);
+
+            Assert.AreEqual("@", _server.ISupport.Prefix.FromMode("o"));
+            Assert.IsNull(_server.ISupport.Prefix.FromMode("a"));
+            Assert.AreEqual("o", _server.ISupport.Prefix.FromPrefix("@"));
+            Assert.IsNull(_server.ISupport.Prefix.FromPrefix("&"));
+
+            _server.Parse(new Line("005 * PREFIX=(qaohv)~&@%+ *"));
+            CollectionAssert.AreEqual(new List<string>
+            {
+                "q",
+                "a",
+                "o",
+                "h",
+                "v"
+            }, _server.ISupport.Prefix.Modes);
+            CollectionAssert.AreEqual(new List<string>
+            {
+                "~",
+                "&",
+                "@",
+                "%",
+                "+"
+            }, _server.ISupport.Prefix.Prefixes);
+            Assert.AreEqual("&", _server.ISupport.Prefix.FromMode("a"));
+            Assert.AreEqual("a", _server.ISupport.Prefix.FromPrefix("&"));
+        }
+
+        [TestMethod]
+        public void ChanTypes()
+        {
+            CollectionAssert.AreEqual(new List<string> {"#"}, _server.ISupport.ChanTypes);
+            _server.Parse(new Line("005 * CHANTYPES=#& *"));
+            CollectionAssert.AreEqual(new List<string> {"#", "&"}, _server.ISupport.ChanTypes);
+        }
+
+        [TestMethod]
+        public void Modes()
+        {
+            Assert.AreEqual(3, _server.ISupport.Modes);
+
+            _server.Parse(new Line("005 * MODES *"));
+            Assert.AreEqual(-1, _server.ISupport.Modes);
+
+            _server.Parse(new Line("005 * MODES=5 *"));
+            Assert.AreEqual(5, _server.ISupport.Modes);
+        }
+
+        [TestMethod]
+        public void Rfc1459()
+        {
+            Assert.AreEqual(IRCStates.Casemap.CaseMapping.Rfc1459, _server.ISupport.CaseMapping);
+            _server.Parse(new Line("005 * CASEMAPPING=rfc1459 *"));
+            Assert.AreEqual(IRCStates.Casemap.CaseMapping.Rfc1459, _server.ISupport.CaseMapping);
+            var lower = _server.CaseFold(@"ÀTEST[]~\");
+            Assert.AreEqual("Àtest{}^|", lower);
+        }
+
+        [TestMethod]
+        public void Ascii()
+        {
+            _server.Parse(new Line("005 * CASEMAPPING=ascii *"));
+            Assert.AreEqual(IRCStates.Casemap.CaseMapping.Ascii, _server.ISupport.CaseMapping);
+            var lower = _server.CaseFold(@"ÀTEST[]~\");
+            Assert.AreEqual(@"Àtest[]~\", lower);
+        }
+
+        [TestMethod]
+        public void FallbackToRfc1459()
+        {
+            _server.Parse(new Line("005 * CASEMAPPING=nonexistent *"));
+            Assert.AreEqual(IRCStates.Casemap.CaseMapping.Rfc1459, _server.ISupport.CaseMapping);
+            var lower = _server.CaseFold(@"ÀTEST[]~\");
+            Assert.AreEqual("Àtest{}^|", lower);
+        }
+
+        [TestMethod]
+        public void Network()
+        {
+            Assert.IsNull(_server.ISupport.Network);
+            _server.Parse(new Line("005 * NETWORK=testnet *"));
+            Assert.AreEqual("testnet", _server.ISupport.Network);
+        }
+
+        [TestMethod]
+        public void StatusMsg()
+        {
+            CollectionAssert.AreEqual(new List<string>(), _server.ISupport.StatusMsg);
+            _server.Parse(new Line("005 * STATUSMSG=&@ *"));
+            CollectionAssert.AreEqual(new List<string> {"&", "@"}, _server.ISupport.StatusMsg);
+        }
+
+        [TestMethod]
+        public void CallerId()
+        {
+            Assert.IsNull(_server.ISupport.CallerId);
+
+            _server.Parse(new Line("005 * CALLERID=U *"));
+            Assert.AreEqual("U", _server.ISupport.CallerId);
+
+            _server.Parse(new Line("005 * CALLERID *"));
+            Assert.AreEqual("g", _server.ISupport.CallerId);
+        }
+
+        [TestMethod]
+        public void Excepts()
+        {
+            Assert.IsNull(_server.ISupport.Excepts);
+
+            _server.Parse(new Line("005 * EXCEPTS=U *"));
+            Assert.AreEqual("U", _server.ISupport.Excepts);
+
+            _server.Parse(new Line("005 * EXCEPTS *"));
+            Assert.AreEqual("e", _server.ISupport.Excepts);
+        }
+
+        [TestMethod]
+        public void Invex()
+        {
+            Assert.IsNull(_server.ISupport.Invex);
+
+            _server.Parse(new Line("005 * INVEX=U *"));
+            Assert.AreEqual("U", _server.ISupport.Invex);
+
+            _server.Parse(new Line("005 * INVEX *"));
+            Assert.AreEqual("I", _server.ISupport.Invex);
+        }
+
+        [TestMethod]
+        public void Whox()
+        {
+            Assert.IsFalse(_server.ISupport.Whox);
+
+            _server.Parse(new Line("005 * WHOX *"));
+            Assert.IsTrue(_server.ISupport.Whox);
+        }
+
+        [TestMethod]
+        public void Monitor()
+        {
+            Assert.IsNull(_server.ISupport.Monitor);
+
+            _server.Parse(new Line("005 * MONITOR=123 *"));
+            Assert.AreEqual(123, _server.ISupport.Monitor);
+
+            _server.Parse(new Line("005 * MONITOR *"));
+            Assert.AreEqual(-1, _server.ISupport.Monitor);
+        }
+
+        [TestMethod]
+        public void Watch()
+        {
+            Assert.IsNull(_server.ISupport.Watch);
+
+            _server.Parse(new Line("005 * WATCH=123 *"));
+            Assert.AreEqual(123, _server.ISupport.Watch);
+
+            _server.Parse(new Line("005 * WATCH *"));
+            Assert.AreEqual(-1, _server.ISupport.Watch);
+        }
+    }
+}
diff --git a/IRCStates/Tests/Mode.cs b/IRCStates/Tests/Mode.cs
new file mode 100644
index 0000000..90763fa
--- /dev/null
+++ b/IRCStates/Tests/Mode.cs
@@ -0,0 +1,179 @@
+using System.Collections.Generic;
+using IRCTokens;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IRCStates.Tests
+{
+    [TestClass]
+    public class Mode
+    {
+        private Server _server;
+
+        [TestInitialize]
+        public void TestInitialize()
+        {
+            _server = new Server("test");
+            _server.Parse(new Line("001 nickname"));
+        }
+
+        [TestMethod]
+        public void UModeAdd()
+        {
+            _server.Parse(new Line("MODE nickname +i"));
+            CollectionAssert.AreEqual(new List<string> {"i"}, _server.Modes);
+        }
+
+        [TestMethod]
+        public void UModeRemove()
+        {
+            _server.Parse(new Line("MODE nickname +i"));
+            _server.Parse(new Line("MODE nickname -i"));
+            CollectionAssert.AreEqual(new List<string>(), _server.Modes);
+        }
+
+        [TestMethod]
+        public void PrefixAdd()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line("MODE #chan +ov nickname nickname"));
+
+            var user        = _server.Users["nickname"];
+            var channel     = _server.Channels["#chan"];
+            var channelUser = channel.Users[user.NickNameLower];
+            CollectionAssert.AreEqual(new List<string> {"o", "v"}, channelUser.Modes);
+        }
+
+        [TestMethod]
+        public void PrefixRemove()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line("MODE #chan +ov nickname nickname"));
+            _server.Parse(new Line("MODE #chan -ov nickname nickname"));
+
+            var user        = _server.Users["nickname"];
+            var channel     = _server.Channels["#chan"];
+            var channelUser = channel.Users[user.NickNameLower];
+            CollectionAssert.AreEqual(new List<string>(), channelUser.Modes);
+        }
+
+        [TestMethod]
+        public void ChannelListAdd()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line("MODE #chan +b asd!*@*"));
+
+            var channel = _server.Channels["#chan"];
+            CollectionAssert.AreEqual(new List<string> {"asd!*@*"}, channel.ListModes["b"]);
+        }
+
+        [TestMethod]
+        public void ChannelListRemove()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line("MODE #chan +b asd!*@*"));
+            _server.Parse(new Line("MODE #chan -b asd!*@*"));
+
+            var channel = _server.Channels["#chan"];
+            CollectionAssert.AreEqual(new Dictionary<string, List<string>>(), channel.ListModes);
+        }
+
+        [TestMethod]
+        public void ChannelTypeBAdd()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line("MODE #chan +k password"));
+
+            var channel = _server.Channels["#chan"];
+            CollectionAssert.AreEqual(new Dictionary<string, string> {{"k", "password"}}, channel.Modes);
+        }
+
+        [TestMethod]
+        public void ChannelTypeBRemove()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line("MODE #chan +k password"));
+            _server.Parse(new Line("MODE #chan -k *"));
+
+            var channel = _server.Channels["#chan"];
+            CollectionAssert.AreEqual(new Dictionary<string, string>(), channel.Modes);
+        }
+
+        [TestMethod]
+        public void ChannelTypeCAdd()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line("MODE #chan +l 100"));
+
+            var channel = _server.Channels["#chan"];
+            CollectionAssert.AreEqual(new Dictionary<string, string> {{"l", "100"}}, channel.Modes);
+        }
+
+        [TestMethod]
+        public void ChannelTypeCRemove()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line("MODE #chan +l 100"));
+            _server.Parse(new Line("MODE #chan -l"));
+
+            var channel = _server.Channels["#chan"];
+            CollectionAssert.AreEqual(new Dictionary<string, string>(), channel.Modes);
+        }
+
+        [TestMethod]
+        public void ChannelTypeDAdd()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line("MODE #chan +i"));
+
+            var channel = _server.Channels["#chan"];
+            CollectionAssert.AreEqual(new Dictionary<string, string> {{"i", null}}, channel.Modes);
+        }
+
+        [TestMethod]
+        public void ChannelTypeDRemove()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line("MODE #chan +i"));
+            _server.Parse(new Line("MODE #chan -i"));
+
+            var channel = _server.Channels["#chan"];
+            CollectionAssert.AreEqual(new Dictionary<string, string>(), channel.Modes);
+        }
+
+        [TestMethod]
+        public void ChannelNumeric()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line("324 * #chan +bkli *!*@* pass 10"));
+
+            var channel = _server.Channels["#chan"];
+            CollectionAssert.AreEqual(new Dictionary<string, string> {{"k", "pass"}, {"l", "10"}, {"i", null}},
+                channel.Modes);
+            CollectionAssert.AreEqual(new List<string> {"*!*@*"}, channel.ListModes["b"]);
+        }
+
+        [TestMethod]
+        public void ChannelNumericWithoutPlus()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line("324 * #chan il 10"));
+
+            var channel = _server.Channels["#chan"];
+            CollectionAssert.AreEqual(new Dictionary<string, string> {{"i", null}, {"l", "10"}}, channel.Modes);
+        }
+
+        [TestMethod]
+        public void UserNumeric()
+        {
+            _server.Parse(new Line("221 * +iw"));
+            CollectionAssert.AreEqual(new List<string> {"i", "w"}, _server.Modes);
+        }
+
+        [TestMethod]
+        public void UserNumericWithoutPlus()
+        {
+            _server.Parse(new Line("221 * iw"));
+            CollectionAssert.AreEqual(new List<string> {"i", "w"}, _server.Modes);
+        }
+    }
+}
diff --git a/IRCStates/Tests/Motd.cs b/IRCStates/Tests/Motd.cs
new file mode 100644
index 0000000..2d75982
--- /dev/null
+++ b/IRCStates/Tests/Motd.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using IRCTokens;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IRCStates.Tests
+{
+    [TestClass]
+    public class Motd
+    {
+        [TestMethod]
+        public void MessageOfTheDay()
+        {
+            var server = new Server("test");
+            server.Parse(new Line("001 nickname"));
+            server.Parse(new Line("375 * :start of motd"));
+            server.Parse(new Line("372 * :first line of motd"));
+            server.Parse(new Line("372 * :second line of motd"));
+
+            CollectionAssert.AreEqual(new List<string> {"start of motd", "first line of motd", "second line of motd"},
+                server.Motd);
+        }
+    }
+}
diff --git a/IRCStates/Tests/Sasl.cs b/IRCStates/Tests/Sasl.cs
new file mode 100644
index 0000000..151ccdf
--- /dev/null
+++ b/IRCStates/Tests/Sasl.cs
@@ -0,0 +1,38 @@
+using IRCTokens;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IRCStates.Tests
+{
+    [TestClass]
+    public class Sasl
+    {
+        private Server _server;
+
+        [TestInitialize]
+        public void TestInitialize()
+        {
+            _server = new Server("test");
+            _server.Parse(new Line("900 * nick!user@host account"));
+        }
+
+        [TestMethod]
+        public void LoggedIn()
+        {
+            Assert.AreEqual("nick", _server.NickName);
+            Assert.AreEqual("user", _server.UserName);
+            Assert.AreEqual("host", _server.HostName);
+            Assert.AreEqual("account", _server.Account);
+        }
+
+        [TestMethod]
+        public void LoggedOut()
+        {
+            _server.Parse(new Line("901 * nick1!user1@host1"));
+
+            Assert.AreEqual("nick1", _server.NickName);
+            Assert.AreEqual("user1", _server.UserName);
+            Assert.AreEqual("host1", _server.HostName);
+            Assert.IsTrue(string.IsNullOrEmpty(_server.Account));
+        }
+    }
+}
diff --git a/IRCStates/Tests/User.cs b/IRCStates/Tests/User.cs
new file mode 100644
index 0000000..61d7157
--- /dev/null
+++ b/IRCStates/Tests/User.cs
@@ -0,0 +1,298 @@
+using IRCTokens;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IRCStates.Tests
+{
+    [TestClass]
+    public class User
+    {
+        private Server _server;
+
+        [TestInitialize]
+        public void TestInitialize()
+        {
+            _server = new Server("test");
+            _server.Parse(new Line("001 nickname"));
+        }
+
+        [TestMethod]
+        public void Welcome()
+        {
+            Assert.AreEqual("test", _server.Name);
+            Assert.AreEqual("nickname", _server.NickName);
+        }
+
+        [TestMethod]
+        public void NicknameChange()
+        {
+            _server.Parse(new Line(":nickname NICK nickname2"));
+            Assert.AreEqual("nickname2", _server.NickName);
+
+            _server.Parse(new Line(":nickname2 JOIN #chan"));
+            _server.Parse(new Line(":other JOIN #chan"));
+            Assert.IsTrue(_server.Users.ContainsKey("other"));
+
+            _server.Parse(new Line(":other NICK other2"));
+            Assert.IsFalse(_server.Users.ContainsKey("other"));
+            Assert.IsTrue(_server.Users.ContainsKey("other2"));
+        }
+
+        [TestMethod]
+        public void HostmaskJoinBoth()
+        {
+            _server.Parse(new Line(":nickname!user@host JOIN #chan"));
+            Assert.AreEqual("user", _server.UserName);
+            Assert.AreEqual("host", _server.HostName);
+
+            _server.Parse(new Line(":other!user@host JOIN #chan"));
+            var user = _server.Users["other"];
+            Assert.AreEqual("user", user.UserName);
+            Assert.AreEqual("host", user.HostName);
+        }
+
+        [TestMethod]
+        public void HostmaskJoinUser()
+        {
+            _server.Parse(new Line(":nickname!user JOIN #chan"));
+            Assert.AreEqual("user", _server.UserName);
+            Assert.IsNull(_server.HostName);
+
+            _server.Parse(new Line(":other!user JOIN #chan"));
+            var user = _server.Users["other"];
+            Assert.AreEqual("user", user.UserName);
+            Assert.IsNull(user.HostName);
+        }
+
+        [TestMethod]
+        public void HostmaskJoinHost()
+        {
+            _server.Parse(new Line(":nickname@host JOIN #chan"));
+            Assert.IsNull(_server.UserName);
+            Assert.AreEqual("host", _server.HostName);
+
+            _server.Parse(new Line(":other@host JOIN #chan"));
+            var user = _server.Users["other"];
+            Assert.IsNull(user.UserName);
+            Assert.AreEqual("host", user.HostName);
+        }
+
+        [TestMethod]
+        public void ExtendedJoinWithoutExtendedJoin()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            Assert.IsNull(_server.Account);
+            Assert.IsNull(_server.RealName);
+
+            _server.Parse(new Line(":other JOIN #chan"));
+            var user = _server.Users["other"];
+            Assert.IsNull(user.Account);
+            Assert.IsNull(user.RealName);
+        }
+
+        [TestMethod]
+        public void ExtendedJoinWithAccount()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan acc :realname"));
+            Assert.AreEqual("acc", _server.Account);
+            Assert.AreEqual("realname", _server.RealName);
+
+            _server.Parse(new Line(":other JOIN #chan acc2 :realname2"));
+            var user = _server.Users["other"];
+            Assert.AreEqual("acc2", user.Account);
+            Assert.AreEqual("realname2", user.RealName);
+        }
+
+        [TestMethod]
+        public void ExtendedJoinWithoutAccount()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan * :realname"));
+            Assert.AreEqual("", _server.Account);
+            Assert.AreEqual("realname", _server.RealName);
+
+            _server.Parse(new Line(":other JOIN #chan * :realname2"));
+            var user = _server.Users["other"];
+            Assert.AreEqual("", user.Account);
+            Assert.AreEqual("realname2", user.RealName);
+        }
+
+        [TestMethod]
+        public void AccountNotifyWithAccount()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line(":nickname ACCOUNT acc"));
+            Assert.AreEqual("acc", _server.Account);
+
+            _server.Parse(new Line(":other JOIN #chan"));
+            _server.Parse(new Line(":other ACCOUNT acc2"));
+            var user = _server.Users["other"];
+            Assert.AreEqual("acc2", user.Account);
+        }
+
+        [TestMethod]
+        public void AccountNotifyWithoutAccount()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line(":nickname ACCOUNT *"));
+            Assert.AreEqual("", _server.Account);
+
+            _server.Parse(new Line(":other JOIN #chan"));
+            _server.Parse(new Line(":other ACCOUNT *"));
+            var user = _server.Users["other"];
+            Assert.AreEqual("", user.Account);
+        }
+
+        [TestMethod]
+        public void HostmaskPrivmsgBoth()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line(":nickname!user@host PRIVMSG #chan :hi"));
+            Assert.AreEqual("user", _server.UserName);
+            Assert.AreEqual("host", _server.HostName);
+
+            _server.Parse(new Line(":other!user@host PRIVMSG #chan :hi"));
+            var user = _server.Users["other"];
+            Assert.AreEqual("user", user.UserName);
+            Assert.AreEqual("host", user.HostName);
+        }
+
+        [TestMethod]
+        public void HostmaskPrivmsgUser()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line(":nickname!user PRIVMSG #chan :hi"));
+            Assert.AreEqual("user", _server.UserName);
+            Assert.IsNull(_server.HostName);
+
+            _server.Parse(new Line(":other!user PRIVMSG #chan :hi"));
+            var user = _server.Users["other"];
+            Assert.AreEqual("user", user.UserName);
+            Assert.IsNull(user.HostName);
+        }
+
+        [TestMethod]
+        public void HostmaskPrivmsgHost()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line(":nickname@host PRIVMSG #chan :hi"));
+            Assert.IsNull(_server.UserName);
+            Assert.AreEqual("host", _server.HostName);
+
+            _server.Parse(new Line(":other@host PRIVMSG #chan :hi"));
+            var user = _server.Users["other"];
+            Assert.IsNull(user.UserName);
+            Assert.AreEqual("host", user.HostName);
+        }
+
+        [TestMethod]
+        public void VisibleHostWithoutUsername()
+        {
+            _server.Parse(new Line("396 * hostname"));
+            Assert.IsNull(_server.UserName);
+            Assert.AreEqual("hostname", _server.HostName);
+        }
+
+        [TestMethod]
+        public void VisibleHostWithUsername()
+        {
+            _server.Parse(new Line("396 * username@hostname"));
+            Assert.AreEqual("username", _server.UserName);
+            Assert.AreEqual("hostname", _server.HostName);
+        }
+
+        [TestMethod]
+        public void Who()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line(":other JOIN #chan"));
+            _server.Parse(new Line("352 * #chan user host * nickname * :0 real"));
+            _server.Parse(new Line("352 * #chan user2 host2 * other * :0 real2"));
+
+            Assert.AreEqual("user", _server.UserName);
+            Assert.AreEqual("host", _server.HostName);
+            Assert.AreEqual("real", _server.RealName);
+
+            var user = _server.Users["other"];
+            Assert.AreEqual("user2", user.UserName);
+            Assert.AreEqual("host2", user.HostName);
+            Assert.AreEqual("real2", user.RealName);
+        }
+
+        [TestMethod]
+        public void Chghost()
+        {
+            _server.Parse(new Line(":nickname!user@host JOIN #chan"));
+            _server.Parse(new Line(":nickname CHGHOST u h"));
+            Assert.AreEqual("u", _server.UserName);
+            Assert.AreEqual("h", _server.HostName);
+
+            _server.Parse(new Line(":other!user2@host2 JOIN #chan"));
+            _server.Parse(new Line(":other CHGHOST u2 h2"));
+            var user = _server.Users["other"];
+            Assert.AreEqual("u2", user.UserName);
+            Assert.AreEqual("h2", user.HostName);
+        }
+
+        [TestMethod]
+        public void Whois()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line("311 * nickname u h * :r"));
+            Assert.AreEqual("u", _server.UserName);
+            Assert.AreEqual("h", _server.HostName);
+            Assert.AreEqual("r", _server.RealName);
+
+            _server.Parse(new Line(":other JOIN #chan"));
+            _server.Parse(new Line(":other CHGHOST u2 h2"));
+            _server.Parse(new Line("311 * other u2 h2 * :r2"));
+            var user = _server.Users["other"];
+            Assert.AreEqual("u2", user.UserName);
+            Assert.AreEqual("h2", user.HostName);
+            Assert.AreEqual("r2", user.RealName);
+        }
+
+        [TestMethod]
+        public void AwaySet()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line(":other JOIN #chan"));
+            var user = _server.Users["other"];
+            Assert.IsNull(_server.Away);
+            Assert.IsNull(user.Away);
+
+            _server.Parse(new Line(":nickname AWAY :bye bye"));
+            _server.Parse(new Line(":other AWAY :ich geh weg"));
+            Assert.AreEqual("bye bye", _server.Away);
+            Assert.AreEqual("ich geh weg", user.Away);
+        }
+
+        [TestMethod]
+        public void AwayUnset()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line(":other JOIN #chan"));
+            _server.Parse(new Line(":nickname AWAY :bye bye"));
+            _server.Parse(new Line(":nickname AWAY"));
+            _server.Parse(new Line(":other AWAY :ich geh weg"));
+            _server.Parse(new Line(":other AWAY"));
+
+            var user = _server.Users["other"];
+            Assert.IsNull(_server.Away);
+            Assert.IsNull(user.Away);
+        }
+
+        [TestMethod]
+        public void Setname()
+        {
+            _server.Parse(new Line(":nickname JOIN #chan"));
+            _server.Parse(new Line(":other JOIN #chan"));
+            var user = _server.Users["other"];
+            Assert.IsNull(user.RealName);
+            Assert.IsNull(_server.RealName);
+
+            _server.Parse(new Line(":nickname SETNAME :new now know how"));
+            _server.Parse(new Line(":other SETNAME :tyrannosaurus hex"));
+            Assert.AreEqual("new now know how", _server.RealName);
+            Assert.AreEqual("tyrannosaurus hex", user.RealName);
+        }
+    }
+}
diff --git a/IRCStates/Tests/Who.cs b/IRCStates/Tests/Who.cs
new file mode 100644
index 0000000..d091785
--- /dev/null
+++ b/IRCStates/Tests/Who.cs
@@ -0,0 +1,61 @@
+using IRCTokens;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IRCStates.Tests
+{
+    [TestClass]
+    public class Who
+    {
+        private Server _server;
+
+        [TestInitialize]
+        public void TestInitialize()
+        {
+            _server = new Server("test");
+            _server.Parse(new Line("001 nickname"));
+            _server.Parse(new Line(":nickname JOIN #chan"));
+        }
+
+        [TestMethod]
+        public void WhoResponse()
+        {
+            _server.Parse(new Line("352 * #chan user host server nickname * :0 real"));
+            var user = _server.Users["nickname"];
+
+            Assert.AreEqual("user", user.UserName);
+            Assert.AreEqual("host", _server.HostName);
+            Assert.AreEqual("real", user.RealName);
+
+            Assert.AreEqual(user.UserName, _server.UserName);
+            Assert.AreEqual(user.HostName, _server.HostName);
+            Assert.AreEqual(user.RealName, _server.RealName);
+        }
+
+        [TestMethod]
+        public void Whox()
+        {
+            _server.Parse(new Line($"354 * {Server.WhoType} user realip host nickname account :real"));
+            var user = _server.Users["nickname"];
+
+            Assert.AreEqual("user", user.UserName);
+            Assert.AreEqual("host", user.HostName);
+            Assert.AreEqual("real", user.RealName);
+            Assert.AreEqual("account", user.Account);
+
+            Assert.AreEqual(user.UserName, _server.UserName);
+            Assert.AreEqual(user.HostName, _server.HostName);
+            Assert.AreEqual(user.RealName, _server.RealName);
+            Assert.AreEqual(user.Account, _server.Account);
+        }
+
+        [TestMethod]
+        public void WhoxNoAccount()
+        {
+            _server.Parse(new Line($"354 * {Server.WhoType} user realip host nickname 0 :real"));
+            var user = _server.Users["nickname"];
+
+            Assert.IsNull(user.Account);
+            Assert.AreEqual(user.Account, _server.Account);
+        }
+    }
+}